From dc0656475f066e7215027bb9ec6696371ec9c036 Mon Sep 17 00:00:00 2001 From: Kevin Hamacher Date: Wed, 1 Jun 2016 23:47:07 +0200 Subject: [PATCH] Some cleanup; Delayed rendering SDL will only used as soon as all pixels have been described in the internal buffer. --- src/cartridge.rs | 6 +- src/cpu.rs | 7 -- src/display.rs | 301 ++++++++++++++++++++++++++++++----------------- 3 files changed, 198 insertions(+), 116 deletions(-) diff --git a/src/cartridge.rs b/src/cartridge.rs index 8984512..f373ffd 100644 --- a/src/cartridge.rs +++ b/src/cartridge.rs @@ -6,7 +6,7 @@ enum MemoryBankControllerType { MBC1, MBC2, MBC3, - HuC1, + //HuC1, } #[derive(Debug)] @@ -18,7 +18,6 @@ enum RamSize { } pub struct Cartridge { - mbc_type: MemoryBankControllerType, mbc: Box, } @@ -67,12 +66,11 @@ impl Cartridge { MemoryBankControllerType::MBC1 => Box::new(super::mbc::mbc1::MBC1::new(rom, ram)), MemoryBankControllerType::MBC2 => Box::new(super::mbc::mbc2::MBC2::new(rom, ram)), MemoryBankControllerType::MBC3 => Box::new(super::mbc::mbc3::MBC3::new(rom, ram)), - _ => panic!("{:?} not implemented", mbc_type), + // _ => panic!("{:?} not implemented", mbc_type), }; Cartridge { mbc: mbc, - mbc_type: mbc_type, } } diff --git a/src/cpu.rs b/src/cpu.rs index d092f85..c9419bb 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -565,12 +565,6 @@ impl CPU { self.set_8bit_reg(b, value as u8); } - fn dump_stack(&self) { - for i in self.sp .. 0xFFFE { - println!("[{:#04X}]: {:#02X}", i, self.interconnect.read_byte(i)); - } - } - #[inline] fn call(&mut self, dst: u16) { let ip = self.ip; @@ -1652,7 +1646,6 @@ impl CPU { _ => panic!("Unknown instruction: {:02x}", instruction) }; } - // self.dump_stack(); self.interconnect.tick(cycles); } } diff --git a/src/display.rs b/src/display.rs index c619966..1727a13 100644 --- a/src/display.rs +++ b/src/display.rs @@ -14,13 +14,16 @@ const TICKS_END_HBLANK: u16 = 204; const TICKS_END_VBLANK: u16 = 456; // Display size -const GB_PIXELS_X: u16 = 160; -const GB_PIXELS_Y: u16 = 144; -const SCALE: u16 = 4; +const GB_PIXELS_X: usize = 160; +const GB_PIXELS_Y: usize = 144; +const SCALE: usize = 4; + +const WND_RES_X: usize = GB_PIXELS_X * SCALE; +const WND_RES_Y: usize = GB_PIXELS_Y * SCALE; // Control flags const CTRL_LCD_DISPLAY_ENABLE: u8 = 1 << 7; -const CTRL_TILE_MAP_SELECT: u8 = 1 << 6; +const CTRL_WND_TILE_MAP_SELECT: u8 = 1 << 6; const CTRL_WND_DISPLAY_ENABLE: u8 = 1 << 5; const CTRL_BG_WINDOW_TILE_DATA_SELECT: u8 = 1 << 4; const CTRL_BG_TILE_MAP_SELECT: u8 = 1 << 3; @@ -35,22 +38,13 @@ const STAT_MODE_VBLANK_INT: u8 = 1 << 4; const STAT_MODE_HBLANK_INT: u8 = 1 << 3; // Sprite flags -/* - Bit7 OBJ-to-BG Priority (0=OBJ Above BG, 1=OBJ Behind BG color 1-3) - (Used for both BG and Window. BG color 0 is always behind OBJ) - Bit6 Y flip (0=Normal, 1=Vertically mirrored) - Bit5 X flip (0=Normal, 1=Horizontally mirrored) - Bit4 Palette number **Non CGB Mode Only** (0=OBP0, 1=OBP1) - Bit3 Tile VRAM-Bank **CGB Mode Only** (0=Bank 0, 1=Bank 1) - Bit2-0 Palette number **CGB Mode Only** (OBP0-7) -*/ - const SPRITE_OBJ_BG_PRIORITY: u8 = 1 << 7; const SPRITE_Y_FLIP: u8 = 1 << 6; const SPRITE_X_FLIP: u8 = 1 << 5; const SPRITE_PALETTE_NO: u8 = 1 << 4; // NonCGB only -const SPRITE_TILE_VRAM_BANK: u8 = 1 << 3; // CGB only +// const SPRITE_TILE_VRAM_BANK: u8 = 1 << 3; // CGB only +// Display color const MONOCHROME_PALETTE: &'static [[f64; 3]; 4] = &[ [0.605, 0.734, 0.059], [0.543, 0.672, 0.059], @@ -58,6 +52,29 @@ const MONOCHROME_PALETTE: &'static [[f64; 3]; 4] = &[ [0.059, 0.219, 0.059], ]; +#[derive(Debug, Copy, Clone)] +enum PixelOrigin { + Empty, + Background, + Window, + Sprite +} + +#[derive(Copy, Clone)] +struct Pixel { + origin: PixelOrigin, + color: sdl2::pixels::Color, +} + +impl Default for Pixel { + fn default() -> Pixel { + Pixel { + origin: PixelOrigin::Empty, + color: sdl2::pixels::Color::RGB(255, 0, 0), + } + } +} + #[derive(Debug)] enum DisplayMode { ReadOAMMemory, @@ -85,19 +102,19 @@ impl Sprite { } fn is_foreground(&self) -> bool { - self.flags & SPRITE_OBJ_BG_PRIORITY == 0 + (self.flags & SPRITE_OBJ_BG_PRIORITY) == 0 } fn is_x_flipped(&self) -> bool { - self.flags & SPRITE_X_FLIP == SPRITE_X_FLIP + (self.flags & SPRITE_X_FLIP) == SPRITE_X_FLIP } fn is_y_flipped(&self) -> bool { - self.flags & SPRITE_Y_FLIP == SPRITE_Y_FLIP + (self.flags & SPRITE_Y_FLIP) == SPRITE_Y_FLIP } fn palette(&self) -> u8 { - if self.flags & SPRITE_PALETTE_NO == 0 { + if (self.flags & SPRITE_PALETTE_NO) == 0 { 0 } else { 1 @@ -131,15 +148,18 @@ pub struct Display { vblank_interrupt: bool, stat_interrupt: bool, + + pixels: [Pixel; GB_PIXELS_X * GB_PIXELS_Y], } impl Display { pub fn new() -> Display { let sdl_ctx = sdl2::init().unwrap(); let video_ctx = sdl_ctx.video().unwrap(); - let wnd = video_ctx.window("RustBoy", (GB_PIXELS_X * SCALE) as u32, (GB_PIXELS_Y * SCALE) as u32).position_centered().build().expect("Failed to create window :<"); + let wnd = video_ctx.window("RustBoy", WND_RES_X as u32, WND_RES_Y as u32).position_centered().build().expect("Failed to create window :<"); let renderer = wnd.renderer().build().expect("Could not build renderer"); let event_pump = sdl_ctx.event_pump().expect("Getting event pump failed"); + let pixels = [Pixel::default(); (GB_PIXELS_X as usize) * (GB_PIXELS_Y as usize)]; Display { control: 0, @@ -164,14 +184,50 @@ impl Display { vblank_interrupt: false, stat_interrupt: false, + pixels: pixels, } } #[inline] - fn set_pixel(&mut self, x: u8, y: u8, color: sdl2::pixels::Color) { - self.renderer.set_draw_color(color); - self.renderer.fill_rect(sdl2::rect::Rect::new((x as i32) * SCALE as i32, (y as i32) * SCALE as i32, SCALE as u32, SCALE as u32)) - .expect("Draw failed"); + fn set_pixel(&mut self, x: u8, y: u8, color: sdl2::pixels::Color, origin: PixelOrigin) { + let p = &mut self.pixels[(x as usize) + (y as usize) * GB_PIXELS_X]; + p.color = color; + p.origin = origin; + } + + #[inline] + fn get_pixel_origin(&self, x: u8, y: u8) -> PixelOrigin { + self.pixels[(x as usize) + (y as usize) * GB_PIXELS_X].origin + } + + fn render_screen(&mut self) { + for y in 0 .. GB_PIXELS_Y { + for x in 0 .. GB_PIXELS_X { + let f = &mut self.pixels[(x as usize) + (y as usize) * GB_PIXELS_X]; + self.renderer.set_draw_color(f.color); + self.renderer.fill_rect( + sdl2::rect::Rect::new( + x as i32 * SCALE as i32, + y as i32 * SCALE as i32, + SCALE as u32, + SCALE as u32, + ) + ).expect("Rendering failed"); + + // Clear origin after rendering + f.origin = PixelOrigin::Empty; + } + } + + self.renderer.set_draw_color(sdl2::pixels::Color::RGB(255, 0, 255)); + self.renderer.draw_rect( + sdl2::rect::Rect::new( + 0, + 0, + (GB_PIXELS_X * SCALE) as u32, + (GB_PIXELS_Y * SCALE) as u32, + ) + ).expect("Rendering failed"); } #[inline] @@ -199,38 +255,27 @@ impl Display { #[inline] pub fn write_byte(&mut self, addr: u16, val: u8) { match addr { - 0x8000 ... 0x9FFF => { - // println!("VRAM: Write {:02X} to {:04X}", val, addr); - self.vram[(addr - 0x8000) as usize] = val; - } - 0xFE00 ... 0xFE9F => { - // println!("OAM: Write {:02X} to {:04X}", val, addr); - self.oam[(addr - 0xFE00) as usize] = val; - } + 0x8000 ... 0x9FFF => self.vram[(addr - 0x8000) as usize] = val, + 0xFE00 ... 0xFE9F => self.oam[(addr - 0xFE00) as usize] = val, 0xFF40 => self.control = val, 0xFF41 => self.status = val, 0xFF42 => self.scrolly = val, 0xFF43 => self.scrollx = val, 0xFF44 => self.curline = 0, 0xFF45 => self.lyc = val, - 0xFF47 => { - println!("BG PALETTE = {:02X}", val); - self.background_palette = val; - } - 0xFF48 => { - println!("OBJ0 PALETTE = {:02X}", val); - self.object_palette_0 = val; - } - 0xFF49 => { - println!("OBJ1 PALETTE = {:02X}", val); - self.object_palette_1 = val; - } + 0xFF47 => self.background_palette = val, + 0xFF48 => self.object_palette_0 = val, + 0xFF49 => self.object_palette_1 = val, 0xFF4A => { - println!("WY set to {:02X}", val); + if self.windowy != val { + println!("WY set to {:02X}", val); + } self.windowy = val; } 0xFF4B => { - println!("WX set to {:02X}", val); + if self.windowx != val { + println!("WX set to {:02X}", val); + } self.windowx = val; } _ => panic!("Display: Write {:02X} to {:04X} unsupported", val, addr), @@ -300,11 +345,6 @@ impl Display { if self.status & STAT_MODE_VBLANK_INT > 0 { self.stat_interrupt = true; } - - // render frame. - self.renderer.present(); - self.renderer.set_draw_color(sdl2::pixels::Color::RGB(255, 255, 255)); - self.renderer.clear(); } else { self.current_mode = DisplayMode::ReadOAMMemory; // Mode 2 again if self.status & STAT_MODE_OAM_INT > 0 { @@ -325,6 +365,10 @@ impl Display { } if self.curline > 153 { self.current_mode = DisplayMode::ReadOAMMemory; // Mode 2, scanline. + self.render_screen(); + self.renderer.present(); + self.renderer.set_draw_color(sdl2::pixels::Color::RGB(255, 255, 255)); + self.renderer.clear(); self.curline = 0; if self.status & STAT_MODE_OAM_INT > 0 { self.stat_interrupt = true; @@ -346,9 +390,14 @@ impl Display { } } - fn render_sprites(&mut self, sprites: &Vec, foreground: bool) { + fn render_sprites(&mut self, sprites: &Vec) { if self.control & CTRL_BG_SPRITE_ENABLE > 0 { + let mut num_rendered: u8 = 0; for i in 0 .. 39 { + // Gameboy limitation + if num_rendered > 10 { + break; + } let sprite = &sprites[i]; // Skip hidden sprites @@ -356,12 +405,6 @@ impl Display { continue; } - // Should we draw this sprite this iteration - if foreground && !sprite.is_foreground() { - continue; - } else if !foreground && sprite.is_foreground() { - continue; - } // Calculate correct coords let x: u8 = sprite.x.wrapping_sub(8); @@ -371,6 +414,7 @@ impl Display { // Is this sprite on the current line? if y.wrapping_add(8) >= render_y && y <= render_y { + num_rendered += 1; // Flip sprite, TODO: Validate let y_o: u8 = match sprite.is_y_flipped() { true => y ^ 7, @@ -396,8 +440,17 @@ impl Display { for x_o in 0 .. limit { let b1: bool; let b2: bool; - let mut factor = 0; + let scr_x = x.wrapping_add(x_o); + let scr_y = render_y; + let pixel_origin = self.get_pixel_origin(scr_x, scr_y); + // Do not draw if the sprite should be drawn in the background + if !sprite.is_foreground() { + match pixel_origin { + PixelOrigin::Background | PixelOrigin::Window => continue, + _ => {} + } + } let x_o2: u8 = match sprite.is_x_flipped() { true => x_o ^ 7, false => x_o, @@ -411,11 +464,6 @@ impl Display { b2 = (tile_line_2 & 1 << (7 - x_o2)) > 0; } - // -> self.background_palette. - //0xFF47 => self.background_palette = val, - //0xFF48 => self.object_palette_0 = val, - //0xFF49 => self.object_palette_1 = val, - // Sprites may be transparent. if !b1 && !b2 { continue; @@ -440,24 +488,43 @@ impl Display { let entry = MONOCHROME_PALETTE[lookup[c as usize] as usize]; // Draw stuff. We're currently only in monochrome mode - self.set_pixel(x.wrapping_add(x_o), render_y, sdl2::pixels::Color::RGB((entry[0]*255.0) as u8, (entry[1]*255.0) as u8, (entry[2]*255.0) as u8)); + self.set_pixel(x.wrapping_add(x_o), render_y, sdl2::pixels::Color::RGB((entry[0]*255.0) as u8, (entry[1]*255.0) as u8, (entry[2]*255.0) as u8), PixelOrigin::Sprite); } } } } } + #[inline] + fn get_bg_window_tile_addr(&self, tile_id: u8) -> usize { + if (self.control & CTRL_BG_WINDOW_TILE_DATA_SELECT) == CTRL_BG_WINDOW_TILE_DATA_SELECT { + let base_addr = 0x0000; + base_addr + (tile_id as usize) * 16 + } else { + let base_addr = 0x0800; + let tile_id = (128u8 as i8).wrapping_add(tile_id as i8) as u8; + base_addr + (tile_id as usize) * 16 + } + } + #[inline] fn renderscan(&mut self) { // Points to the background map offset to use. let background_map: usize; + let window_map: usize; // verify! - if self.control & CTRL_BG_TILE_MAP_SELECT > 0 { + if self.control & CTRL_BG_TILE_MAP_SELECT == CTRL_BG_TILE_MAP_SELECT { background_map = 0x1C00; } else { background_map = 0x1800; } + if self.control & CTRL_WND_TILE_MAP_SELECT == CTRL_WND_TILE_MAP_SELECT { + window_map = 0x1C00; + } else { + window_map = 0x1800; + } + // Those are pixel units, not tile units. let map_offset_y: u8 = self.scrolly; let map_offset_x: u8 = self.scrollx; @@ -489,55 +556,27 @@ impl Display { queue.reverse(); - // Draw sprites behind the background - self.render_sprites(&queue, false); - // Render background - if self.control & CTRL_BG_DISPLAY > 0 { - // Draw borders if enabled - if false { - for t_x in 0 .. 160/8 { - self.renderer.set_draw_color(sdl2::pixels::Color::RGB(0xFF, 10, 0xFF)); - - let rx: i32 = (t_x as i32)*8 + map_offset_x as i32; - let ry: i32 = ((render_y as u32) & 0xFFFFFFF8) as i32 - map_offset_y as i32; - let ts: u32 = 8u32 * (SCALE as u32); - self.renderer.draw_rect( - sdl2::rect::Rect::new(rx * SCALE as i32, ry * SCALE as i32, ts, ts) - ).expect("Draw failed"); - } - } + if (self.control & CTRL_BG_DISPLAY) == CTRL_BG_DISPLAY { // Render pixels (20 tiles) for render_x in 0 .. 160 { // Absolute render coordinates let render_abs_x = map_offset_x.wrapping_add(render_x); let render_abs_y = map_offset_y.wrapping_add(render_y); - let tile_index_x: u8 = render_abs_x / 8; - let tile_offset_x: u8 = render_abs_x % 8; + let tile_index_x: u8 = render_abs_x >> 3; + let tile_offset_x: u8 = render_abs_x & 7; - let tile_index_y: u8 = render_abs_y / 8; - let tile_offset_y: u8 = render_abs_y % 8; + let tile_index_y: u8 = render_abs_y >> 3; + let tile_offset_y: u8 = render_abs_y & 7; let vram_offset: usize = background_map + (tile_index_y as usize) * 32 + tile_index_x as usize; // Obtain tile ID in this area - let mut tile_id = self.vram[vram_offset]; + let tile_id = self.vram[vram_offset]; // Obtain tile information - let tile_base_addr: usize; - if self.control & CTRL_BG_WINDOW_TILE_DATA_SELECT > 0 { - tile_base_addr = 0x0000; - } else { - tile_base_addr = 0x0800; - // This set goes from -127 to 127. - // TODO: Fix this. (?) - let s_tid: i8 = tile_id as i8; - tile_id = ((128u8 as i8).wrapping_add(s_tid)) as u8; - // panic!("OH MY GOD, this wasn't tested yet"); - } - - let tile_base_addr: usize = tile_base_addr + (tile_id as usize) * 16; + let tile_base_addr: usize = self.get_bg_window_tile_addr(tile_id); let tile_line_1 = self.vram[tile_base_addr + (tile_offset_y as usize) * 2]; let tile_line_2 = self.vram[tile_base_addr + (tile_offset_y as usize) * 2 + 1]; @@ -545,6 +584,7 @@ impl Display { let b1: bool = (tile_line_1 & 1 << (7 - tile_offset_x)) > 0; let b2: bool = (tile_line_2 & 1 << (7 - tile_offset_x)) > 0; + // Lookup the color let c = (b1 as u8) * 2 + b2 as u8; let lookup: [u8; 4] = [ self.background_palette & 3, @@ -554,12 +594,63 @@ impl Display { ]; let entry = MONOCHROME_PALETTE[lookup[c as usize] as usize]; - // Draw stuff. We're currently only in monochrome mode - self.set_pixel(render_x, render_y, sdl2::pixels::Color::RGB((entry[0]*255.0) as u8, (entry[1]*255.0) as u8, (entry[2]*255.0) as u8)); + let origin = match c { + 0 => PixelOrigin::Empty, // Hack so that objects will be in front of it. + _ => PixelOrigin::Background, + }; + self.set_pixel(render_x, render_y, sdl2::pixels::Color::RGB((entry[0]*255.0) as u8, (entry[1]*255.0) as u8, (entry[2]*255.0) as u8), origin); } - if self.control & CTRL_BG_SPRITE_ENABLE > 0 { - self.render_sprites(&queue, true); + if (self.control & CTRL_BG_SPRITE_ENABLE) == CTRL_BG_SPRITE_ENABLE { + self.render_sprites(&queue); + } + } + + if (self.control & CTRL_WND_DISPLAY_ENABLE) == CTRL_WND_DISPLAY_ENABLE { + // Draw 'window' over the background. + // Screen coordinates of the top left corner are WX-7, WY + if self.windowx < 167 && self.windowy < 144 { + //let rx = self.windowx.wrapping_add(7); + let rx = 7u8.wrapping_sub(self.windowx); + let ry = render_y.wrapping_add(self.windowy); + for r_x in 0 .. 160u8 { + let render_x = r_x.wrapping_add(rx); + // Absolute render coordinates + let tile_index_x: u8 = render_x >> 3; + let tile_offset_x: u8 = render_x & 7; + let tile_index_y: u8 = ry >> 3; + let tile_offset_y: u8 = ry & 7; + + let vram_offset: usize = window_map + (tile_index_y as usize) * 32 + tile_index_x as usize; + + // Obtain tile ID in this area + let tile_id = self.vram[vram_offset]; + + // Obtain tile information + let tile_base_addr: usize = self.get_bg_window_tile_addr(tile_id); + let tile_line_1 = self.vram[tile_base_addr + (tile_offset_y as usize) * 2]; + let tile_line_2 = self.vram[tile_base_addr + (tile_offset_y as usize) * 2 + 1]; + + // Get the correct bit + let b1: bool = (tile_line_1 & 1 << (7 - tile_offset_x)) > 0; + let b2: bool = (tile_line_2 & 1 << (7 - tile_offset_x)) > 0; + + let c = (b1 as u8) * 2 + b2 as u8; + let lookup: [u8; 4] = [ + self.background_palette & 3, + (self.background_palette >> 2) & 3, + (self.background_palette >> 4) & 3, + (self.background_palette >> 6) & 3, + ]; + let entry = MONOCHROME_PALETTE[lookup[c as usize] as usize]; + + // Draw stuff. We're currently only in monochrome mode + let origin = match c { + 0 => PixelOrigin::Empty, // Hack so that objects will be in front of it. + _ => PixelOrigin::Window, + }; + self.set_pixel(render_x, render_y, sdl2::pixels::Color::RGB((entry[0]*255.0) as u8, (entry[1]*255.0) as u8, (entry[2]*255.0) as u8), origin); + } } } }