Working Sprite implementation
Some checks failed
Cargo Build & Test / Rust project - latest (stable) (push) Failing after 9s
Some checks failed
Cargo Build & Test / Rust project - latest (stable) (push) Failing after 9s
This commit is contained in:
268
src/ppu.rs
268
src/ppu.rs
@@ -88,11 +88,205 @@ pub enum PPUMMRegisters {
|
||||
Palette,
|
||||
}
|
||||
|
||||
bitfield::bitfield! {
|
||||
#[derive(Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct SpriteAttrs(u8);
|
||||
impl Debug;
|
||||
vflip, set_vflip: 7;
|
||||
hflip, set_hflip: 6;
|
||||
priority, set_priority: 5;
|
||||
palette, set_palette: 1, 0;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct SpriteOutputUnit {
|
||||
high: u8,
|
||||
low: u8,
|
||||
x_pos: u8,
|
||||
y_pos: u8,
|
||||
tile: u8,
|
||||
attrs: SpriteAttrs,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum OamState {
|
||||
ReadY,
|
||||
ReadTile,
|
||||
ReadAttrs,
|
||||
ReadX,
|
||||
OverflowScan,
|
||||
Wait,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OAM {
|
||||
mem: Vec<u8>,
|
||||
secondary: Vec<u8>,
|
||||
addr: u8,
|
||||
count: u8,
|
||||
state: OamState,
|
||||
oam_read_buffer: u8,
|
||||
edit_ver: usize,
|
||||
sprite_output_units: Vec<SpriteOutputUnit>,
|
||||
overflow: bool,
|
||||
sprite_offset_0x1000: bool,
|
||||
}
|
||||
|
||||
impl OAM {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
mem: vec![0u8; 256],
|
||||
addr: 0,
|
||||
count: 0,
|
||||
edit_ver: 0,
|
||||
secondary: vec![0xFF; 32],
|
||||
sprite_output_units: vec![SpriteOutputUnit::default(); 8],
|
||||
state: OamState::ReadY,
|
||||
oam_read_buffer: 0,
|
||||
overflow: false,
|
||||
sprite_offset_0x1000: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn ppu_cycle(&mut self, pixel: usize, line: usize, mem: &mut PpuMem<'_>) {
|
||||
if pixel < 64 {
|
||||
} else if pixel == 64 {
|
||||
self.secondary.fill(0xFF);
|
||||
self.state = OamState::ReadY;
|
||||
self.addr = 0;
|
||||
self.count = 0;
|
||||
} else if pixel <= 256 {
|
||||
// Fetch/evalute sprites for next line
|
||||
if pixel % 2 == 1 && !matches!(self.state, OamState::Wait) {
|
||||
self.oam_read_buffer = self.mem[self.addr as usize];
|
||||
if self.addr == 255 {
|
||||
self.addr = 0;
|
||||
} else {
|
||||
self.addr += 1;
|
||||
}
|
||||
} else {
|
||||
match self.state {
|
||||
OamState::ReadY => {
|
||||
let l = self.oam_read_buffer as usize;
|
||||
if self.count < 8 {
|
||||
if l <= line && line < l + 8 {
|
||||
self.secondary[self.count as usize * 4] = self.oam_read_buffer;
|
||||
self.state = OamState::ReadTile;
|
||||
} else {
|
||||
if self.addr < 0xFD {
|
||||
self.addr += 3;
|
||||
} else {
|
||||
self.state = OamState::Wait; // Should be Overflow scan...
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.state = OamState::Wait; // Should be Overflow scan...
|
||||
}
|
||||
}
|
||||
OamState::ReadTile => {
|
||||
self.secondary[self.count as usize * 4 + 1] = self.oam_read_buffer;
|
||||
self.state = OamState::ReadAttrs;
|
||||
}
|
||||
OamState::ReadAttrs => {
|
||||
self.secondary[self.count as usize * 4 + 2] = self.oam_read_buffer;
|
||||
self.state = OamState::ReadX;
|
||||
}
|
||||
OamState::ReadX => {
|
||||
self.secondary[self.count as usize * 4 + 3] = self.oam_read_buffer;
|
||||
self.state = OamState::ReadY;
|
||||
self.count += 1;
|
||||
if self.count == 8 {
|
||||
self.state = OamState::Wait; // Should be Overflow scan...
|
||||
}
|
||||
}
|
||||
OamState::OverflowScan => todo!(),
|
||||
OamState::Wait => (),
|
||||
}
|
||||
}
|
||||
} else if pixel <= 320 {
|
||||
if pixel == 257 {
|
||||
// Reset output units
|
||||
self.sprite_output_units.fill(SpriteOutputUnit::default());
|
||||
}
|
||||
let run = pixel - 257;
|
||||
if run / 8 < self.count as usize {
|
||||
if run % 8 == 0 {
|
||||
self.sprite_output_units[run / 8].y_pos = self.secondary[(run / 8) * 4];
|
||||
} else if run % 8 == 1 {
|
||||
self.sprite_output_units[run / 8].tile = self.secondary[(run / 8) * 4 + 1];
|
||||
} else if run % 8 == 2 {
|
||||
self.sprite_output_units[run / 8].attrs.0 = self.secondary[(run / 8) * 4 + 2];
|
||||
} else if run % 8 == 3 {
|
||||
self.sprite_output_units[run / 8].x_pos = self.secondary[(run / 8) * 4 + 3];
|
||||
} else if run % 8 == 4 {
|
||||
} else if run % 8 == 5 {
|
||||
let off = line - self.sprite_output_units[run / 8].y_pos as usize;
|
||||
let off = if self.sprite_output_units[run / 8].attrs.vflip() {
|
||||
7 - off
|
||||
} else {
|
||||
off
|
||||
};
|
||||
let addr = if self.sprite_offset_0x1000 {
|
||||
0x1000
|
||||
} else {
|
||||
0x0000
|
||||
} + 16 * self.sprite_output_units[run / 8].tile as u16
|
||||
+ off as u16;
|
||||
self.sprite_output_units[run / 8].low = mem.read(addr).reg_map(|_, _| todo!());
|
||||
} else if run % 8 == 6 {
|
||||
} else if run % 8 == 7 {
|
||||
let off = line - self.sprite_output_units[run / 8].y_pos as usize;
|
||||
let off = if self.sprite_output_units[run / 8].attrs.vflip() {
|
||||
7 - off
|
||||
} else {
|
||||
off
|
||||
};
|
||||
let addr = if self.sprite_offset_0x1000 {
|
||||
0x1000
|
||||
} else {
|
||||
0x0000
|
||||
} + 16 * self.sprite_output_units[run / 8].tile as u16
|
||||
+ off as u16
|
||||
+ 8;
|
||||
self.sprite_output_units[run / 8].high = mem.read(addr).reg_map(|_, _| todo!());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns Some((palette index, above background))
|
||||
fn pixel(&mut self, pixel: usize, _line: usize) -> Option<(u8, bool, bool)> {
|
||||
self.sprite_output_units
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, s)| {
|
||||
if pixel >= s.x_pos as usize && pixel < s.x_pos as usize + 8 && s.x_pos < 0xFF {
|
||||
let bit_pos = pixel - s.x_pos as usize;
|
||||
let bit_pos = if s.attrs.hflip() {
|
||||
bit_pos
|
||||
} else {
|
||||
7 - bit_pos
|
||||
};
|
||||
// println!("Shifting: 0b{:08b} by {}", s.high, 8 - bit_pos);
|
||||
let hi = (s.high >> bit_pos) & 1;
|
||||
// println!("Shifting: 0b{:08b} by {}", s.low, bit_pos);
|
||||
let lo = (s.low >> bit_pos) & 1;
|
||||
let idx = (hi << 1) | lo;
|
||||
Some((
|
||||
if idx != 0 {
|
||||
idx + s.attrs.palette() * 4 + 0x10
|
||||
} else {
|
||||
0
|
||||
},
|
||||
!s.attrs.priority(),
|
||||
i == 0,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.next()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -463,9 +657,9 @@ pub struct Palette {
|
||||
}
|
||||
|
||||
impl Palette {
|
||||
pub fn color(&self, idx: u8, palette: u8) -> Color {
|
||||
pub fn color(&self, idx: u8) -> Color {
|
||||
debug_assert!(idx < 0x20, "Palette index out of range");
|
||||
self.colors[(self.ram[idx as usize + palette as usize * 4] & 0x3F) as usize]
|
||||
self.colors[(self.ram[idx as usize] & 0x3F) as usize]
|
||||
}
|
||||
|
||||
pub fn ram(&self, offset: u8) -> u8 {
|
||||
@@ -487,6 +681,7 @@ pub struct PPU {
|
||||
|
||||
pub mask: Mask,
|
||||
pub vblank: bool,
|
||||
sprite_zero_hit: bool,
|
||||
|
||||
pub palette: Palette,
|
||||
pub background: Background,
|
||||
@@ -542,6 +737,7 @@ impl PPU {
|
||||
],
|
||||
},
|
||||
vblank: false,
|
||||
sprite_zero_hit: false,
|
||||
frame_count: 0,
|
||||
nmi_enabled: false,
|
||||
// nmi_waiting: false,
|
||||
@@ -566,11 +762,7 @@ impl PPU {
|
||||
next_attr: 0,
|
||||
next_attr_2: 0,
|
||||
},
|
||||
oam: OAM {
|
||||
mem: vec![0u8; 256],
|
||||
addr: 0,
|
||||
edit_ver: 0,
|
||||
},
|
||||
oam: OAM::new(),
|
||||
vram_buffer: 0,
|
||||
}
|
||||
}
|
||||
@@ -587,7 +779,9 @@ impl PPU {
|
||||
0 => panic!("ppuctrl is write-only"),
|
||||
1 => panic!("ppumask is write-only"),
|
||||
2 => {
|
||||
let tmp = if self.vblank { 0b1000_0000 } else { 0 };
|
||||
let tmp = if self.vblank { 0b1000_0000 } else { 0 }
|
||||
| if self.sprite_zero_hit { 0b0100_0000 } else { 0 }
|
||||
| if self.oam.overflow { 0b0010_0000 } else { 0 };
|
||||
self.vblank = false;
|
||||
self.background.w = false;
|
||||
tmp
|
||||
@@ -605,10 +799,11 @@ impl PPU {
|
||||
let val = self.vram_buffer;
|
||||
self.vram_buffer = v;
|
||||
val
|
||||
},
|
||||
Value::Register { reg: PPUMMRegisters::Palette, offset } => {
|
||||
self.palette.ram[offset as usize]
|
||||
},
|
||||
}
|
||||
Value::Register {
|
||||
reg: PPUMMRegisters::Palette,
|
||||
offset,
|
||||
} => self.palette.ram[offset as usize],
|
||||
};
|
||||
// .reg_map(|a, off| match a {
|
||||
// PPUMMRegisters::Palette => self.palette.ram[off as usize],
|
||||
@@ -626,9 +821,10 @@ impl PPU {
|
||||
0x00 => {
|
||||
self.nmi_enabled = val & 0b1000_0000 != 0;
|
||||
self.background.t =
|
||||
(self.background.t & 0b0001_1000_0000_0000) | (((val & 0b11) as u16) << 10);
|
||||
(self.background.t & !0b0001_1000_0000_0000) | (((val & 0b11) as u16) << 10);
|
||||
self.background.vram_column = val & 0b0000_0100 != 0;
|
||||
self.background.second_pattern = val & 0b0001_0000 != 0;
|
||||
self.oam.sprite_offset_0x1000 = val & 0b0000_1000 != 0;
|
||||
// TODO: other control fields
|
||||
}
|
||||
0x01 => {
|
||||
@@ -752,31 +948,54 @@ impl PPU {
|
||||
self.background.cur_shift_high <<= 1;
|
||||
self.background.cur_shift_low <<= 1;
|
||||
}
|
||||
if self.scanline != 261 {
|
||||
self.oam.ppu_cycle(self.pixel, self.scanline, mem);
|
||||
}
|
||||
// TODO
|
||||
if self.pixel == 0 {
|
||||
if self.scanline < 9 {
|
||||
// dbg!((self.scanline, &self.oam.sprite_output_units[0]));
|
||||
// dbg!(&self.oam.secondary);
|
||||
}
|
||||
// self.dbg_int = true;
|
||||
// idle cycle
|
||||
} else if self.pixel < 257 || self.pixel > 320 {
|
||||
// self.dbg_int = true;
|
||||
const POS: u32 = 1 << 15;
|
||||
// const POS: u32 = 1 << 15;
|
||||
let bit_pos = 15 - self.background.x;
|
||||
// let pos: u32 = 1 << (15 + self.background.x*0); // TODO: handle this correctly
|
||||
// Determine background color
|
||||
let a = self.background.cur_shift_high & POS;
|
||||
let b = self.background.cur_shift_low & POS;
|
||||
let val = (a >> 14) | (b >> 15);
|
||||
let a = (self.background.cur_shift_high >> bit_pos) & 1;
|
||||
let b = (self.background.cur_shift_low >> bit_pos) & 1;
|
||||
let val = (a << 1) | b;
|
||||
debug_assert!(val < 4);
|
||||
|
||||
let h_off = ((self.pixel - 1) / 16) % 2;
|
||||
let v_off = (self.scanline / 16) % 2;
|
||||
let off = v_off * 4 + h_off * 2;
|
||||
let palette = (self.background.cur_attr >> off) & 0x3;
|
||||
let color = val as u8 + if val != 0 { palette * 4 } else { 0 };
|
||||
|
||||
if self.scanline < 240 && self.pixel < 257 {
|
||||
let color = if let Some((sp_color, above, is_sprite_zero)) =
|
||||
self.oam.pixel(self.pixel, self.scanline)
|
||||
{
|
||||
if is_sprite_zero && sp_color != 0 && color != 0 {
|
||||
self.sprite_zero_hit = true;
|
||||
}
|
||||
if sp_color == 0 || (color != 0 && !above) {
|
||||
color
|
||||
} else {
|
||||
sp_color
|
||||
}
|
||||
} else {
|
||||
color
|
||||
};
|
||||
// Write to screen
|
||||
self.render_buffer.write(
|
||||
self.scanline,
|
||||
self.pixel - 1,
|
||||
self.palette
|
||||
.color(val as u8, if val != 0 { palette } else { 0 }), // self.palette.colors[val as usize],
|
||||
self.palette.color(color), // self.palette.colors[val as usize],
|
||||
); // TODO: this should come from shift registers
|
||||
}
|
||||
if self.pixel < 337 {
|
||||
@@ -868,7 +1087,8 @@ impl PPU {
|
||||
}
|
||||
if self.scanline == 261 && self.pixel == 1 {
|
||||
self.vblank = false;
|
||||
// TODO: clear sprite 0 & sprite overflow
|
||||
self.sprite_zero_hit = false;
|
||||
self.oam.overflow = false;
|
||||
}
|
||||
if self.scanline == 241 && self.pixel == 1 {
|
||||
self.vblank = true;
|
||||
@@ -930,10 +1150,10 @@ impl PPU {
|
||||
),
|
||||
Size::new(1., 1.),
|
||||
match (low & (1 << bit) != 0, high & (1 << bit) != 0) {
|
||||
(false, false) => self.palette.color(0, 0),
|
||||
(true, false) => self.palette.color(1, palette),
|
||||
(false, true) => self.palette.color(2, palette),
|
||||
(true, true) => self.palette.color(3, palette),
|
||||
(false, false) => self.palette.color(0),
|
||||
(true, false) => self.palette.color(1 + 4 * palette),
|
||||
(false, true) => self.palette.color(2 + 4 * palette),
|
||||
(true, true) => self.palette.color(3 + 4 * palette),
|
||||
// (false, false) => Color { r: 0, g: 0, b: 0 },
|
||||
// (true, false) => Color {
|
||||
// r: 64,
|
||||
|
||||
Reference in New Issue
Block a user