diff --git a/README.md b/README.md index 3f8ed109c8ab0e88ed70238c3d8b14942218acde..92442418c33ead139e0f8fffbcc352dd3704cc96 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Boytacian +# Boytacean A Game Boy emulator that is written in Rust 🦀. diff --git a/examples/sdl/src/main.rs b/examples/sdl/src/main.rs index c241329b987dccbc03c0baa84ee26ab1bd918119..74b7b4982f7eacda4ba334cdba1dd0d9eba9695f 100644 --- a/examples/sdl/src/main.rs +++ b/examples/sdl/src/main.rs @@ -1,17 +1,111 @@ -use boytacean::gb::GameBoy; +use boytacean::{gb::GameBoy, ppu::{DISPLAY_WIDTH, DISPLAY_HEIGHT}}; +use sdl2::{video::Window, pixels::PixelFormatEnum, VideoSubsystem, TimerSubsystem, EventPump}; + +/// The base title to be used in the window. +static TITLE: &'static str = "Boytacean"; + +pub struct Graphics { + window: Window, + video_subsystem: VideoSubsystem, + timer_subsystem: TimerSubsystem, + event_pump: EventPump, +} + +fn start_sdl() -> Graphics { + // initializes the SDL sub-system, making it ready to be + // used for display of graphics and audio + let sdl = sdl2::init().unwrap(); + let video_subsystem = sdl.video().unwrap(); + let timer_subsystem = sdl.timer().unwrap(); + let audio_subsystem = sdl.audio().unwrap(); + let event_pump = sdl.event_pump().unwrap(); + + // initialized the fonts context to be used + // in the loading of fonts + let ttf_context = sdl2::ttf::init().unwrap(); + + // creates the system window that is going to be used to + // show the emulator and sets it to the central are o screen + let mut window = video_subsystem + .window( + TITLE, + 2 as u32 * DISPLAY_WIDTH as u32, //@todo check screen scale + 2 as u32 * DISPLAY_HEIGHT as u32, //@todo check screen scale + ) + .resizable() + .position_centered() + .opengl() + .build() + .unwrap(); + + Graphics { + window: window, + video_subsystem: video_subsystem, + timer_subsystem: timer_subsystem, + event_pump: event_pump + } +} fn main() { + let mut graphics = start_sdl(); + + let mut canvas = graphics.window.into_canvas().accelerated().build().unwrap(); + canvas.clear(); + canvas.present(); + + let texture_creator = canvas.texture_creator(); + + // creates the texture streaming that is going to be used + // as the target for the pixel buffer + let mut texture = texture_creator + .create_texture_streaming( + PixelFormatEnum::RGB24, + DISPLAY_WIDTH as u32, + DISPLAY_HEIGHT as u32, + ) + .unwrap(); + let mut game_boy = GameBoy::new(); game_boy.load_boot_default(); - for i in 0..37000 { - // runs the Game Boy clock, this operations should - // include the advance of both the CPU and the PPU - game_boy.clock(); + let mut counter = 0; - if game_boy.cpu().pc() >= 0x6032 { - println!("{}", i); + 'main: loop { + if counter >= 7000000 { break; } + + while let Some(event) = graphics.event_pump.poll_event() { + } + + let mut counter_ticks = 0u32; + + loop { + // limits the number of ticks to the typical number + // of ticks required to do a complete PPU draw + if counter_ticks >= 70224 { + break; + } + + // runs the Game Boy clock, this operations should + // include the advance of both the CPU and the PPU + counter_ticks += game_boy.clock() as u32; + } + + counter += counter_ticks; + + let frame_buffer = game_boy.frame_buffer().as_ref(); + texture + .update(None, frame_buffer, DISPLAY_WIDTH as usize * 3) + .unwrap(); + canvas.copy(&texture, None, None).unwrap(); + + // presents the canvas effectively updating the screen + // information presented to the user + canvas.present(); + + graphics.timer_subsystem.delay(17); } + + //println!("{:?}", game_boy.frame_buffer().as_ref()); } diff --git a/src/gb.rs b/src/gb.rs index 182ec36f7f6a26ae2966be8dfb53b414fae999c0..b3e4949f704ef2c2bfe6451054818f5d20348543 100644 --- a/src/gb.rs +++ b/src/gb.rs @@ -38,6 +38,10 @@ impl GameBoy { self.cpu.ppu() } + pub fn frame_buffer(&mut self) -> &Box<[u8; 73920]> { + &(self.ppu().frame_buffer) + } + pub fn load_boot(&mut self, path: &str) { let data = read_file(path); self.cpu.mmu().write_boot(0x0000, &data); diff --git a/src/ppu.rs b/src/ppu.rs index 5935dc888987e4a8f1d1f373d2aed0e3178e4e81..fc1680067ef5b6049df4c050628defb55b273558 100644 --- a/src/ppu.rs +++ b/src/ppu.rs @@ -1,23 +1,23 @@ pub const VRAM_SIZE: usize = 8192; pub const HRAM_SIZE: usize = 128; pub const PALETTE_SIZE: usize = 4; -pub const RGBA_SIZE: usize = 4; +pub const RGB_SIZE: usize = 3; /// The number of tiles that can be store in Game Boy's /// VRAM memory according to specifications. pub const TILE_COUNT: usize = 384; /// The width of the Game Boy screen in pixels. -pub const SCREEN_WIDTH: usize = 160; +pub const DISPLAY_WIDTH: usize = 160; /// The height of the Game Boy screen in pixels. -pub const SCREEN_HEIGHT: usize = 154; +pub const DISPLAY_HEIGHT: usize = 154; /// Represents the Game Boy PPU (Pixel Processing Unit) and controls /// all of the logic behind the graphics processing and presentation. /// Should store both the VRAM and HRAM together with the internal /// graphic related registers. -/// Outputs the screen as a RGBA 8 bit frame buffer. +/// Outputs the screen as a RGB 8 bit frame buffer. /// /// # Basic usage /// ```rust @@ -25,9 +25,9 @@ pub const SCREEN_HEIGHT: usize = 154; /// ppu.tick(); /// ``` pub struct Ppu { - /// The 8 bit based RGBA frame buffer with the + /// The 8 bit based RGB frame buffer with the /// processed set of pixels ready to be displayed on screen. - pub frame_buffer: Box<[u8; SCREEN_WIDTH * SCREEN_HEIGHT * RGBA_SIZE]>, + pub frame_buffer: Box<[u8; DISPLAY_WIDTH * DISPLAY_HEIGHT * RGB_SIZE]>, /// Video dedicated memory (VRAM) where both the tiles and /// the sprites are going to be stored. pub vram: [u8; VRAM_SIZE], @@ -38,7 +38,7 @@ pub struct Ppu { /// PPU related structures. tiles: [[[u8; 8]; 8]; TILE_COUNT], /// The palette of colors that is currently loaded in Game Boy. - palette: [[u8; RGBA_SIZE]; PALETTE_SIZE], + palette: [[u8; RGB_SIZE]; PALETTE_SIZE], /// The scroll Y register that controls the Y offset /// of the background. scy: u8, @@ -71,11 +71,11 @@ pub enum PpuMode { impl Ppu { pub fn new() -> Ppu { Ppu { - frame_buffer: Box::new([0u8; SCREEN_WIDTH * SCREEN_HEIGHT * RGBA_SIZE]), + frame_buffer: Box::new([0u8; DISPLAY_WIDTH * DISPLAY_HEIGHT * RGB_SIZE]), vram: [0u8; VRAM_SIZE], hram: [0u8; HRAM_SIZE], tiles: [[[0u8; 8]; 8]; TILE_COUNT], - palette: [[0u8; RGBA_SIZE]; PALETTE_SIZE], + palette: [[0u8; RGB_SIZE]; PALETTE_SIZE], scy: 0x0, scx: 0x0, line: 0x0, @@ -168,10 +168,10 @@ impl Ppu { 0x0047 => { for index in 0..PALETTE_SIZE { match (value >> (index * 2)) & 3 { - 0 => self.palette[index] = [255, 255, 255, 255], - 1 => self.palette[index] = [192, 192, 192, 255], - 2 => self.palette[index] = [96, 96, 96, 255], - 3 => self.palette[index] = [0, 0, 0, 255], + 0 => self.palette[index] = [255, 255, 255], + 1 => self.palette[index] = [192, 192, 192], + 2 => self.palette[index] = [96, 96, 96], + 3 => self.palette[index] = [0, 0, 0], color_index => panic!("Invalid palette color index {:04x}", color_index), } } @@ -225,10 +225,10 @@ impl Ppu { } // calculates the frame buffer offset position assuming the proper - // Game Boy screen width and RGBA pixel (4 bytes) size - let mut frame_offset = self.line as usize * SCREEN_WIDTH * RGBA_SIZE; + // Game Boy screen width and RGB pixel (3 bytes) size + let mut frame_offset = self.line as usize * DISPLAY_WIDTH * RGB_SIZE; - for _index in 0..SCREEN_WIDTH { + for _index in 0..DISPLAY_WIDTH { // in case the end of tile width has been reached then // a new tile must be retrieved for plotting if x == 8 { @@ -257,9 +257,8 @@ impl Ppu { self.frame_buffer[frame_offset] = color[0]; self.frame_buffer[frame_offset + 1] = color[1]; self.frame_buffer[frame_offset + 2] = color[2]; - self.frame_buffer[frame_offset + 3] = color[3]; - frame_offset += RGBA_SIZE; + frame_offset += RGB_SIZE; // increments the current tile X position in drawing x += 1;