#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[repr(u8)]
pub enum FastMode {
OamRead = 2,
VramRead = 3,
HBlank = 0,
VBlank = 1,
}
pub struct FastPpu {
pub clock: u32,
pub clock_target: u32,
pub ly: u8,
pub mode: FastMode,
pub frame_index: u32,
pub render_pending: bool,
pub int_vblank: bool,
}
impl Default for FastPpu {
fn default() -> Self {
Self::new()
}
}
impl FastPpu {
pub fn new() -> Self {
Self {
clock: 0,
clock_target: 80,
ly: 0,
mode: FastMode::OamRead,
frame_index: 0,
render_pending: false,
int_vblank: false,
}
}
#[inline(always)]
pub fn clock(&mut self, cycles: u16) -> bool {
self.clock += cycles as u32;
let mut rendered = false;
while self.clock >= self.clock_target {
self.advance_mode(&mut rendered);
}
rendered
}
#[inline(always)]
fn advance_mode(&mut self, rendered: &mut bool) {
match self.mode {
FastMode::OamRead => {
self.mode = FastMode::VramRead;
self.clock = self.clock.saturating_sub(80);
self.clock_target = 172;
}
FastMode::VramRead => {
*rendered = true;
self.render_pending = true;
self.mode = FastMode::HBlank;
self.clock = self.clock.saturating_sub(172);
self.clock_target = 204;
}
FastMode::HBlank => {
self.ly += 1;
self.clock = self.clock.saturating_sub(204);
if self.ly == 144 {
self.int_vblank = true;
self.mode = FastMode::VBlank;
self.clock_target = 456;
} else {
self.mode = FastMode::OamRead;
self.clock_target = 80;
}
}
FastMode::VBlank => {
self.ly += 1;
self.clock = self.clock.saturating_sub(456);
if self.ly == 154 {
self.ly = 0;
self.mode = FastMode::OamRead;
self.frame_index = self.frame_index.wrapping_add(1);
self.clock_target = 80;
} else {
self.clock_target = 456;
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn one_frame_is_70224_cycles() {
let mut d = FastPpu::new();
let mut cycles = 0u32;
let start_frame = d.frame_index;
while d.frame_index == start_frame {
d.clock(4);
cycles += 4;
assert!(cycles < 100_000, "frame did not complete");
}
assert!(
(70_220..=70_230).contains(&cycles),
"frame took {} cycles",
cycles
);
}
#[test]
fn renders_once_per_visible_line() {
let mut fast_ppu = FastPpu::new();
let mut renders = 0;
let start_frame = fast_ppu.frame_index;
while fast_ppu.frame_index == start_frame {
if fast_ppu.clock(1) {
renders += 1;
}
}
assert_eq!(renders, 144, "expected 144 renders, got {}", renders);
}
}