Skip to content

Commit

Permalink
Implement sprites
Browse files Browse the repository at this point in the history
  • Loading branch information
twvd committed Nov 9, 2023
1 parent 72b560e commit 20b5ea0
Show file tree
Hide file tree
Showing 5 changed files with 244 additions and 58 deletions.
3 changes: 2 additions & 1 deletion src/snes/bus/mainbus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -708,7 +708,8 @@ where
TRenderer: Renderer,
{
fn tick(&mut self, ticks: Ticks) -> Result<()> {
self.ppu.tick(ticks)?;
// This ratio is not based on anything that makes sense yet
self.ppu.tick(ticks * 8)?;

let entered_vblank = self.ppu.get_clr_intreq_vblank();
let entered_hblank = self.ppu.get_clr_intreq_hblank();
Expand Down
2 changes: 1 addition & 1 deletion src/snes/ppu/bus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ where
let val = val & 0x83; // bit 10-14 unused
if val & 0x80 != 0 {
// Obj priority
todo!();
// TODO
}
Some(self.oamadd.set(v | (val as u16) << 8))
}
Expand Down
1 change: 1 addition & 0 deletions src/snes/ppu/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod bus;
pub mod render;
pub mod sprites;

#[cfg(test)]
pub mod tests;
Expand Down
150 changes: 94 additions & 56 deletions src/snes/ppu/render.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use super::sprites::{SpriteTile, OAM_ENTRIES};
use super::*;
use crate::frontend::{Color, Renderer};

Expand All @@ -24,6 +25,11 @@ where
self.cgram_to_color(palette + idx)
}

fn sprite_cindex_to_color(&self, tile: &SpriteTile, idx: u8) -> Color {
let palette = 128 + (tile.oam.palette() * 16);
self.cgram_to_color(palette + idx)
}

pub fn render_scanline_bglayer(
&mut self,
scanline: usize,
Expand Down Expand Up @@ -59,101 +65,133 @@ where
}
}

pub fn render_scanline_sprites(
&mut self,
scanline: usize,
line_idx: &mut [u8],
line_paletted: &mut [Color],
priority: u8,
) {
for idx in 0..OAM_ENTRIES {
let e = self.get_oam_entry(idx);

if e.priority != priority {
continue;
}

if (e.y..(e.y + e.height)).contains(&scanline) {
for x in e.x..(e.x + e.width) {
if x >= line_idx.len() {
break;
}

let t_x = (x - e.x) / 8;
let t_y = (scanline - e.y) / 8;
let sprite = self.get_sprite_tile(&e, t_x, t_y);

let coloridx = sprite.get_coloridx((x - e.x) % 8, (scanline - e.y) % 8);
if coloridx == 0 || line_idx[x] != 0 {
continue;
}
line_idx[x] = coloridx;
line_paletted[x] = self.sprite_cindex_to_color(&sprite, coloridx);
}
}
}
}

pub fn render_scanline(&mut self, scanline: usize) {
let mut line_idx: [u8; SCREEN_WIDTH] = [0; SCREEN_WIDTH];
let mut line_paletted: [Color; SCREEN_WIDTH] = [(0, 0, 0); SCREEN_WIDTH];

match self.get_screen_mode() {
0 => {
let mut rs = |layer, prio| {
self.render_scanline_bglayer(
scanline,
layer,
&mut line_idx,
&mut line_paletted,
prio,
)
};
// 4 layers, 2bpp (4 colors)
// TODO Sprites with priority 3
// Sprites with priority 3
self.render_scanline_sprites(scanline, &mut line_idx, &mut line_paletted, 3);
// BG1 tiles with priority 1
rs(0, true);
self.render_scanline_bglayer(scanline, 0, &mut line_idx, &mut line_paletted, true);
// BG2 tiles with priority 1
rs(1, true);
// TODO Sprites with priority 2
self.render_scanline_bglayer(scanline, 1, &mut line_idx, &mut line_paletted, true);
// Sprites with priority 2
self.render_scanline_sprites(scanline, &mut line_idx, &mut line_paletted, 2);
// BG1 tiles with priority 0
rs(0, false);
self.render_scanline_bglayer(scanline, 0, &mut line_idx, &mut line_paletted, false);
// BG2 tiles with priority 0
rs(1, false);
// TODO Sprites with priority 1
self.render_scanline_bglayer(scanline, 1, &mut line_idx, &mut line_paletted, false);
// Sprites with priority 1
self.render_scanline_sprites(scanline, &mut line_idx, &mut line_paletted, 1);
// BG3 tiles with priority 1
rs(2, true);
self.render_scanline_bglayer(scanline, 2, &mut line_idx, &mut line_paletted, true);
// BG4 tiles with priority 1
rs(3, true);
// TODO Sprites with priority 0
self.render_scanline_bglayer(scanline, 3, &mut line_idx, &mut line_paletted, true);
// Sprites with priority 0
self.render_scanline_sprites(scanline, &mut line_idx, &mut line_paletted, 0);
// BG3 tiles with priority 0
rs(2, false);
self.render_scanline_bglayer(scanline, 2, &mut line_idx, &mut line_paletted, false);
// BG4 tiles with priority 0
rs(3, false);
self.render_scanline_bglayer(scanline, 3, &mut line_idx, &mut line_paletted, false);
}
1 => {
let bg3_prio = self.bgmode & (1 << 3) != 0;
let mut rs = |layer, prio| {
// BG3 tiles with priority 1 if bit 3 of $2105 is set
if bg3_prio {
self.render_scanline_bglayer(
scanline,
layer,
2,
&mut line_idx,
&mut line_paletted,
prio,
)
};
// BG3 tiles with priority 1 if bit 3 of $2105 is set
if bg3_prio {
rs(2, true);
true,
);
}
// TODO Sprites with priority 3
// Sprites with priority 3
self.render_scanline_sprites(scanline, &mut line_idx, &mut line_paletted, 3);
// BG1 tiles with priority 1
rs(0, true);
self.render_scanline_bglayer(scanline, 0, &mut line_idx, &mut line_paletted, true);
// BG2 tiles with priority 1
rs(1, true);
// TODO Sprites with priority 2
self.render_scanline_bglayer(scanline, 1, &mut line_idx, &mut line_paletted, true);
// Sprites with priority 2
self.render_scanline_sprites(scanline, &mut line_idx, &mut line_paletted, 2);
// BG1 tiles with priority 0
rs(0, false);
self.render_scanline_bglayer(scanline, 0, &mut line_idx, &mut line_paletted, false);
// BG2 tiles with priority 0
rs(1, false);
// TODO Sprites with priority 1
self.render_scanline_bglayer(scanline, 1, &mut line_idx, &mut line_paletted, false);
// Sprites with priority 1
self.render_scanline_sprites(scanline, &mut line_idx, &mut line_paletted, 1);
// BG3 tiles with priority 1 if bit 3 of $2105 is clear
if !bg3_prio {
rs(2, true);
}
// TODO Sprites with priority 0
// BG3 tiles with priority 0
rs(2, false);
}
3 => {
let mut rs = |layer, prio| {
self.render_scanline_bglayer(
scanline,
layer,
2,
&mut line_idx,
&mut line_paletted,
prio,
)
};
true,
);
}
// Sprites with priority 0
self.render_scanline_sprites(scanline, &mut line_idx, &mut line_paletted, 0);
// BG3 tiles with priority 0
self.render_scanline_bglayer(scanline, 2, &mut line_idx, &mut line_paletted, false);
}
3 => {
// 2 layers, bg1: 8bpp (256 colors)
// bg2: 4bpp (16 colors)
// TODO Sprites with priority 3
// Sprites with priority 3
self.render_scanline_sprites(scanline, &mut line_idx, &mut line_paletted, 3);
// BG1 tiles with priority 1
rs(0, true);
// TODO Sprites with priority 2
self.render_scanline_bglayer(scanline, 0, &mut line_idx, &mut line_paletted, true);
// Sprites with priority 2
self.render_scanline_sprites(scanline, &mut line_idx, &mut line_paletted, 2);
// BG2 tiles with priority 1
rs(1, true);
// TODO Sprites with priority 1
self.render_scanline_bglayer(scanline, 1, &mut line_idx, &mut line_paletted, true);
// Sprites with priority 1
self.render_scanline_sprites(scanline, &mut line_idx, &mut line_paletted, 1);
// BG1 tiles with priority 0
rs(0, false);
// TODO Sprites with priority 0
self.render_scanline_bglayer(scanline, 0, &mut line_idx, &mut line_paletted, false);
// Sprites with priority 0
self.render_scanline_sprites(scanline, &mut line_idx, &mut line_paletted, 0);
// BG2 tiles with priority 0
rs(1, false);
self.render_scanline_bglayer(scanline, 1, &mut line_idx, &mut line_paletted, false);
}
_ => todo!(),
}
Expand Down
146 changes: 146 additions & 0 deletions src/snes/ppu/sprites.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
use super::*;

pub const OAM_ENTRIES: usize = 128;
pub const TILE_WIDTH: usize = 8;
pub const TILE_HEIGHT: usize = 8;

#[derive(Debug)]
pub struct OAMEntry {
pub x: usize,
pub y: usize,
pub priority: u8,
pub attr: u8,
pub tileidx: u16,
pub width: usize,
pub height: usize,
}
impl OAMEntry {
pub fn flip_x(&self) -> bool {
self.attr & (1 << 6) != 0
}
pub fn flip_y(&self) -> bool {
self.attr & (1 << 7) != 0
}
pub fn palette(&self) -> u8 {
(self.attr >> 1) & 0x07
}
pub fn get_tileidx(&self, tile_x: usize, tile_y: usize) -> usize {
debug_assert!(tile_x < self.width / TILE_WIDTH);
debug_assert!(tile_y < self.height / TILE_HEIGHT);

let t_w = self.width / TILE_WIDTH;
let t_h = self.height / TILE_HEIGHT;

// Flip tiles as well as the pixels in the tiles
let tile_x = if self.flip_x() {
t_w - tile_x - 1
} else {
tile_x
};
let tile_y = if self.flip_y() {
t_h - tile_y - 1
} else {
tile_y
};

(usize::from(self.tileidx) + tile_x + 0x10 * tile_y) & 0x1FF
}
}

#[derive(Debug)]
pub struct SpriteTile<'a> {
pub data: &'a [VramWord],
pub oam: &'a OAMEntry,
}
impl<'a> SpriteTile<'a> {
pub fn get_coloridx(&self, x: usize, y: usize) -> u8 {
let mut result: u8 = 0;
let bitp_w = BPP::Four.num_bitplanes() / VRAM_WORDSIZE;
let y = if self.oam.flip_y() {
TILE_HEIGHT - 1 - y
} else {
y
};

let (x_a, x_b) = if self.oam.flip_x() {
(1 << x, 1 << 8 + x)
} else {
(1 << 7 - x, 1 << 15 - x)
};

for i in 0..bitp_w {
let offset = y + (TILE_WIDTH * i);

if self.data[offset] & x_a != 0 {
result |= 1 << (i * VRAM_WORDSIZE);
}
if self.data[offset] & x_b != 0 {
result |= 1 << ((i * VRAM_WORDSIZE) + 1);
}
}
result
}
}

impl<TRenderer> PPU<TRenderer>
where
TRenderer: Renderer,
{
pub fn get_oam_entry(&self, idx: usize) -> OAMEntry {
let e = &self.oam[(idx * 4)..((idx + 1) * 4)];
let extoffset_byte = 512 + (idx / 4);
let extoffset_sh = (idx % 4) * 2;
let ext = (self.oam[extoffset_byte] >> extoffset_sh) & 0x03;
let large = ext & 0x02 != 0;
let (width, height) = match ((self.obsel >> 5) & 0x07, large) {
// Small sprites
(0..=2, false) => (8, 8),
(3..=4, false) => (16, 16),
(5, false) => (32, 32),
(6..=7, false) => (16, 32),

// Large sprites
(0, true) => (16, 16),
(1, true) => (32, 32),
(2 | 4..=5, true) => (64, 64),
(3 | 7, true) => (32, 32),
(6, true) => (32, 64),

_ => unreachable!(),
};

OAMEntry {
x: usize::from(e[0]) | (usize::from(ext) & 0x01) << 8,
y: e[1].into(),
tileidx: e[2] as u16 | (e[3] as u16 & 0x01) << 8,
attr: e[3],
priority: (e[3] >> 4) & 0x03,
width,
height,
}
}

pub fn get_sprite_tile<'a>(
&'a self,
oam: &'a OAMEntry,
tile_x: usize,
tile_y: usize,
) -> SpriteTile {
let base_addr = (self.obsel as usize & 0x07) * 8192;
let len = 32 * BPP::Four.num_bitplanes() / VRAM_WORDSIZE;

let idx = base_addr + (oam.get_tileidx(tile_x, tile_y) << 4);
let idx = if oam.tileidx >= 0x100 {
idx + ((self.obsel as usize >> 3) & 3) * 4096
} else {
idx
};
let idx = idx & VRAM_ADDRMASK;
let end_idx = (idx + len) & VRAM_ADDRMASK;

SpriteTile {
data: &self.vram[idx..end_idx],
oam,
}
}
}

0 comments on commit 20b5ea0

Please sign in to comment.