chore(ppu): improve accuracy of sprite pixel fifo

This commit is contained in:
Rekai Nyangadzayi Musuka 2021-04-21 00:03:17 -05:00
parent d457761c3b
commit 9bf10f0c7d
1 changed files with 71 additions and 23 deletions

View File

@ -33,6 +33,7 @@ pub struct Ppu {
pub vram: Box<[u8; VRAM_SIZE]>, pub vram: Box<[u8; VRAM_SIZE]>,
pub stat: LCDStatus, pub stat: LCDStatus,
pub oam: ObjectAttributeTable, pub oam: ObjectAttributeTable,
clock: TimingClock,
fetcher: PixelFetcher, fetcher: PixelFetcher,
fifo: FifoRenderer, fifo: FifoRenderer,
obj_buffer: ObjectBuffer, obj_buffer: ObjectBuffer,
@ -82,7 +83,12 @@ impl Ppu {
self.stat.set_mode(Mode::HBlank); self.stat.set_mode(Mode::HBlank);
} else { } else {
if self.control.lcd_enabled() {
// Only Draw when the LCD Is Enabled
self.draw(self.cycles.into()); self.draw(self.cycles.into());
} else {
self.reset();
}
} }
} }
Mode::HBlank => { Mode::HBlank => {
@ -147,17 +153,22 @@ impl Ppu {
} }
} }
} }
// The TimingClock is either Tick or Tock, and it changes
// every other cycle, which means that we can use it to ensure
// that things run every other cycle
self.clock_next();
} }
} }
fn scan_oam(&mut self, cycle: u32) { fn scan_oam(&mut self, cycle: u32) {
if cycle % 2 == 0 { if self.clock == TimingClock::Tock {
// This is run 50% of the time, or 40 times // This is run 50% of the time, or 40 times
// which is the number of sprites in OAM // which is the number of sprites in OAM
let sprite_height = match self.control.obj_size() { let sprite_height = match self.control.obj_size() {
ObjectSize::EightByEight => 8, ObjectSize::Eight => 8,
ObjectSize::EightBySixteen => 16, ObjectSize::Sixteen => 16,
}; };
let attr = self.oam.attribute((cycle / 2) as usize); let attr = self.oam.attribute((cycle / 2) as usize);
@ -201,17 +212,17 @@ impl Ppu {
if let Some(attr) = obj_attr { if let Some(attr) = obj_attr {
match self.fetcher.obj.state { match self.fetcher.obj.state {
TileNumber => { TileNumber => {
if cycle % 2 != 0 { if self.clock == TimingClock::Tick {
self.fetcher.obj.tile.with_id(attr.tile_index); self.fetcher.obj.tile.with_id(attr.tile_index);
self.fetcher.obj.next(TileDataLow); self.fetcher.obj.next(TileDataLow);
} }
} }
TileDataLow => { TileDataLow => {
if cycle % 2 != 0 { if self.clock == TimingClock::Tick {
let obj_size = match self.control.obj_size() { let obj_size = match self.control.obj_size() {
ObjectSize::EightByEight => 8, ObjectSize::Eight => 8,
ObjectSize::EightBySixteen => 16, ObjectSize::Sixteen => 16,
}; };
let addr = PixelFetcher::get_obj_low_addr(&attr, &self.pos, obj_size); let addr = PixelFetcher::get_obj_low_addr(&attr, &self.pos, obj_size);
@ -223,10 +234,10 @@ impl Ppu {
} }
} }
TileDataHigh => { TileDataHigh => {
if cycle % 2 != 0 { if self.clock == TimingClock::Tick {
let obj_size = match self.control.obj_size() { let obj_size = match self.control.obj_size() {
ObjectSize::EightByEight => 8, ObjectSize::Eight => 8,
ObjectSize::EightBySixteen => 16, ObjectSize::Sixteen => 16,
}; };
let addr = PixelFetcher::get_obj_low_addr(&attr, &self.pos, obj_size); let addr = PixelFetcher::get_obj_low_addr(&attr, &self.pos, obj_size);
@ -251,8 +262,8 @@ impl Ppu {
let pixel = TwoBitsPerPixel::from_bytes(high, low); let pixel = TwoBitsPerPixel::from_bytes(high, low);
let palette = match attr.flags.palette() { let palette = match attr.flags.palette() {
ObjectPaletteId::Palette0 => self.monochrome.obj_palette_0, ObjectPaletteId::Zero => self.monochrome.obj_palette_0,
ObjectPaletteId::Palette1 => self.monochrome.obj_palette_1, ObjectPaletteId::One => self.monochrome.obj_palette_1,
}; };
let num_to_add = 8 - self.fifo.object.len(); let num_to_add = 8 - self.fifo.object.len();
@ -288,7 +299,7 @@ impl Ppu {
} }
// By only running on odd cycles, we can ensure that we draw every two T cycles // By only running on odd cycles, we can ensure that we draw every two T cycles
if cycle % 2 != 0 && self.fetcher.bg.is_enabled() { if self.clock == TimingClock::Tick && self.fetcher.bg.is_enabled() {
match self.fetcher.bg.state { match self.fetcher.bg.state {
TileNumber => { TileNumber => {
// Increment Window line counter if scanline had any window pixels on it // Increment Window line counter if scanline had any window pixels on it
@ -369,6 +380,30 @@ impl Ppu {
} }
} }
fn clock_next(&mut self) {
use TimingClock::*;
self.clock = match self.clock {
Tick => Tock,
Tock => Tick,
}
}
fn reset(&mut self) {
// FIXME: Discover what actually is supposed to be reset here
self.clock = Default::default();
self.cycles = Cycle::new(0);
self.x_pos = 0;
self.stat.set_mode(Mode::OamScan);
self.pos.line_y = 0;
self.fetcher.bg.reset();
self.fetcher.obj.reset();
self.obj_buffer.clear();
}
pub fn copy_to_gui(&self, frame: &mut [u8]) { pub fn copy_to_gui(&self, frame: &mut [u8]) {
frame.copy_from_slice(self.frame_buf.as_ref()); frame.copy_from_slice(self.frame_buf.as_ref());
} }
@ -386,6 +421,7 @@ impl Default for Ppu {
pos: Default::default(), pos: Default::default(),
stat: Default::default(), stat: Default::default(),
oam: Default::default(), oam: Default::default(),
clock: Default::default(),
fetcher: Default::default(), fetcher: Default::default(),
fifo: Default::default(), fifo: Default::default(),
obj_buffer: Default::default(), obj_buffer: Default::default(),
@ -394,6 +430,18 @@ impl Default for Ppu {
} }
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum TimingClock {
Tick = 0,
Tock = 1,
}
impl Default for TimingClock {
fn default() -> Self {
Self::Tick
}
}
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]
pub struct Interrupt { pub struct Interrupt {
_vblank: bool, _vblank: bool,
@ -607,15 +655,15 @@ impl Default for TileDataAddress {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
enum ObjectSize { enum ObjectSize {
EightByEight = 0, Eight = 0,
EightBySixteen = 1, Sixteen = 1,
} }
impl From<u8> for ObjectSize { impl From<u8> for ObjectSize {
fn from(byte: u8) -> Self { fn from(byte: u8) -> Self {
match byte { match byte {
0b00 => Self::EightByEight, 0b00 => Self::Eight,
0b01 => Self::EightBySixteen, 0b01 => Self::Sixteen,
_ => unreachable!("{:#04X} is not a valid value for ObjSize", byte), _ => unreachable!("{:#04X} is not a valid value for ObjSize", byte),
} }
} }
@ -629,7 +677,7 @@ impl From<ObjectSize> for u8 {
impl Default for ObjectSize { impl Default for ObjectSize {
fn default() -> Self { fn default() -> Self {
Self::EightByEight Self::Eight
} }
} }
@ -938,15 +986,15 @@ impl Default for RenderPriority {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum ObjectPaletteId { pub enum ObjectPaletteId {
Palette0 = 0, Zero = 0,
Palette1 = 1, One = 1,
} }
impl From<u8> for ObjectPaletteId { impl From<u8> for ObjectPaletteId {
fn from(byte: u8) -> Self { fn from(byte: u8) -> Self {
match byte { match byte {
0b00 => ObjectPaletteId::Palette0, 0b00 => ObjectPaletteId::Zero,
0b01 => ObjectPaletteId::Palette1, 0b01 => ObjectPaletteId::One,
_ => unreachable!("{:#04X} is not a valid value for BgPaletteNumber", byte), _ => unreachable!("{:#04X} is not a valid value for BgPaletteNumber", byte),
} }
} }