Skip to content

Commit

Permalink
Initial mode 7 support
Browse files Browse the repository at this point in the history
  • Loading branch information
twvd committed Dec 8, 2023
1 parent eb2e4a8 commit 9f0ec48
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 15 deletions.
68 changes: 68 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
27 changes: 23 additions & 4 deletions src/snes/ppu/bus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,32 @@ 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;
$self.m7_old = $val
}};
}

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<T: Integer + WrappingShl + WrappingShr>(val: T, nbits: u32) -> T {
let notherbits = size_of_val(&val) as u32 * 8 - nbits;
val.wrapping_shl(notherbits).wrapping_shr(notherbits)
}

impl<TRenderer> BusMember<Address> for PPU<TRenderer>
where
TRenderer: Renderer,
Expand Down Expand Up @@ -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))
Expand All @@ -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)
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/snes/ppu/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod bus;
pub mod color;
pub mod render;
pub mod render_m7;
pub mod sprites;
pub mod tile;

Expand Down
33 changes: 23 additions & 10 deletions src/snes/ppu/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
Expand All @@ -75,7 +75,7 @@ impl<TRenderer> PPU<TRenderer>
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])
}

Expand Down Expand Up @@ -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(),
Expand Down
76 changes: 76 additions & 0 deletions src/snes/ppu/render_m7.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use super::render::*;
use super::*;
use crate::frontend::Renderer;

impl<TRenderer> PPU<TRenderer>
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;
}
}
}
2 changes: 1 addition & 1 deletion src/snes/ppu/sprites.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ where
idx + ((self.obsel as usize >> 3) & 3) * 4096
} else {
idx
};
} & VRAM_ADDRMASK;

SpriteTile {
data: &self.vram[idx..(idx + len)],
Expand Down

0 comments on commit 9f0ec48

Please sign in to comment.