diff --git a/src/core/gpu/mod.rs b/src/core/gpu/mod.rs index ef44dc9..1b8b482 100644 --- a/src/core/gpu/mod.rs +++ b/src/core/gpu/mod.rs @@ -7,6 +7,7 @@ use crate::bitfield::Bit; use crate::num::FromPrimitive; mod mosaic; +mod obj; mod sfx; pub mod regs; @@ -23,11 +24,11 @@ const CYCLES_SCANLINE: usize = 1232; const CYCLES_VDRAW: usize = 197120; const CYCLES_VBLANK: usize = 83776; -const TILE_SIZE: u32 = 0x20; +pub const TILE_SIZE: u32 = 0x20; // TODO - remove the one in palette.rs bitfield! { - #[derive(Copy, Clone, Default)] + #[derive(Copy, Clone, Default, PartialEq)] pub struct Rgb15(u16); impl Debug; pub r, set_r: 4, 0; @@ -67,22 +68,6 @@ pub enum PixelFormat { BPP8 = 1, } -#[derive(Debug, Primitive, Clone, Copy)] -pub enum BGMode { - BGMode0 = 0, - BGMode1 = 1, - BGMode2 = 2, - BGMode3 = 3, - BGMode4 = 4, - BGMode5 = 5, -} - -impl From for BGMode { - fn from(v: u16) -> BGMode { - BGMode::from_u16(v).unwrap() - } -} - #[derive(Debug, PartialEq, Copy, Clone)] pub enum GpuState { HDraw = 0, @@ -122,32 +107,28 @@ impl std::ops::IndexMut for FrameBuffer { } #[derive(Copy, Clone)] -pub struct Scanline([Rgb15; DISPLAY_WIDTH]); +pub struct Scanline([T; DISPLAY_WIDTH]); -impl Default for Scanline { - fn default() -> Scanline { - Scanline([Rgb15(0); DISPLAY_WIDTH]) +impl Default for Scanline { + fn default() -> Scanline { + Scanline([Rgb15::TRANSPARENT; DISPLAY_WIDTH]) } } -impl fmt::Debug for Scanline { +impl fmt::Debug for Scanline { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Scanline: ")?; - for i in 0..6 { - write!(f, "#{:06x}, ", self[i].0)?; - } write!(f, "...") } } -impl std::ops::Index for Scanline { - type Output = Rgb15; +impl std::ops::Index for Scanline { + type Output = T; fn index(&self, index: usize) -> &Self::Output { &self.0[index] } } -impl std::ops::IndexMut for Scanline { +impl std::ops::IndexMut for Scanline { fn index_mut(&mut self, index: usize) -> &mut Self::Output { &mut self.0[index] } @@ -158,10 +139,10 @@ pub struct Bg { pub bgcnt: BgControl, pub bgvofs: u16, pub bghofs: u16, - line: Scanline, + line: Scanline, // for mosaic - mosaic_first_row: Scanline, + mosaic_first_row: Scanline, } #[derive(Debug, Default)] @@ -233,6 +214,7 @@ pub struct Gpu { pub bldalpha: BlendAlpha, pub bldy: u16, + pub obj_line: Scanline, pub frame_buffer: FrameBuffer, } @@ -255,6 +237,7 @@ impl Gpu { state: HDraw, current_scanline: 0, cycles: 0, + obj_line: Scanline::default(), frame_buffer: FrameBuffer([0; DISPLAY_WIDTH * DISPLAY_HEIGHT]), } } @@ -282,11 +265,20 @@ impl Gpu { } } - pub fn get_palette_color(&self, sb: &SysBus, index: u32, palette_index: u32) -> Rgb15 { + pub fn get_palette_color( + &self, + sb: &SysBus, + index: u32, + palette_index: u32, + offset: u32, + ) -> Rgb15 { if index == 0 || (palette_index != 0 && index % 16 == 0) { return Rgb15::TRANSPARENT; } - Rgb15(sb.palette_ram.read_16(2 * index + 0x20 * palette_index)) + Rgb15( + sb.palette_ram + .read_16(offset + 2 * index + 0x20 * palette_index), + ) } fn render_pixel(&mut self, x: i32, y: i32, p: Rgb15) { @@ -315,7 +307,7 @@ impl Gpu { // | [0] | [0][1] | [0] | [0][1] | // |___________|___________|_____________|___________| // - let mut screen_block = match (bg_width, bg_height) { + let mut sbb = match (bg_width, bg_height) { (256, 256) => 0, (512, 256) => bg_x / 256, (256, 512) => bg_y / 256, @@ -331,14 +323,13 @@ impl Gpu { let tile_py = (bg_y % 8) as u32; loop { - let mut map_addr = tilemap_base - + SCREEN_BLOCK_SIZE * screen_block - + 2 * index2d!(u32, se_row, se_column, 32); + let mut map_addr = + tilemap_base + SCREEN_BLOCK_SIZE * sbb + 2 * index2d!(u32, se_row, se_column, 32); for _ in se_row..32 { let entry = TileMapEntry(sb.vram.read_16(map_addr - VRAM_ADDR)); let tile_addr = tileset_base + entry.tile_index() * tile_size; - for tile_px in start_tile_x..=7 { + for tile_px in start_tile_x..8 { let index = self.read_pixel_index( sb, tile_addr, @@ -350,7 +341,7 @@ impl Gpu { PixelFormat::BPP4 => entry.palette_bank() as u32, PixelFormat::BPP8 => 0u32, }; - let color = self.get_palette_color(sb, index as u32, palette_bank); + let color = self.get_palette_color(sb, index as u32, palette_bank, 0); self.bg[bg].line[screen_x as usize] = color; screen_x += 1; if (DISPLAY_WIDTH as u32) == screen_x { @@ -362,7 +353,7 @@ impl Gpu { } se_row = 0; if bg_width == 512 { - screen_block = screen_block ^ 1; + sbb = sbb ^ 1; } } } @@ -395,7 +386,7 @@ impl Gpu { let bitmap_index = index2d!(x, y, DISPLAY_WIDTH); let bitmap_ofs = page_ofs + (bitmap_index as u32); let index = sb.vram.read_8(bitmap_ofs as Addr) as u32; - let color = self.get_palette_color(sb, index, 0); + let color = self.get_palette_color(sb, index, 0, 0); self.bg[bg].line[x] = color; } } @@ -403,14 +394,14 @@ impl Gpu { pub fn render_scanline(&mut self, sb: &mut SysBus) { // TODO - also render objs match self.dispcnt.mode() { - BGMode::BGMode0 => { + 0 => { for bg in 0..3 { if self.dispcnt.disp_bg(bg) { self.scanline_reg_bg(bg, sb); } } } - BGMode::BGMode1 => { + 1 => { if self.dispcnt.disp_bg(2) { self.scanline_aff_bg(2, sb); } @@ -421,7 +412,7 @@ impl Gpu { self.scanline_reg_bg(0, sb); } } - BGMode::BGMode2 => { + 2 => { if self.dispcnt.disp_bg(3) { self.scanline_aff_bg(3, sb); } @@ -429,14 +420,17 @@ impl Gpu { self.scanline_aff_bg(2, sb); } } - BGMode::BGMode3 => { + 3 => { self.scanline_mode3(2, sb); } - BGMode::BGMode4 => { + 4 => { self.scanline_mode4(2, sb); } _ => panic!("{:?} not supported", self.dispcnt.mode()), } + if self.dispcnt.disp_obj() { + self.render_objs(sb); + } self.mosaic_sfx(); let post_sfx_line = self.composite_sfx(sb); for x in 0..DISPLAY_WIDTH { diff --git a/src/core/gpu/obj.rs b/src/core/gpu/obj.rs new file mode 100644 index 0000000..6aabebe --- /dev/null +++ b/src/core/gpu/obj.rs @@ -0,0 +1,234 @@ +use super::super::SysBus; +use super::regs::*; +use super::*; + +use crate::core::sysbus::OAM_ADDR; + +const OVRAM: u32 = 0x0601_0000; +const PALRAM_OFS_FG: u32 = 0x200; +const ATTRS_SIZE: u32 = 2 * 3 + 2; + +struct ObjAttrs(Attribute0, Attribute1, Attribute2); + +struct ObjAffineParams { + pa: i16, + pb: i16, + pc: i16, + pd: i16, +} + +const AFFINE_FILL: u32 = 2 * 3; + +impl ObjAffineParams { + fn from_index(sb: &SysBus, index: u32) -> ObjAffineParams { + let mut offset = AFFINE_FILL + index * 16 * 2; + let pa = sb.read_16(offset) as i16; + offset += 2 + AFFINE_FILL; + let pb = sb.read_16(offset) as i16; + offset += 2 + AFFINE_FILL; + let pc = sb.read_16(offset) as i16; + offset += 2 + AFFINE_FILL; + let pd = sb.read_16(offset) as i16; + + ObjAffineParams { pa, pb, pc, pd } + } +} + +impl ObjAttrs { + fn size(&self) -> (usize, usize) { + let n = 8 << self.1.size(); + match self.0.shape() { + 0 /* Square */ => (n, n), + 1 /* Wide */ => (n, n >> 1), + 2 /* Tall */ => (n >> 1, n), + _ => unreachable!("invalid obj shape") + } + } + fn coords(&self) -> (usize, usize) { + (self.1.x_coord() as usize, self.0.y_coord() as usize) + } + fn tile_format(&self) -> (usize, PixelFormat) { + if self.0.is_8bpp() { + (0x40, PixelFormat::BPP8) + } else { + (0x20, PixelFormat::BPP4) + } + } + fn is_affine(&self) -> bool { + match self.0.objtype() { + ObjType::Affine | ObjType::AffineDoubleSize => true, + _ => false, + } + } + fn affine_index(&self) -> u32 { + let attr1 = (self.1).0; + ((attr1 >> 9) & 0x1f) as u32 + } + fn is_hidden(&self) -> bool { + self.0.objtype() == ObjType::Hidden + } + fn flip_xy(&self) -> (bool, bool) { + if !self.is_affine() { + (self.1.h_flip(), self.1.v_flip()) + } else { + (false, false) + } + } +} + +fn read_obj_attrs(sb: &SysBus, obj: usize) -> ObjAttrs { + let addr = ATTRS_SIZE * (obj as u32); + let attr0 = Attribute0(sb.oam.read_16(addr + 0)); + let attr1 = Attribute1(sb.oam.read_16(addr + 2)); + let attr2 = Attribute2(sb.oam.read_16(addr + 4)); + ObjAttrs(attr0, attr1, attr2) +} + +impl Gpu { + fn obj_tile_base(&self) -> u32 { + match self.dispcnt.mode() { + mode if mode > 2 => OVRAM + 0x4000, + _ => OVRAM, + } + } + + pub fn render_objs(&mut self, sb: &SysBus) { + let screen_y = self.current_scanline; + // reset the scanline + self.obj_line = Scanline::default(); + for obj_num in (0..128).rev() { + let obj = read_obj_attrs(sb, obj_num); + if obj.is_hidden() { + continue; + } + + let is_affine = obj.is_affine(); + if is_affine { + panic!("im not ready for that yet :("); + } + + let (obj_x, obj_y) = obj.coords(); + let (obj_w, obj_h) = obj.size(); + // skip this obj if not within its bounds. + if !(screen_y >= obj_y && screen_y < obj_y + obj_h) { + continue; + } + + let tile_base = self.obj_tile_base() + 0x20 * (obj.2.tile() as u32); + + let (tile_size, pixel_format) = obj.tile_format(); + let palette_bank = match pixel_format { + PixelFormat::BPP4 => obj.2.palette(), + _ => 0u32, + }; + + let tile_array_width = match self.dispcnt.obj_mapping() { + ObjMapping::OneDimension => obj_w / 8, + ObjMapping::TwoDimension => { + if obj.0.is_8bpp() { + 16 + } else { + 32 + } + } + }; + + let (xflip, yflip) = obj.flip_xy(); + + let end_x = obj_x + obj_w; + for screen_x in obj_x..end_x { + if screen_x > DISPLAY_WIDTH { + break; + } + let mut sprite_y = screen_y - obj_y; + let mut sprite_x = screen_x - obj_x; + if (!is_affine) { + sprite_y = if yflip { + obj_h - sprite_y - 1 + } else { + sprite_y + }; + sprite_x = if xflip { + obj_w - sprite_x - 1 + } else { + sprite_x + }; + } + let tile_x = sprite_x % 8; + let tile_y = sprite_y % 8; + let tile_addr = tile_base + + index2d!(u32, sprite_x / 8, sprite_y / 8, tile_array_width) + * (tile_size as u32); + let pixel_index = self.read_pixel_index( + sb, + tile_addr, + tile_x as u32, + tile_y as u32, + pixel_format, + ); + let pixel_color = + self.get_palette_color(sb, pixel_index as u32, palette_bank, PALRAM_OFS_FG); + if pixel_color != Rgb15::TRANSPARENT { + // TODO - handle priority + self.obj_line[screen_x] = pixel_color; + } + } + } + } +} + +#[derive(Debug, Primitive, Copy, Clone, PartialEq)] +enum ObjMode { + Normal = 0b00, + Sfx = 0b01, + Window = 0b10, + Forbidden = 0b11, +} + +impl From for ObjMode { + fn from(v: u16) -> ObjMode { + ObjMode::from_u16(v as u16).unwrap() + } +} + +#[derive(Debug, Primitive, Copy, Clone, PartialEq)] +enum ObjType { + Normal = 0b00, + Affine = 0b01, + Hidden = 0b10, + AffineDoubleSize = 0b11, +} + +impl From for ObjType { + fn from(v: u16) -> ObjType { + ObjType::from_u16(v as u16).unwrap() + } +} + +bitfield! { + pub struct Attribute0(u16); + u16; + y_coord, _ : 7, 0; + into ObjType, objtype, _: 9, 8; + into ObjMode, objmode, _: 11, 10; + pub mosaic, _: 12; + is_8bpp, _: 13; + shape, _: 15, 14; +} + +bitfield! { + pub struct Attribute1(u16); + u16; + x_coord, _ : 8, 0; + h_flip, _: 12; + v_flip, _: 13; + size, _: 15, 14; +} + +bitfield! { + pub struct Attribute2(u16); + u16; + tile, _: 9, 0; + priority, _: 11, 10; + into u32, palette, _: 15, 12; +} diff --git a/src/core/gpu/regs.rs b/src/core/gpu/regs.rs index 87df149..2077152 100644 --- a/src/core/gpu/regs.rs +++ b/src/core/gpu/regs.rs @@ -3,6 +3,12 @@ use super::*; pub const SCREEN_BLOCK_SIZE: u32 = 0x800; +#[derive(Debug, PartialEq)] +pub enum ObjMapping { + TwoDimension, + OneDimension, +} + impl DisplayControl { pub fn disp_bg(&self, bg: usize) -> bool { self.0.bit(8 + bg) @@ -10,6 +16,13 @@ impl DisplayControl { pub fn is_using_windows(&self) -> bool { self.disp_window0() || self.disp_window1() || self.disp_obj_window() } + pub fn obj_mapping(&self) -> ObjMapping { + if self.obj_character_vram_mapping() { + ObjMapping::OneDimension + } else { + ObjMapping::TwoDimension + } + } } impl BgControl { @@ -55,7 +68,7 @@ bitfield! { pub struct DisplayControl(u16); impl Debug; u16; - pub into BGMode, mode, set_mode: 2, 0; + pub mode, set_mode: 2, 0; pub display_frame, set_display_frame: 4, 4; pub hblank_interval_free, _: 5; pub obj_character_vram_mapping, _: 6; diff --git a/src/core/gpu/sfx.rs b/src/core/gpu/sfx.rs index 9737322..2bb7985 100644 --- a/src/core/gpu/sfx.rs +++ b/src/core/gpu/sfx.rs @@ -50,6 +50,15 @@ impl Gpu { // TODO - only BGs are supported, don't forget OBJs // priorities are 0-4 when 0 is the highest 'outer: for priority in 0..4 { + if bflags.contains(BlendFlags::OBJ) + && wflags.contains(WindowFlags::OBJ) + && !self.obj_line[screen_x].is_transparent() + { + return Some(Layer { + color: self.obj_line[screen_x], + blend_flag: BlendFlags::OBJ, + }); + } for bg in 0..4 { let c = self.bg[bg].line[screen_x]; let bflag = BlendFlags::from_bg(bg); @@ -138,8 +147,8 @@ impl Gpu { None } - pub fn composite_sfx(&self, sb: &SysBus) -> Scanline { - let mut line = Scanline::default(); + pub fn composite_sfx(&self, sb: &SysBus) -> Scanline { + let mut line: Scanline = Scanline::default(); let y = self.current_scanline; for x in 0..DISPLAY_WIDTH { let window = self.get_active_window_type(x, y);