From 9f0ec48f082b54f259d183612c4df564776e473e Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 8 Dec 2023 20:18:24 +0100 Subject: [PATCH] Initial mode 7 support --- Cargo.lock | 68 +++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/snes/ppu/bus.rs | 27 +++++++++++--- src/snes/ppu/mod.rs | 1 + src/snes/ppu/render.rs | 33 +++++++++++------ src/snes/ppu/render_m7.rs | 76 +++++++++++++++++++++++++++++++++++++++ src/snes/ppu/sprites.rs | 2 +- 7 files changed, 193 insertions(+), 15 deletions(-) create mode 100644 src/snes/ppu/render_m7.rs diff --git a/Cargo.lock b/Cargo.lock index 148a42e..498ee39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -332,6 +332,40 @@ dependencies = [ "adler", ] +[[package]] +name = "num" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +dependencies = [ + "num-traits", +] + [[package]] name = "num-derive" version = "0.4.0" @@ -343,6 +377,39 @@ dependencies = [ "syn 2.0.30", ] +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.16" @@ -496,6 +563,7 @@ dependencies = [ "dbg_hex", "hex-literal", "itertools", + "num", "num-derive", "num-traits", "sdl2", diff --git a/Cargo.toml b/Cargo.toml index 998d142..1d51014 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ colored = "2.0.4" dbg_hex = "0.1.1" hex-literal = "0.4.1" itertools = "0.11.0" +num = "0.4.1" num-derive = "0.4.0" num-traits = "0.2.16" sdl2 = { version = "0.35.2", features = ["unsafe_textures"] } diff --git a/src/snes/ppu/bus.rs b/src/snes/ppu/bus.rs index 21f1528..de56b4f 100644 --- a/src/snes/ppu/bus.rs +++ b/src/snes/ppu/bus.rs @@ -3,6 +3,10 @@ use super::*; use crate::frontend::Renderer; use crate::snes::bus::{Address, BusMember}; +use num::traits::{WrappingShl, WrappingShr}; +use num::Integer; +use std::mem::size_of_val; + macro_rules! write_m7x { ($self:ident, $reg:ident, $val:expr) => {{ $self.$reg = (($val as u16) << 8 | $self.m7_old as u16) as i16; @@ -10,6 +14,21 @@ macro_rules! write_m7x { }}; } +macro_rules! write_m7x_13b { + ($self:ident, $reg:ident, $val:expr) => {{ + $self.$reg = sign_extend( + ((($val as u16) & 0x1F) << 8 | $self.m7_old as u16) as i16, + 13, + ); + $self.m7_old = $val + }}; +} + +fn sign_extend(val: T, nbits: u32) -> T { + let notherbits = size_of_val(&val) as u32 * 8 - nbits; + val.wrapping_shl(notherbits).wrapping_shr(notherbits) +} + impl BusMember
for PPU where TRenderer: Renderer, @@ -191,7 +210,7 @@ where if idx == 0 { // M7HOFS - write_m7x!(self, m7hofs, val); + write_m7x_13b!(self, m7hofs, val); } Some(self.bgxhofs[idx] = (new << 8) | (prev & !7) | ((cur >> 8) & 7)) @@ -205,7 +224,7 @@ where if idx == 0 { // M7VOFS - write_m7x!(self, m7vofs, val); + write_m7x_13b!(self, m7vofs, val); } Some(self.bgxvofs[idx] = (new << 8) | prev) @@ -255,9 +274,9 @@ where // M7D - Rotation/Scaling Parameter D (W) 0x211E => Some(write_m7x!(self, m7d, val)), // M7X - Rotation/Scaling Center Coordinate X (W) - 0x211F => Some(write_m7x!(self, m7x, val)), + 0x211F => Some(write_m7x_13b!(self, m7x, val)), // M7Y - Rotation/Scaling Center Coordinate Y (W) - 0x2120 => Some(write_m7x!(self, m7y, val)), + 0x2120 => Some(write_m7x_13b!(self, m7y, val)), // CGADD - Palette CGRAM Address (Color Generator Memory) 0x2121 => { self.cgadd.set(val); diff --git a/src/snes/ppu/mod.rs b/src/snes/ppu/mod.rs index cc27675..f09416b 100644 --- a/src/snes/ppu/mod.rs +++ b/src/snes/ppu/mod.rs @@ -1,6 +1,7 @@ pub mod bus; pub mod color; pub mod render; +pub mod render_m7; pub mod sprites; pub mod tile; diff --git a/src/snes/ppu/render.rs b/src/snes/ppu/render.rs index 4840e23..0a80801 100644 --- a/src/snes/ppu/render.rs +++ b/src/snes/ppu/render.rs @@ -8,27 +8,27 @@ use num_traits::FromPrimitive; const LAYER_BACKDROP: u8 = 255; const LAYER_SPRITES: u8 = 4; -struct RenderState { +pub struct RenderState { /// Color index from tile data - idx: [u8; SCREEN_WIDTH], + pub idx: [u8; SCREEN_WIDTH], /// Palette from OAM (sprites only!) - palette: [u8; SCREEN_WIDTH], + pub palette: [u8; SCREEN_WIDTH], /// Paletted color - paletted: [SnesColor; SCREEN_WIDTH], + pub paletted: [SnesColor; SCREEN_WIDTH], /// Layer that produced the pixel - layer: [u8; SCREEN_WIDTH], + pub layer: [u8; SCREEN_WIDTH], /// Layer mask for the window - windowlayermask: u8, + pub windowlayermask: u8, /// Layer mask - layermask: u8, + pub layermask: u8, /// State/settings of the window - window: WindowState, + pub window: WindowState, } impl RenderState { @@ -53,7 +53,7 @@ impl RenderState { type WindowLine = [bool; SCREEN_WIDTH]; #[derive(Clone)] -struct WindowState { +pub struct WindowState { bg: [WindowLine; 4], math: WindowLine, sprites: WindowLine, @@ -75,7 +75,7 @@ impl PPU where TRenderer: Renderer, { - fn cgram_to_color(&self, addr: u8) -> SnesColor { + pub fn cgram_to_color(&self, addr: u8) -> SnesColor { SnesColor::from(self.cgram[addr as usize]) } @@ -289,6 +289,19 @@ where // BG2 tiles with priority 0 self.render_scanline_bglayer(scanline, 1, &mut state, false); } + 7 => { + // TODO extbg + // Sprites with priority 3 + self.render_scanline_sprites(scanline, &mut state, 3); + // Sprites with priority 2 + self.render_scanline_sprites(scanline, &mut state, 2); + // Sprites with priority 1 + self.render_scanline_sprites(scanline, &mut state, 1); + // BG1 + self.render_scanline_mode7(scanline, &mut state); + // Sprites with priority 0 + self.render_scanline_sprites(scanline, &mut state, 0); + } _ => println!( "TODO unimplemented PPU mode {} at scanline {}", self.get_screen_mode(), diff --git a/src/snes/ppu/render_m7.rs b/src/snes/ppu/render_m7.rs new file mode 100644 index 0000000..a768af6 --- /dev/null +++ b/src/snes/ppu/render_m7.rs @@ -0,0 +1,76 @@ +use super::render::*; +use super::*; +use crate::frontend::Renderer; + +impl PPU +where + TRenderer: Renderer, +{ + fn mode7_vram_to_color(&self, vram_x: i32, vram_y: i32) -> u8 { + let tile_x = ((vram_x as u32 >> 11) & 0x7f) as u16; + let tile_y = ((vram_y as u32 >> 11) & 0x7f) as u16; + let pixel_x = ((vram_x >> 8) & 0x07) as u16; + let pixel_y = ((vram_y >> 8) & 0x07) as u16; + + let tilemap_addr = ((tile_y << 7) + tile_x) as usize; + let tileidx = self.vram[tilemap_addr & VRAM_ADDRMASK] & 0xFF; + let pixel_addr = ((tileidx << 6) + (pixel_y << 3) + pixel_x) as usize; + (self.vram[pixel_addr & VRAM_ADDRMASK] >> 8) as u8 + } + + fn mode7_get_pixel(&self, x: usize, scanline: usize) -> u8 { + // IF xflip THEN SCREEN.X=((0..255) XOR FFh), ELSE SCREEN.X=(0..255) + let screen_x = if self.m7sel & 0x01 != 0 { + // H-flip + (x & 0xFF) ^ 0xFF + } else { + x & 0xFF + } as i16; + // IF yflip THEN SCREEN.Y=((1..224/239) XOR FFh), ELSE SCREEN.Y=(1..224/239) + let screen_y = if self.m7sel & 0x02 != 0 { + // V-flip + (scanline & 0xFF) ^ 0xFF + } else { + scanline + } as i16; + // ORG.X = (M7HOFS-M7X) AND NOT 1C00h, IF ORG.X<0 THEN ORG.X=ORG.X OR 1C00h + let mut org_x = (self.m7hofs - self.m7x) & !0x1C00; + if org_x < 0 { + org_x |= 0x1C00; + } + + // ORG.Y = (M7VOFS-M7Y) AND NOT 1C00h, IF ORG.Y<0 THEN ORG.Y=ORG.Y OR 1C00h + let mut org_y = (self.m7vofs - self.m7y) & !0x1C00; + if org_y < 0 { + org_y |= 0x1C00; + } + // VRAM.X = ((M7A*ORG.X) AND NOT 3Fh) + ((M7B*ORG.Y) AND NOT 3Fh) + M7X*100h + let mut vram_x = ((self.m7a as i32 * org_x as i32) & !0x3F) + + ((self.m7b as i32 * org_y as i32) & !0x3F) + + self.m7x as i32 * 0x100; + // VRAM.Y = ((M7C*ORG.X) AND NOT 3Fh) + ((M7D*ORG.Y) AND NOT 3Fh) + M7Y*100h + let mut vram_y = ((self.m7c as i32 * org_x as i32) & !0x3F) + + ((self.m7d as i32 * org_y as i32) & !0x3F) + + self.m7y as i32 * 0x100; + // VRAM.X = VRAM.X + ((M7B*SCREEN.Y) AND NOT 3Fh) + (M7A*SCREEN.X) + vram_x += + ((self.m7b as i32 * screen_y as i32) & !0x3F) + (self.m7a as i32 * screen_x as i32); + // VRAM.Y = VRAM.Y + ((M7D*SCREEN.Y) AND NOT 3Fh) + (M7C*SCREEN.X) + vram_y += + ((self.m7d as i32 * screen_y as i32) & !0x3F) + (self.m7c as i32 * screen_x as i32); + + self.mode7_vram_to_color(vram_x, vram_y) + } + + pub fn render_scanline_mode7(&mut self, scanline: usize, state: &mut RenderState) { + for x in 0..SCREEN_WIDTH { + let c = self.mode7_get_pixel(x, scanline); + if c == 0 || state.idx[x] != 0 { + continue; + } + state.idx[x] = c; + state.paletted[x] = self.cgram_to_color(c); + state.layer[x] = 1 as u8; + } + } +} diff --git a/src/snes/ppu/sprites.rs b/src/snes/ppu/sprites.rs index 71fc46b..2ecafca 100644 --- a/src/snes/ppu/sprites.rs +++ b/src/snes/ppu/sprites.rs @@ -133,7 +133,7 @@ where idx + ((self.obsel as usize >> 3) & 3) * 4096 } else { idx - }; + } & VRAM_ADDRMASK; SpriteTile { data: &self.vram[idx..(idx + len)],