From 17560eeb0c63b513c8d182f61e97a380206ac5cc Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Fri, 7 Feb 2020 14:39:35 +0200 Subject: [PATCH] fix: Completly refactor GPU scanline composition code. This change fixes #9 and emulates: - Correct layer ordering - Correct emulation of blending sfx - Correct emulation of the object window (BIOS boot animation is now fixed) Former-commit-id: caf46fe4b62cc54e6f2c02a8001da552f8e6b54a --- Cargo.toml | 1 + src/core/gba.rs | 1 - src/core/gpu/layer.rs | 91 ++++++++++ src/core/gpu/mod.rs | 86 ++++------ src/core/gpu/mosaic.rs | 2 +- src/core/gpu/regs.rs | 34 ++-- src/core/gpu/render/obj.rs | 70 ++++---- src/core/gpu/sfx.rs | 332 ++++++++++++++++++++++--------------- src/core/gpu/window.rs | 79 +++++++++ src/lib.rs | 1 + 10 files changed, 456 insertions(+), 241 deletions(-) create mode 100644 src/core/gpu/layer.rs create mode 100644 src/core/gpu/window.rs diff --git a/Cargo.toml b/Cargo.toml index b03b809..e3f04c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ bytesize = "1.0.0" memmem = "0.1.1" log = "0.4.8" flexi_logger = {version = "0.14", features = ["colors"]} +arrayvec = "0.5.1" rustyline = {version = "5.0.0", optional = true} nom = {version = "5.0.0", optional = true} diff --git a/src/core/gba.rs b/src/core/gba.rs index 99f7dde..fd4d51f 100644 --- a/src/core/gba.rs +++ b/src/core/gba.rs @@ -84,7 +84,6 @@ impl GameBoyAdvance { pub fn frame(&mut self) { self.key_poll(); - self.sysbus.io.gpu.clear(); while self.sysbus.io.gpu.vcount != DISPLAY_HEIGHT { self.step(); } diff --git a/src/core/gpu/layer.rs b/src/core/gpu/layer.rs new file mode 100644 index 0000000..3d953bb --- /dev/null +++ b/src/core/gpu/layer.rs @@ -0,0 +1,91 @@ +use num::FromPrimitive; + +use super::*; + +#[derive(Primitive, Debug, Ord, Eq, PartialOrd, PartialEq, Clone, Copy)] +pub enum RenderLayerKind { + Backdrop = 0b00100000, + Background3 = 0b00001000, + Background2 = 0b00000100, + Background1 = 0b00000010, + Background0 = 0b00000001, + Objects = 0b00010000, +} + +impl RenderLayerKind { + pub fn get_blend_flag(&self) -> BlendFlags { + match self { + RenderLayerKind::Background0 => BlendFlags::BG0, + RenderLayerKind::Background1 => BlendFlags::BG1, + RenderLayerKind::Background2 => BlendFlags::BG2, + RenderLayerKind::Background3 => BlendFlags::BG3, + RenderLayerKind::Objects => BlendFlags::OBJ, + RenderLayerKind::Backdrop => BlendFlags::BACKDROP, + } + } +} + +#[derive(Debug, PartialEq)] +pub struct RenderLayer { + pub kind: RenderLayerKind, + pub priority: u16, + pub pixel: Rgb15, + /// priority used to distinguish between sprites, backgrounds and backdrop + pub priority_by_type: u8, +} + +impl RenderLayer { + pub fn background(bg: usize, pixel: Rgb15, priority: u16) -> RenderLayer { + RenderLayer { + kind: RenderLayerKind::from_usize(1 << bg).unwrap(), + pixel: pixel, + priority: priority, + priority_by_type: 1, + } + } + + pub fn objects(pixel: Rgb15, priority: u16) -> RenderLayer { + RenderLayer { + kind: RenderLayerKind::Objects, + pixel: pixel, + priority: priority, + priority_by_type: 0, + } + } + + pub fn backdrop(pixel: Rgb15) -> RenderLayer { + RenderLayer { + kind: RenderLayerKind::Backdrop, + pixel: pixel, + priority: 4, + priority_by_type: 2, + } + } + + pub(super) fn is_object(&self) -> bool { + self.kind == RenderLayerKind::Objects + } +} + +#[cfg(test)] +mod tests { + use super::*; + use arrayvec::ArrayVec; + + #[test] + fn test_layer_sort_order() { + let mut layers = ArrayVec::<[_; 7]>::new(); + + let backdrop = Rgb15(0xaaaa); + let pixel = Rgb15::WHITE; + layers.push(RenderLayer::background(0, pixel, 3)); + layers.push(RenderLayer::backdrop(backdrop)); + layers.push(RenderLayer::background(1, pixel, 2)); + layers.push(RenderLayer::background(3, pixel, 0)); + layers.push(RenderLayer::background(2, pixel, 2)); + layers.push(RenderLayer::backdrop(backdrop)); + layers.push(RenderLayer::objects(pixel, 1)); + layers.sort_by_key(|k| (k.priority, k.priority_by_type)); + assert_eq!(RenderLayer::background(3, pixel, 0), layers[0]); + } +} diff --git a/src/core/gpu/mod.rs b/src/core/gpu/mod.rs index c4b0c08..2fed04a 100644 --- a/src/core/gpu/mod.rs +++ b/src/core/gpu/mod.rs @@ -16,10 +16,14 @@ mod render; use render::Point; +mod layer; mod mosaic; mod rgb15; mod sfx; +mod window; + pub use rgb15::Rgb15; +pub use window::*; pub mod regs; pub use regs::*; @@ -109,42 +113,6 @@ pub struct Background { mosaic_first_row: Scanline, } -#[derive(Serialize, Deserialize, Clone, Debug, Default)] -pub struct Window { - pub left: u8, - pub right: u8, - pub top: u8, - pub bottom: u8, - pub flags: WindowFlags, -} - -impl Window { - pub fn inside(&self, x: usize, y: usize) -> bool { - let left = self.left as usize; - let mut right = self.right as usize; - let top = self.top as usize; - let mut bottom = self.bottom as usize; - - if right > DISPLAY_WIDTH || right < left { - right = DISPLAY_WIDTH; - } - if bottom > DISPLAY_HEIGHT || bottom < top { - bottom = DISPLAY_HEIGHT; - } - - (x >= left && x < right) && (y >= top && y < bottom) - } -} - -#[derive(Debug)] -pub enum WindowType { - Win0, - Win1, - WinObj, - WinOut, - WinNone, -} - #[derive(Debug, Default, Copy, Clone)] pub struct AffineMatrix { pub pa: i32, @@ -167,15 +135,17 @@ pub struct BgAffine { #[derive(Serialize, Deserialize, Debug, Copy, Clone)] pub struct ObjBufferEntry { + pub(super) window: bool, + pub(super) alpha: bool, pub(super) color: Rgb15, pub(super) priority: u16, - pub(super) window: bool, } impl Default for ObjBufferEntry { fn default() -> ObjBufferEntry { ObjBufferEntry { window: false, + alpha: false, color: Rgb15::TRANSPARENT, priority: 4, } @@ -250,6 +220,7 @@ impl Gpu { oam: BoxedMemory::new(vec![0; OAM_SIZE].into_boxed_slice()), obj_buffer: vec![Default::default(); DISPLAY_WIDTH * DISPLAY_HEIGHT], + frame_buffer: vec![0; DISPLAY_WIDTH * DISPLAY_HEIGHT], } } @@ -289,18 +260,16 @@ impl Gpu { ) } + #[inline] pub(super) fn obj_buffer_get(&self, x: usize, y: usize) -> &ObjBufferEntry { &self.obj_buffer[index2d!(x, y, DISPLAY_WIDTH)] } + #[inline] pub(super) fn obj_buffer_get_mut(&mut self, x: usize, y: usize) -> &mut ObjBufferEntry { &mut self.obj_buffer[index2d!(x, y, DISPLAY_WIDTH)] } - pub(super) fn render_pixel(&mut self, x: i32, y: i32, p: Rgb15) { - self.frame_buffer[index2d!(usize, x, y, DISPLAY_WIDTH)] = p.to_rgb24(); - } - pub fn get_ref_point(&self, bg: usize) -> Point { assert!(bg == 2 || bg == 3); ( @@ -310,46 +279,50 @@ impl Gpu { } pub fn render_scanline(&mut self) { + if self.dispcnt.enable_obj() { + self.render_objs(); + } match self.dispcnt.mode() { 0 => { - for bg in 0..4 { - if self.dispcnt.disp_bg(bg) { + for bg in 0..=3 { + if self.dispcnt.enable_bg(bg) { self.render_reg_bg(bg); } } + self.finalize_scanline(0, 3); } 1 => { - if self.dispcnt.disp_bg(2) { + if self.dispcnt.enable_bg(2) { self.render_aff_bg(2); } - if self.dispcnt.disp_bg(1) { + if self.dispcnt.enable_bg(1) { self.render_reg_bg(1); } - if self.dispcnt.disp_bg(0) { + if self.dispcnt.enable_bg(0) { self.render_reg_bg(0); } + self.finalize_scanline(0, 2); } 2 => { - if self.dispcnt.disp_bg(3) { + if self.dispcnt.enable_bg(3) { self.render_aff_bg(3); } - if self.dispcnt.disp_bg(2) { + if self.dispcnt.enable_bg(2) { self.render_aff_bg(2); } + self.finalize_scanline(2, 3); } 3 => { self.render_mode3(2); + self.finalize_scanline(2, 2); } 4 => { self.render_mode4(2); + self.finalize_scanline(2, 2); } _ => panic!("{:?} not supported", self.dispcnt.mode()), } - if self.dispcnt.disp_obj() { - self.render_objs(); - } - self.mosaic_sfx(); - self.composite_sfx_to_framebuffer(); + // self.mosaic_sfx(); } fn update_vcount(&mut self, value: usize, irqs: &mut IrqBitmask) { @@ -363,8 +336,8 @@ impl Gpu { } } - // Clears the gpu internal buffer - pub fn clear(&mut self) { + /// Clears the gpu obj buffer + pub fn obj_buffer_reset(&mut self) { for x in self.obj_buffer.iter_mut() { *x = Default::default(); } @@ -377,7 +350,7 @@ impl Gpu { irqs: &mut IrqBitmask, video_device: &VideoDeviceRcRefCell, ) { - match self.state { + match completed { HDraw => { // Transition to HBlank self.state = HBlank; @@ -419,6 +392,7 @@ impl Gpu { sb.io.dmac.notify_vblank(); video_device.borrow_mut().render(&self.frame_buffer); + self.obj_buffer_reset(); self.cycles_left_for_current_state = CYCLES_SCANLINE; } } diff --git a/src/core/gpu/mosaic.rs b/src/core/gpu/mosaic.rs index 5ae6310..b2a18a5 100644 --- a/src/core/gpu/mosaic.rs +++ b/src/core/gpu/mosaic.rs @@ -13,7 +13,7 @@ impl Gpu { let vsize = (self.mosaic.bg_vsize() + 1) as usize; for bg in 0..4 { - if self.dispcnt.disp_bg(bg) && self.bg[bg].bgcnt.mosaic() { + if self.dispcnt.enable_bg(bg) && self.bg[bg].bgcnt.mosaic() { let y = self.vcount as usize; if y % vsize == 0 { self.bg[bg].mosaic_first_row = self.bg[bg].line.clone(); diff --git a/src/core/gpu/regs.rs b/src/core/gpu/regs.rs index 08ad0e8..b67f9d9 100644 --- a/src/core/gpu/regs.rs +++ b/src/core/gpu/regs.rs @@ -1,6 +1,8 @@ +use super::layer::{RenderLayer, RenderLayerKind}; use super::sfx::BldMode; use super::*; +use num::ToPrimitive; use serde::{Deserialize, Serialize}; pub const SCREEN_BLOCK_SIZE: u32 = 0x800; @@ -12,11 +14,11 @@ pub enum ObjMapping { } impl DisplayControl { - pub fn disp_bg(&self, bg: usize) -> bool { + pub fn enable_bg(&self, bg: usize) -> bool { self.0.bit(8 + bg) } pub fn is_using_windows(&self) -> bool { - self.disp_window0() || self.disp_window1() || self.disp_obj_window() + self.enable_window0() || self.enable_window1() || self.enable_obj_window() } pub fn obj_mapping(&self) -> ObjMapping { if self.obj_character_vram_mapping() { @@ -71,14 +73,14 @@ bitfield! { pub hblank_interval_free, _: 5; pub obj_character_vram_mapping, _: 6; pub forst_vblank, _: 7; - pub disp_bg0, _ : 8; - pub disp_bg1, _ : 9; - pub disp_bg2, _ : 10; - pub disp_bg3, _ : 11; - pub disp_obj, _ : 12; - pub disp_window0, _ : 13; - pub disp_window1, _ : 14; - pub disp_obj_window, _ : 15; + pub enable_bg0, _ : 8; + pub enable_bg1, _ : 9; + pub enable_bg2, _ : 10; + pub enable_bg3, _ : 11; + pub enable_obj, _ : 12; + pub enable_window0, _ : 13; + pub enable_window1, _ : 14; + pub enable_obj_window, _ : 15; } bitfield! { @@ -149,6 +151,15 @@ impl BlendFlags { pub fn from_bg(bg: usize) -> BlendFlags { Self::BG_LAYER_FLAG[bg] } + + pub fn obj_enabled(&self) -> bool { + self.contains(BlendFlags::OBJ) + } + + pub fn contains_render_layer(&self, layer: &RenderLayer) -> bool { + let layer_flags = BlendFlags::from_bits(layer.kind.to_u32().unwrap()).unwrap(); + self.contains(layer_flags) + } } bitfield! { @@ -194,6 +205,9 @@ impl WindowFlags { pub fn bg_enabled(&self, bg: usize) -> bool { self.contains(BG_WIN_FLAG[bg]) } + pub fn obj_enabled(&self) -> bool { + self.contains(WindowFlags::OBJ) + } } const BG_WIN_FLAG: [WindowFlags; 4] = [ diff --git a/src/core/gpu/render/obj.rs b/src/core/gpu/render/obj.rs index 40b23ff..23114c5 100644 --- a/src/core/gpu/render/obj.rs +++ b/src/core/gpu/render/obj.rs @@ -45,18 +45,12 @@ impl ObjAttrs { (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 is_obj_window(&self) -> bool { + self.0.objmode() == ObjMode::Window } } @@ -89,14 +83,14 @@ impl Gpu { ObjAttrs(attr0, attr1, attr2) } - fn render_affine_obj(&mut self, obj: ObjAttrs, _obj_num: usize) { + fn render_affine_obj(&mut self, attrs: ObjAttrs, _obj_num: usize) { let screen_y = self.vcount as i32; - let (ref_x, ref_y) = obj.coords(); + let (ref_x, ref_y) = attrs.coords(); - let (obj_w, obj_h) = obj.size(); + let (obj_w, obj_h) = attrs.size(); - let (bbox_w, bbox_h) = match obj.0.objtype() { + let (bbox_w, bbox_h) = match attrs.0.objtype() { ObjType::AffineDoubleSize => (2 * obj_w, 2 * obj_h), _ => (obj_w, obj_h), }; @@ -106,18 +100,18 @@ impl Gpu { return; } - let tile_base = self.obj_tile_base() + 0x20 * (obj.2.tile() as u32); + let tile_base = self.obj_tile_base() + 0x20 * (attrs.2.tile() as u32); - let (tile_size, pixel_format) = obj.tile_format(); + let (tile_size, pixel_format) = attrs.tile_format(); let palette_bank = match pixel_format { - PixelFormat::BPP4 => obj.2.palette(), + PixelFormat::BPP4 => attrs.2.palette(), _ => 0u32, }; let tile_array_width = match self.dispcnt.obj_mapping() { ObjMapping::OneDimension => obj_w / 8, ObjMapping::TwoDimension => { - if obj.0.is_8bpp() { + if attrs.0.is_8bpp() { 16 } else { 32 @@ -125,7 +119,7 @@ impl Gpu { } }; - let affine_matrix = self.get_affine_matrix(obj.affine_index()); + let affine_matrix = self.get_affine_matrix(attrs.affine_index()); let half_width = bbox_w / 2; let half_height = bbox_h / 2; @@ -142,7 +136,8 @@ impl Gpu { if self .obj_buffer_get(screen_x as usize, screen_y as usize) .priority - <= obj.2.priority() + <= attrs.2.priority() + && !attrs.is_obj_window() { continue; } @@ -161,34 +156,36 @@ impl Gpu { self.read_pixel_index(tile_addr, tile_x as u32, tile_y as u32, pixel_format); let pixel_color = self.get_palette_color(pixel_index as u32, palette_bank, PALRAM_OFS_FG); - self.write_obj_pixel(screen_x as usize, screen_y as usize, pixel_color, &obj); + if pixel_color != Rgb15::TRANSPARENT { + self.write_obj_pixel(screen_x as usize, screen_y as usize, pixel_color, &attrs); + } } } } - fn render_normal_obj(&mut self, obj: ObjAttrs, _obj_num: usize) { + fn render_normal_obj(&mut self, attrs: ObjAttrs, _obj_num: usize) { let screen_y = self.vcount as i32; - let (ref_x, ref_y) = obj.coords(); - let (obj_w, obj_h) = obj.size(); + let (ref_x, ref_y) = attrs.coords(); + let (obj_w, obj_h) = attrs.size(); // skip this obj if not within its vertical bounds. if !(screen_y >= ref_y && screen_y < ref_y + obj_h) { return; } - let tile_base = self.obj_tile_base() + 0x20 * (obj.2.tile() as u32); + let tile_base = self.obj_tile_base() + 0x20 * (attrs.2.tile() as u32); - let (tile_size, pixel_format) = obj.tile_format(); + let (tile_size, pixel_format) = attrs.tile_format(); let palette_bank = match pixel_format { - PixelFormat::BPP4 => obj.2.palette(), + PixelFormat::BPP4 => attrs.2.palette(), _ => 0u32, }; let tile_array_width = match self.dispcnt.obj_mapping() { ObjMapping::OneDimension => obj_w / 8, ObjMapping::TwoDimension => { - if obj.0.is_8bpp() { + if attrs.0.is_8bpp() { 16 } else { 32 @@ -209,18 +206,19 @@ impl Gpu { if self .obj_buffer_get(screen_x as usize, screen_y as usize) .priority - <= obj.2.priority() + <= attrs.2.priority() + && !attrs.is_obj_window() { continue; } let mut sprite_y = screen_y - ref_y; let mut sprite_x = screen_x - ref_x; - sprite_y = if obj.1.v_flip() { + sprite_y = if attrs.1.v_flip() { obj_h - sprite_y - 1 } else { sprite_y }; - sprite_x = if obj.1.h_flip() { + sprite_x = if attrs.1.h_flip() { obj_w - sprite_x - 1 } else { sprite_x @@ -233,18 +231,20 @@ impl Gpu { self.read_pixel_index(tile_addr, tile_x as u32, tile_y as u32, pixel_format); let pixel_color = self.get_palette_color(pixel_index as u32, palette_bank, PALRAM_OFS_FG); - self.write_obj_pixel(screen_x as usize, screen_y as usize, pixel_color, &obj); + if pixel_color != Rgb15::TRANSPARENT { + self.write_obj_pixel(screen_x as usize, screen_y as usize, pixel_color, &attrs); + } } } fn write_obj_pixel(&mut self, x: usize, y: usize, pixel_color: Rgb15, attrs: &ObjAttrs) { let mut current_obj = self.obj_buffer_get_mut(x, y); - match attrs.0.objmode() { + let obj_mode = attrs.0.objmode(); + match obj_mode { ObjMode::Normal | ObjMode::Sfx => { - if pixel_color != Rgb15::TRANSPARENT { - current_obj.color = pixel_color; - current_obj.priority = attrs.2.priority(); - } + current_obj.color = pixel_color; + current_obj.priority = attrs.2.priority(); + current_obj.alpha = obj_mode == ObjMode::Sfx; } ObjMode::Window => { current_obj.window = true; diff --git a/src/core/gpu/sfx.rs b/src/core/gpu/sfx.rs index ef6540a..91adc41 100644 --- a/src/core/gpu/sfx.rs +++ b/src/core/gpu/sfx.rs @@ -1,9 +1,14 @@ use std::cmp; +use arrayvec::ArrayVec; +use num::FromPrimitive; + use super::regs::*; + +use super::layer::*; use super::*; -#[derive(Debug, Primitive, Clone, Copy)] +#[derive(Debug, Primitive, PartialEq, Clone, Copy)] pub enum BldMode { BldNone = 0b00, BldAlpha = 0b01, @@ -32,159 +37,210 @@ impl From for BlendFlags { } } -#[derive(Debug, Default)] -struct Layer { - color: Rgb15, - blend_flag: BlendFlags, -} - impl Gpu { - fn get_top_layer( + /// returns a none sorted array of background indexes that are enabled + fn active_backgrounds_sorted( &self, - screen_x: usize, - bflags: BlendFlags, - wflags: WindowFlags, - ) -> Option { - // priorities are 0-4 when 0 is the highest - 'outer: for priority in 0..4 { - let obj = self.obj_buffer_get(screen_x, self.vcount); - if bflags.contains(BlendFlags::OBJ) - && wflags.contains(WindowFlags::OBJ) - && !obj.color.is_transparent() - && obj.priority == priority - { - return Some(Layer { - color: obj.color, - blend_flag: BlendFlags::OBJ, - }); - } - for bg in 0..4 { - let c = self.bg[bg].line[screen_x]; - let bflag = BlendFlags::from_bg(bg); - if self.dispcnt.disp_bg(bg) - && !c.is_transparent() - && bflags.contains(bflag) - && wflags.bg_enabled(bg) - && self.bg[bg].bgcnt.priority() == priority - { - return Some(Layer { - color: c, - blend_flag: bflag, - }); + bg_start: usize, + bg_end: usize, + window_flags: WindowFlags, + ) -> ArrayVec<[usize; 4]> { + let mut backgrounds = ArrayVec::<[usize; 4]>::new(); + + for bg in bg_start..=bg_end { + if self.dispcnt.enable_bg(bg) && window_flags.bg_enabled(bg) { + unsafe { + backgrounds.push_unchecked(bg); } } } - let backdrop = self.palette_ram.read_16(0); - if bflags.contains(BlendFlags::BACKDROP) { - return Some(Layer { - color: Rgb15(backdrop), - blend_flag: BlendFlags::BACKDROP, - }); - } - None + backgrounds.sort_by_key(|bg| (self.bg[*bg].bgcnt.priority(), *bg)); + + backgrounds } - fn get_active_window_type(&self, x: usize, y: usize) -> WindowType { - if !self.dispcnt.is_using_windows() { - WindowType::WinNone - } else { - if self.dispcnt.disp_window0() && self.win0.inside(x, y) { - return WindowType::Win0; - } - if self.dispcnt.disp_window1() && self.win1.inside(x, y) { - return WindowType::Win1; - } - // TODO win_obj - return WindowType::WinOut; + #[allow(unused)] + fn layer_to_pixel(&self, x: usize, y: usize, layer: &RenderLayer) -> Rgb15 { + match layer.kind { + RenderLayerKind::Background0 => self.bg[0].line[x], + RenderLayerKind::Background1 => self.bg[1].line[x], + RenderLayerKind::Background2 => self.bg[2].line[x], + RenderLayerKind::Background3 => self.bg[3].line[x], + RenderLayerKind::Objects => self.obj_buffer_get(x, y).color, + RenderLayerKind::Backdrop => Rgb15(self.palette_ram.read_16(0)), } } - fn get_window_flags(&self, wintyp: WindowType) -> WindowFlags { - match wintyp { - WindowType::Win0 => self.win0.flags, - WindowType::Win1 => self.win1.flags, - WindowType::WinObj => self.winobj_flags, - WindowType::WinOut => self.winout_flags, - WindowType::WinNone => WindowFlags::all(), - } - } - - fn sfx_blend_alpha(&self, x: usize, _y: usize, wflags: WindowFlags) -> Option { - let top_layers = self.bldcnt.top(); - let bottom_layers = self.bldcnt.bottom(); - if let Some(top_layer) = self.get_top_layer(x, top_layers, wflags) { - if let Some(bot_layer) = self.get_top_layer(x, bottom_layers, wflags) { - let eva = self.bldalpha.eva(); - let evb = self.bldalpha.evb(); - return Some(top_layer.color.blend_with(bot_layer.color, eva, evb)); - } else { - return Some(top_layer.color); - } - } - None - } - - fn sfx_blend_bw( - &self, - fadeto: Rgb15, - x: usize, - _y: usize, - wflags: WindowFlags, - ) -> Option { - let top_layers = self.bldcnt.top(); - let evy = self.bldy; - - if let Some(layer) = self.get_top_layer(x, top_layers, wflags) { - return Some(layer.color.blend_with(fadeto, 16 - evy, evy)); - } - None - } - - pub fn composite_sfx_to_framebuffer(&mut self) { + /// Composes the render layers into a final scanline while applying needed special effects, and render it to the frame buffer + pub fn finalize_scanline(&mut self, bg_start: usize, bg_end: usize) { let y = self.vcount; - - for x in 0..DISPLAY_WIDTH { - let window = self.get_active_window_type(x, y); - let wflags = self.get_window_flags(window); - let toplayer = self.get_top_layer(x, BlendFlags::all(), wflags).unwrap(); - - let bldmode = if wflags.sfx_enabled() { - self.bldcnt.mode() - } else { - BldMode::BldNone - }; - - let pixel = match bldmode { - BldMode::BldAlpha => { - if self.bldcnt.top().contains(toplayer.blend_flag) - || self.bldcnt.bottom().contains(toplayer.blend_flag) - { - self.sfx_blend_alpha(x, y, wflags).unwrap_or(toplayer.color) - } else { - toplayer.color + let output = unsafe { + let ptr = self.frame_buffer[y * DISPLAY_WIDTH..].as_mut_ptr(); + std::slice::from_raw_parts_mut(ptr, DISPLAY_WIDTH) + }; + if !self.dispcnt.is_using_windows() { + let win = WindowInfo::new(WindowType::WinNone, WindowFlags::all()); + let backgrounds = self.active_backgrounds_sorted(bg_start, bg_end, win.flags); + for x in 0..DISPLAY_WIDTH { + let pixel = self.compose_pixel(x, y, &win, &backgrounds); + output[x] = pixel.to_rgb24(); + } + } else { + let mut occupied = [false; DISPLAY_WIDTH]; + let mut occupied_count = 0; + if self.dispcnt.enable_window0() && self.win0.contains_y(y) { + let win = WindowInfo::new(WindowType::Win0, self.win0.flags); + let backgrounds = self.active_backgrounds_sorted(bg_start, bg_end, win.flags); + for x in self.win0.left()..self.win0.right() { + let pixel = self.compose_pixel(x, y, &win, &backgrounds); + output[x] = pixel.to_rgb24(); + occupied[x] = true; + occupied_count += 1; + } + } + if occupied_count == DISPLAY_WIDTH { + return; + } + if self.dispcnt.enable_window1() && self.win1.contains_y(y) { + let win = WindowInfo::new(WindowType::Win1, self.win1.flags); + let backgrounds = self.active_backgrounds_sorted(bg_start, bg_end, win.flags); + for x in self.win1.left()..self.win1.right() { + if !occupied[x] { + let pixel = self.compose_pixel(x, y, &win, &backgrounds); + output[x] = pixel.to_rgb24(); + occupied[x] = true; + occupied_count += 1; } } - BldMode::BldWhite => { - let result = if self.bldcnt.top().contains(toplayer.blend_flag) { - self.sfx_blend_bw(Rgb15::WHITE, x, y, wflags) - .unwrap_or(toplayer.color) + } + if occupied_count == DISPLAY_WIDTH { + return; + } + let win_out = WindowInfo::new(WindowType::WinOut, self.winout_flags); + let win_out_backgrounds = + self.active_backgrounds_sorted(bg_start, bg_end, win_out.flags); + if self.dispcnt.enable_obj_window() { + let win_obj = WindowInfo::new(WindowType::WinObj, self.winobj_flags); + let win_obj_backgrounds = + self.active_backgrounds_sorted(bg_start, bg_end, win_obj.flags); + for x in 0..DISPLAY_WIDTH { + if occupied[x] { + continue; + } + let obj_entry = self.obj_buffer_get(x, y); + if obj_entry.window { + // WinObj + let pixel = self.compose_pixel(x, y, &win_obj, &win_obj_backgrounds); + output[x] = pixel.to_rgb24(); + occupied[x] = true; + occupied_count += 1; } else { - toplayer.color - }; - result + // WinOut + let pixel = self.compose_pixel(x, y, &win_out, &win_out_backgrounds); + output[x] = pixel.to_rgb24(); + occupied[x] = true; + occupied_count += 1; + } } - BldMode::BldBlack => { - let result = if self.bldcnt.top().contains(toplayer.blend_flag) { - self.sfx_blend_bw(Rgb15::BLACK, x, y, wflags) - .unwrap_or(toplayer.color) - } else { - toplayer.color - }; - result + } else { + for x in 0..DISPLAY_WIDTH { + if occupied[x] { + continue; + } + let pixel = self.compose_pixel(x, y, &win_out, &win_out_backgrounds); + output[x] = pixel.to_rgb24(); + occupied[x] = true; + occupied_count += 1; } - BldMode::BldNone => toplayer.color, - }; - self.render_pixel(x as i32, y as i32, pixel); + } } } + + fn compose_pixel(&self, x: usize, y: usize, win: &WindowInfo, backgrounds: &[usize]) -> Rgb15 { + let backdrop_color = Rgb15(self.palette_ram.read_16(0)); + + let mut layers = ArrayVec::<[_; 7]>::new(); + unsafe { + layers.push_unchecked(RenderLayer::backdrop(backdrop_color)); + } + + for bg in backgrounds.into_iter() { + let bg_pixel = self.bg[*bg].line[x]; + if !bg_pixel.is_transparent() { + unsafe { + layers.push_unchecked(RenderLayer::background( + *bg, + bg_pixel, + self.bg[*bg].bgcnt.priority(), + )); + } + } + } + + let obj_entry = self.obj_buffer_get(x, y); + if self.dispcnt.enable_obj() && win.flags.obj_enabled() && !obj_entry.color.is_transparent() + { + unsafe { + layers.push_unchecked(RenderLayer::objects(obj_entry.color, obj_entry.priority)) + } + } + + // now, sort the layers + layers.sort_by_key(|k| (k.priority, k.priority_by_type)); + + let top_pixel = layers[0].pixel; // self.layer_to_pixel(x, y, &layers[0]); + let mut result = top_pixel; + 'blend: loop { + /* loop hack so we can leave this block early */ + let obj_sfx = obj_entry.alpha && layers[0].is_object(); + if win.flags.sfx_enabled() || obj_sfx { + let top_layer_flags = self.bldcnt.top(); + let bot_layer_flags = self.bldcnt.bottom(); + + if !(top_layer_flags.contains_render_layer(&layers[0]) || obj_sfx) { + break 'blend; + } + if layers.len() > 1 && !(bot_layer_flags.contains_render_layer(&layers[1])) { + break 'blend; + } + + let mut blend_mode = self.bldcnt.mode(); + + // push another backdrop layer in case there is only 1 layer + // unsafe { layers.push_unchecked(RenderLayer::backdrop(backdrop_color)); } + // if this is object alpha blending, ensure that the bottom layer contains a color to blend with + if obj_sfx && layers.len() > 1 && bot_layer_flags.contains_render_layer(&layers[1]) + { + blend_mode = BldMode::BldAlpha; + } + + match blend_mode { + BldMode::BldAlpha => { + let bot_pixel = if layers.len() > 1 { + layers[1].pixel //self.layer_to_pixel(x, y, &layers[1]) + } else { + backdrop_color + }; + + let eva = self.bldalpha.eva(); + let evb = self.bldalpha.evb(); + result = top_pixel.blend_with(bot_pixel, eva, evb); + } + BldMode::BldWhite => { + let evy = self.bldy; + result = top_pixel.blend_with(Rgb15::WHITE, 16 - evy, evy); + } + BldMode::BldBlack => { + let evy = self.bldy; + result = top_pixel.blend_with(Rgb15::BLACK, 16 - evy, evy); + } + BldMode::BldNone => { + result = top_pixel; + } + } + } + break 'blend; + } + result + } } diff --git a/src/core/gpu/window.rs b/src/core/gpu/window.rs new file mode 100644 index 0000000..36388ad --- /dev/null +++ b/src/core/gpu/window.rs @@ -0,0 +1,79 @@ +use serde::{Deserialize, Serialize}; + +use super::consts::*; +use super::WindowFlags; + +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct Window { + pub left: u8, + pub right: u8, + pub top: u8, + pub bottom: u8, + pub flags: WindowFlags, +} + +impl Window { + pub fn inside(&self, x: usize, y: usize) -> bool { + let left = self.left(); + let right = self.right(); + self.contains_y(y) && (x >= left && x < right) + } + + #[inline] + pub fn left(&self) -> usize { + self.left as usize + } + + #[inline] + pub fn right(&self) -> usize { + let left = self.left as usize; + let mut right = self.right as usize; + if right > DISPLAY_WIDTH || right < left { + right = DISPLAY_WIDTH; + } + right + } + + #[inline] + pub fn top(&self) -> usize { + self.top as usize + } + + #[inline] + pub fn bottom(&self) -> usize { + let top = self.top as usize; + let mut bottom = self.bottom as usize; + if bottom > DISPLAY_HEIGHT || bottom < top { + bottom = DISPLAY_HEIGHT; + } + bottom + } + + #[inline] + pub fn contains_y(&self, y: usize) -> bool { + let top = self.top(); + let bottom = self.bottom(); + (y >= top && y < bottom) + } +} + +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum WindowType { + Win0, + Win1, + WinObj, + WinOut, + WinNone, +} + +#[derive(Debug)] +pub struct WindowInfo { + pub typ: WindowType, + pub flags: WindowFlags, +} + +impl WindowInfo { + pub fn new(typ: WindowType, flags: WindowFlags) -> WindowInfo { + WindowInfo { typ, flags } + } +} diff --git a/src/lib.rs b/src/lib.rs index c4275d0..b96d3cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +#![warn(unused_extern_crates)] #![feature(asm)] #![feature(core_intrinsics)] #![feature(exclusive_range_pattern)]