From 8abebbe8444e2d1034eb599634adcef6f3753dca Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Sat, 24 Aug 2019 00:36:48 +0300 Subject: [PATCH] Implement Window special effect (win_demo.gba works, excpet the sprite) Former-commit-id: 511d04045bbb678ceec39e34c483f04db154997b --- src/core/gpu/mod.rs | 73 +++++++++++----- src/core/gpu/regs.rs | 67 +++++++++++++-- src/core/gpu/sfx.rs | 193 ++++++++++++++++++++++++++++++------------- src/core/ioregs.rs | 55 +++++++++--- 4 files changed, 291 insertions(+), 97 deletions(-) diff --git a/src/core/gpu/mod.rs b/src/core/gpu/mod.rs index a3f8ef4..ef44dc9 100644 --- a/src/core/gpu/mod.rs +++ b/src/core/gpu/mod.rs @@ -6,10 +6,10 @@ use super::*; use crate::bitfield::Bit; use crate::num::FromPrimitive; -mod blend; mod mosaic; +mod sfx; -mod regs; +pub mod regs; pub use regs::*; pub const VRAM_ADDR: Addr = 0x0600_0000; @@ -164,6 +164,42 @@ pub struct Bg { mosaic_first_row: Scanline, } +#[derive(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 BgAffine { pub pa: i16, // dx @@ -176,29 +212,28 @@ pub struct BgAffine { #[derive(Debug)] pub struct Gpu { + pub state: GpuState, + cycles: usize, + // registers + pub current_scanline: usize, // VCOUNT pub dispcnt: DisplayControl, pub dispstat: DisplayStatus, pub bg: [Bg; 4], pub bg_aff: [BgAffine; 2], - pub win0h: u16, - pub win1h: u16, - pub win0v: u16, - pub win1v: u16, - pub winin: u16, - pub winout: u16, + pub win0: Window, + pub win1: Window, + pub winout_flags: WindowFlags, + pub winobj_flags: WindowFlags, + pub mosaic: RegMosaic, pub bldcnt: BlendControl, pub bldalpha: BlendAlpha, pub bldy: u16, - cycles: usize, - pub frame_buffer: FrameBuffer, - pub state: GpuState, - pub current_scanline: usize, // VCOUNT } impl Gpu { @@ -208,12 +243,10 @@ impl Gpu { dispstat: DisplayStatus(0), bg: [Bg::default(); 4], bg_aff: [BgAffine::default(); 2], - win0h: 0, - win1h: 0, - win0v: 0, - win1v: 0, - winin: 0, - winout: 0, + win0: Window::default(), + win1: Window::default(), + winout_flags: WindowFlags::from(0), + winobj_flags: WindowFlags::from(0), mosaic: RegMosaic(0), bldcnt: BlendControl(0), bldalpha: BlendAlpha(0), @@ -405,10 +438,10 @@ impl Gpu { _ => panic!("{:?} not supported", self.dispcnt.mode()), } self.mosaic_sfx(); - let post_blend_line = self.blend_line(sb); + let post_sfx_line = self.composite_sfx(sb); for x in 0..DISPLAY_WIDTH { self.frame_buffer.0[x + self.current_scanline * DISPLAY_WIDTH] = - post_blend_line[x].to_rgb24(); + post_sfx_line[x].to_rgb24(); } } diff --git a/src/core/gpu/regs.rs b/src/core/gpu/regs.rs index bba1f80..87df149 100644 --- a/src/core/gpu/regs.rs +++ b/src/core/gpu/regs.rs @@ -1,4 +1,4 @@ -use super::blend::BldMode; +use super::sfx::BldMode; use super::*; pub const SCREEN_BLOCK_SIZE: u32 = 0x800; @@ -7,6 +7,9 @@ impl DisplayControl { pub fn disp_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() + } } impl BgControl { @@ -106,6 +109,7 @@ bitfield! { } bitflags! { + #[derive(Default)] pub struct BlendFlags: u32 { const BG0 = 0b00000001; const BG1 = 0b00000010; @@ -122,12 +126,18 @@ impl From for BlendFlags { } } -pub const BG_LAYER_FLAG: [BlendFlags; 4] = [ - BlendFlags::BG0, - BlendFlags::BG1, - BlendFlags::BG2, - BlendFlags::BG3, -]; +impl BlendFlags { + const BG_LAYER_FLAG: [BlendFlags; 4] = [ + BlendFlags::BG0, + BlendFlags::BG1, + BlendFlags::BG2, + BlendFlags::BG3, + ]; + + pub fn from_bg(bg: usize) -> BlendFlags { + Self::BG_LAYER_FLAG[bg] + } +} bitfield! { #[derive(Default, Copy, Clone)] @@ -146,3 +156,46 @@ bitfield! { pub eva, _: 5, 0; pub evb, _: 12, 8; } + +bitflags! { + #[derive(Default)] + pub struct WindowFlags: u32 { + const BG0 = 0b00000001; + const BG1 = 0b00000010; + const BG2 = 0b00000100; + const BG3 = 0b00001000; + const OBJ = 0b00010000; + const SFX = 0b00100000; + } +} + +impl From for WindowFlags { + fn from(v: u16) -> WindowFlags { + WindowFlags::from_bits(v as u32).unwrap() + } +} + +impl WindowFlags { + pub fn sfx_enabled(&self) -> bool { + self.contains(WindowFlags::SFX) + } + pub fn bg_enabled(&self, bg: usize) -> bool { + self.contains(BG_WIN_FLAG[bg]) + } +} + +const BG_WIN_FLAG: [WindowFlags; 4] = [ + WindowFlags::BG0, + WindowFlags::BG1, + WindowFlags::BG2, + WindowFlags::BG3, +]; + +bitfield! { + #[derive(Default, Copy, Clone)] + pub struct WindowReg(u16); + impl Debug; + u16; + pub into WindowFlags, lower, _: 5, 0; + pub into WindowFlags, upper, _: 13, 8; +} diff --git a/src/core/gpu/sfx.rs b/src/core/gpu/sfx.rs index 7668800..9737322 100644 --- a/src/core/gpu/sfx.rs +++ b/src/core/gpu/sfx.rs @@ -27,91 +27,168 @@ impl Rgb15 { } } +impl From for BlendFlags { + fn from(wf: WindowFlags) -> BlendFlags { + BlendFlags::from_bits(wf.bits()).unwrap() + } +} + +#[derive(Debug, Default)] +struct Layer { + color: Rgb15, + blend_flag: BlendFlags, +} + impl Gpu { - fn get_topmost_color( + fn get_top_layer( &self, sb: &SysBus, screen_x: usize, - layers: &BlendFlags, - ) -> Option { + bflags: BlendFlags, + wflags: WindowFlags, + ) -> Option { // TODO - only BGs are supported, don't forget OBJs - - let mut color: Option = None; - // priorities are 0-4 when 0 is the highest 'outer: for priority in 0..4 { 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() - && (layers.is_empty() || layers.contains(BG_LAYER_FLAG[bg])) + && bflags.contains(bflag) + && wflags.bg_enabled(bg) && self.bg[bg].bgcnt.priority() == priority { - color = Some(c); - break 'outer; + return Some(Layer { + color: c, + blend_flag: bflag, + }); } } } - if color.is_none() && layers.contains(BlendFlags::BACKDROP) { - color = Some(Rgb15(sb.palette_ram.read_16(0))) + if bflags.contains(BlendFlags::BACKDROP) { + return Some(Layer { + color: Rgb15(sb.palette_ram.read_16(0)), + blend_flag: BlendFlags::BACKDROP, + }); } - color + None } - pub fn blend_line(&mut self, sb: &mut SysBus) -> Scanline { - let mut bld_line = Scanline::default(); - let bldmode = self.bldcnt.mode(); - match bldmode { - BldMode::BldAlpha => { - let top_layers = self.bldcnt.top(); - let bottom_layers = self.bldcnt.bottom(); + 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; + } + } - for x in 0..DISPLAY_WIDTH { - if let Some(top_color) = self.get_topmost_color(sb, x, &top_layers) { - if let Some(bot_color) = self.get_topmost_color(sb, x, &bottom_layers) { - let eva = self.bldalpha.eva(); - let evb = self.bldalpha.evb(); - bld_line[x] = top_color.blend_with(bot_color, eva, evb); - } else { - bld_line[x] = top_color; - } + 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, + sb: &SysBus, + 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(sb, x, top_layers, wflags) { + if let Some(bot_layer) = self.get_top_layer(sb, 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, + sb: &SysBus, + 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(sb, x, top_layers, wflags) { + return Some(layer.color.blend_with(fadeto, 16 - evy, evy)); + } + None + } + + pub fn composite_sfx(&self, sb: &SysBus) -> Scanline { + let mut line = Scanline::default(); + let y = self.current_scanline; + 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(sb, x, BlendFlags::all(), wflags) + .unwrap(); + + let bldmode = if wflags.sfx_enabled() { + self.bldcnt.mode() + } else { + BldMode::BldNone + }; + + match bldmode { + BldMode::BldAlpha => { + if self.bldcnt.top().contains(toplayer.blend_flag) + || self.bldcnt.bottom().contains(toplayer.blend_flag) + { + line[x] = self + .sfx_blend_alpha(sb, x, y, wflags) + .unwrap_or(toplayer.color); } else { - bld_line[x] = self.get_topmost_color(sb, x, &BlendFlags::all()).unwrap(); + line[x] = toplayer.color; } } - } - BldMode::BldWhite => { - let top_layers = self.bldcnt.top(); - let evy = self.bldy; - - for x in 0..DISPLAY_WIDTH { - bld_line[x] = - if let Some(top_color) = self.get_topmost_color(sb, x, &top_layers) { - top_color.blend_with(Rgb15::WHITE, 16 - evy, evy) - } else { - self.get_topmost_color(sb, x, &BlendFlags::all()).unwrap() - }; + BldMode::BldWhite => { + let result = if self.bldcnt.top().contains(toplayer.blend_flag) { + self.sfx_blend_bw(sb, Rgb15::WHITE, x, y, wflags) + .unwrap_or(toplayer.color) + } else { + toplayer.color + }; + line[x] = result; } - } - BldMode::BldBlack => { - let top_layers = self.bldcnt.top(); - let evy = self.bldy; - - for x in 0..DISPLAY_WIDTH { - bld_line[x] = - if let Some(top_color) = self.get_topmost_color(sb, x, &top_layers) { - top_color.blend_with(Rgb15::BLACK, 16 - evy, evy) - } else { - self.get_topmost_color(sb, x, &BlendFlags::all()).unwrap() - }; + BldMode::BldBlack => { + let result = if self.bldcnt.top().contains(toplayer.blend_flag) { + self.sfx_blend_bw(sb, Rgb15::BLACK, x, y, wflags) + .unwrap_or(toplayer.color) + } else { + toplayer.color + }; + line[x] = result; } - } - BldMode::BldNone => { - for x in 0..DISPLAY_WIDTH { - bld_line[x] = self.get_topmost_color(sb, x, &BlendFlags::all()).unwrap(); + BldMode::BldNone => { + line[x] = toplayer.color; } } } - bld_line + line } } diff --git a/src/core/ioregs.rs b/src/core/ioregs.rs index abf1edc..7fcfb47 100644 --- a/src/core/ioregs.rs +++ b/src/core/ioregs.rs @@ -3,6 +3,7 @@ use std::rc::Rc; use super::arm7tdmi::{Addr, Bus}; use super::gba::IoDevices; +use super::gpu::regs::WindowFlags; use super::keypad; use super::sysbus::BoxedMemory; @@ -163,12 +164,16 @@ impl Bus for IoRegs { REG_BG1CNT => io.gpu.bg[1].bgcnt.0, REG_BG2CNT => io.gpu.bg[2].bgcnt.0, REG_BG3CNT => io.gpu.bg[3].bgcnt.0, - REG_WIN0H => io.gpu.win0h, - REG_WIN1H => io.gpu.win1h, - REG_WIN0V => io.gpu.win0v, - REG_WIN1V => io.gpu.win1v, - REG_WININ => io.gpu.winin, - REG_WINOUT => io.gpu.winout, + REG_WIN0H => ((io.gpu.win0.left as u16) << 8 | (io.gpu.win0.right as u16)), + REG_WIN1H => ((io.gpu.win1.left as u16) << 8 | (io.gpu.win1.right as u16)), + REG_WIN0V => ((io.gpu.win0.top as u16) << 8 | (io.gpu.win0.bottom as u16)), + REG_WIN1V => ((io.gpu.win1.top as u16) << 8 | (io.gpu.win1.bottom as u16)), + REG_WININ => { + ((io.gpu.win1.flags.bits() as u16) << 8) | (io.gpu.win0.flags.bits() as u16) + } + REG_WINOUT => { + ((io.gpu.winobj_flags.bits() as u16) << 8) | (io.gpu.winout_flags.bits() as u16) + } REG_BLDCNT => io.gpu.bldcnt.0, REG_BLDALPHA => io.gpu.bldalpha.0, @@ -236,12 +241,38 @@ impl Bus for IoRegs { REG_BG3PB => io.gpu.bg_aff[1].pb = value as i16, REG_BG3PC => io.gpu.bg_aff[1].pc = value as i16, REG_BG3PD => io.gpu.bg_aff[1].pd = value as i16, - REG_WIN0H => io.gpu.win0h = value, - REG_WIN1H => io.gpu.win1h = value, - REG_WIN0V => io.gpu.win0v = value, - REG_WIN1V => io.gpu.win1v = value, - REG_WININ => io.gpu.winin = value, - REG_WINOUT => io.gpu.winout = value, + REG_WIN0H => { + let right = value & 0xff; + let left = value >> 8; + io.gpu.win0.right = right as u8; + io.gpu.win0.left = left as u8; + } + REG_WIN1H => { + let right = value & 0xff; + let left = value >> 8; + io.gpu.win1.right = right as u8; + io.gpu.win1.left = left as u8; + } + REG_WIN0V => { + let bottom = value & 0xff; + let top = value >> 8; + io.gpu.win0.bottom = bottom as u8; + io.gpu.win0.top = top as u8; + } + REG_WIN1V => { + let bottom = value & 0xff; + let top = value >> 8; + io.gpu.win1.bottom = bottom as u8; + io.gpu.win1.top = top as u8; + } + REG_WININ => { + io.gpu.win0.flags = WindowFlags::from(value & 0xff); + io.gpu.win1.flags = WindowFlags::from(value >> 8); + } + REG_WINOUT => { + io.gpu.winout_flags = WindowFlags::from(value & 0xff); + io.gpu.winobj_flags = WindowFlags::from(value >> 8); + } REG_MOSAIC => io.gpu.mosaic.0 = value, REG_BLDCNT => io.gpu.bldcnt.0 = value, REG_BLDALPHA => io.gpu.bldalpha.0 = value,