use alloc::boxed::Box;
use alloc::sync::Arc;
use alloc::vec;
use core::fmt;
use crate::core::error::Result;
use crate::core::space::{AddressSpace, MemAttrs};
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::core::value::Width;
use super::regs::*;
pub const DOTS_PER_SCANLINE: u16 = 341;
pub const SCANLINES_PER_FRAME: u16 = 262;
pub const DOTS_PER_FRAME: u64 = DOTS_PER_SCANLINE as u64 * SCANLINES_PER_FRAME as u64;
pub const SCREEN_WIDTH: usize = 256;
pub const SCREEN_HEIGHT: usize = 240;
pub const FRAMEBUFFER_LEN: usize = SCREEN_WIDTH * SCREEN_HEIGHT;
pub const VBLANK_SCANLINE: u16 = 241;
pub const PRE_RENDER_SCANLINE: u16 = 261;
pub const WARMUP_DOTS: u64 = 29_658 * 3;
pub const DEFAULT_DECAY_DOTS: u64 = 3_221_591;
pub const NMI_ANNOUNCE_DOTS: u64 = 3;
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Pixel(pub u16);
impl Pixel {
#[inline]
pub const fn new(index: u8, emphasis: u8) -> Pixel {
Pixel(((emphasis as u16 & 0x07) << 6) | (index as u16 & 0x3f))
}
#[inline]
pub const fn index(self) -> u8 {
(self.0 & 0x3f) as u8
}
#[inline]
pub const fn emphasis(self) -> u8 {
((self.0 >> 6) & 0x07) as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EvalPhase {
Copy,
Overflow,
Idle,
}
impl EvalPhase {
const fn to_bits(self) -> u8 {
match self {
EvalPhase::Copy => 0,
EvalPhase::Overflow => 1,
EvalPhase::Idle => 2,
}
}
const fn from_bits(bits: u8) -> EvalPhase {
match bits {
0 => EvalPhase::Copy,
1 => EvalPhase::Overflow,
_ => EvalPhase::Idle,
}
}
}
pub struct Engine {
pub(crate) dots: u64,
pub(crate) frame: u64,
pub(crate) scanline: u16,
pub(crate) dot: u16,
pub(crate) ctrl: u8,
pub(crate) mask: u8,
pub(crate) status: u8,
pub(crate) oam_addr: u8,
pub(crate) v: u16,
pub(crate) t: u16,
pub(crate) x: u8,
pub(crate) w: bool,
pub(crate) read_buffer: u8,
pub(crate) latch: IoLatch,
pub(crate) bus_latch: u8,
pub(crate) oam: [u8; 256],
pub(crate) secondary_oam: [u8; 32],
pub(crate) palette: [u8; 32],
pub(crate) nt_latch: u8,
pub(crate) at_latch: u8,
pub(crate) bg_lo_latch: u8,
pub(crate) bg_hi_latch: u8,
pub(crate) bg_shift_lo: u16,
pub(crate) bg_shift_hi: u16,
pub(crate) at_shift_lo: u16,
pub(crate) at_shift_hi: u16,
pub(crate) eval_phase: EvalPhase,
pub(crate) eval_n: u8,
pub(crate) eval_m: u8,
pub(crate) eval_sec: u8,
pub(crate) eval_found: u8,
pub(crate) eval_base: u8,
pub(crate) eval_latch: u8,
pub(crate) sprite_zero_next: bool,
pub(crate) sprite_pat_lo: [u8; 8],
pub(crate) sprite_pat_hi: [u8; 8],
pub(crate) sprite_attr: [u8; 8],
pub(crate) sprite_x: [u8; 8],
pub(crate) sprite_active: u8,
pub(crate) sprite_zero_active: bool,
pub(crate) sp_y_latch: u8,
pub(crate) sp_tile_latch: u8,
pub(crate) sp_attr_latch: u8,
pub(crate) sprite0_pending: bool,
pub(crate) vblank_set_dot: u64,
pub(crate) suppress_vblank_set: bool,
pub(crate) suppress_nmi: bool,
pub(crate) warmup: bool,
pub(crate) fb: Box<[Pixel]>,
pub(crate) bus: Option<Arc<AddressSpace>>,
}
impl fmt::Debug for Engine {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Engine")
.field("dots", &self.dots)
.field("frame", &self.frame)
.field("scanline", &self.scanline)
.field("dot", &self.dot)
.field("ctrl", &self.ctrl)
.field("mask", &self.mask)
.field("status", &self.status)
.field("v", &self.v)
.field("t", &self.t)
.field("x", &self.x)
.field("w", &self.w)
.finish_non_exhaustive()
}
}
impl Engine {
pub fn new(warmup: bool, decay_dots: u64) -> Engine {
let mut engine = Engine {
dots: 0,
frame: 0,
scanline: 0,
dot: 0,
ctrl: 0,
mask: 0,
status: 0,
oam_addr: 0,
v: 0,
t: 0,
x: 0,
w: false,
read_buffer: 0,
latch: IoLatch::new(decay_dots),
bus_latch: 0,
oam: [0; 256],
secondary_oam: [0xff; 32],
palette: [0; 32],
nt_latch: 0,
at_latch: 0,
bg_lo_latch: 0,
bg_hi_latch: 0,
bg_shift_lo: 0,
bg_shift_hi: 0,
at_shift_lo: 0,
at_shift_hi: 0,
eval_phase: EvalPhase::Copy,
eval_n: 0,
eval_m: 0,
eval_sec: 0,
eval_found: 0,
eval_base: 0,
eval_latch: 0,
sprite_zero_next: false,
sprite_pat_lo: [0; 8],
sprite_pat_hi: [0; 8],
sprite_attr: [0; 8],
sprite_x: [0; 8],
sprite_active: 0,
sprite_zero_active: false,
sp_y_latch: 0,
sp_tile_latch: 0,
sp_attr_latch: 0,
sprite0_pending: false,
vblank_set_dot: 0,
suppress_vblank_set: false,
suppress_nmi: false,
warmup,
fb: vec![Pixel::default(); FRAMEBUFFER_LEN].into_boxed_slice(),
bus: None,
};
engine.reset_cold();
engine
}
pub fn reset_cold(&mut self) {
let warmup = self.warmup;
let ttl = self.latch.ttl();
self.dots = 0;
self.frame = 0;
self.scanline = 0;
self.dot = 0;
self.ctrl = 0;
self.mask = 0;
self.status = 0;
self.oam_addr = 0;
self.v = 0;
self.t = 0;
self.x = 0;
self.w = false;
self.read_buffer = 0;
self.latch = IoLatch::new(ttl);
self.bus_latch = 0;
self.oam = [0; 256];
self.secondary_oam = [0xff; 32];
self.palette = [0; 32];
self.warmup = warmup;
self.reset_pipelines();
self.fb.fill(Pixel::default());
}
pub fn reset_warm(&mut self) {
self.dots = 0;
self.frame = 0;
self.scanline = 0;
self.dot = 0;
self.ctrl = 0;
self.mask = 0;
self.status = 0;
self.v = 0;
self.t = 0;
self.x = 0;
self.w = false;
self.read_buffer = 0;
self.reset_pipelines();
}
fn reset_pipelines(&mut self) {
self.nt_latch = 0;
self.at_latch = 0;
self.bg_lo_latch = 0;
self.bg_hi_latch = 0;
self.bg_shift_lo = 0;
self.bg_shift_hi = 0;
self.at_shift_lo = 0;
self.at_shift_hi = 0;
self.eval_phase = EvalPhase::Copy;
self.eval_n = 0;
self.eval_m = 0;
self.eval_sec = 0;
self.eval_found = 0;
self.eval_base = 0;
self.eval_latch = 0;
self.sprite_zero_next = false;
self.sprite_pat_lo = [0; 8];
self.sprite_pat_hi = [0; 8];
self.sprite_attr = [0; 8];
self.sprite_x = [0; 8];
self.sprite_active = 0;
self.sprite_zero_active = false;
self.sp_y_latch = 0;
self.sp_tile_latch = 0;
self.sp_attr_latch = 0;
self.sprite0_pending = false;
self.vblank_set_dot = 0;
self.suppress_vblank_set = false;
self.suppress_nmi = false;
}
#[inline]
pub fn rendering_enabled(&self) -> bool {
self.mask & MASK_RENDERING != 0
}
#[inline]
fn render_line(&self) -> bool {
self.scanline < SCREEN_HEIGHT as u16 || self.scanline == PRE_RENDER_SCANLINE
}
#[inline]
pub fn nmi_active(&self) -> bool {
self.status & STATUS_VBLANK != 0
&& self.ctrl & CTRL_NMI != 0
&& !self.suppress_nmi
&& self.dots >= self.vblank_set_dot.saturating_add(NMI_ANNOUNCE_DOTS)
}
#[inline]
fn sprite_height(&self) -> u8 {
if self.ctrl & CTRL_SPRITE_16 != 0 {
16
} else {
8
}
}
#[inline]
fn warm(&self) -> bool {
!self.warmup || self.dots >= WARMUP_DOTS
}
fn bus_read(&mut self, addr: u16, attrs: MemAttrs) -> u8 {
let addr = u64::from(addr & 0x3fff);
let fallback = self.bus_latch;
let value = match self.bus.as_ref() {
Some(space) => space
.read(addr, Width::U8, attrs)
.map_or(fallback, |v| v as u8),
None => fallback,
};
if !attrs.debug {
self.bus_latch = value;
}
value
}
fn bus_write(&mut self, addr: u16, value: u8, attrs: MemAttrs) {
let addr = u64::from(addr & 0x3fff);
if let Some(space) = self.bus.as_ref() {
let _ = space.write(addr, Width::U8, u64::from(value), attrs);
}
if !attrs.debug {
self.bus_latch = value;
}
}
#[inline]
pub const fn palette_index(addr: u16) -> usize {
let a = (addr & 0x1f) as u8;
if a & 0x13 == 0x10 {
(a & 0x0f) as usize
} else {
a as usize
}
}
#[inline]
pub fn palette_read(&self, addr: u16) -> u8 {
self.palette[Self::palette_index(addr)] & 0x3f
}
#[inline]
pub fn palette_write(&mut self, addr: u16, value: u8) {
self.palette[Self::palette_index(addr)] = value & 0x3f;
}
fn increment_coarse_x(&mut self) {
if self.v & 0x001f == 31 {
self.v &= !0x001f;
self.v ^= 0x0400;
} else {
self.v += 1;
}
}
fn increment_y(&mut self) {
if self.v & 0x7000 != 0x7000 {
self.v += 0x1000;
} else {
self.v &= !0x7000;
let mut coarse_y = (self.v & 0x03e0) >> 5;
if coarse_y == 29 {
coarse_y = 0;
self.v ^= 0x0800;
} else if coarse_y == 31 {
coarse_y = 0;
} else {
coarse_y += 1;
}
self.v = (self.v & !0x03e0) | (coarse_y << 5);
}
}
fn copy_horizontal(&mut self) {
self.v = (self.v & !0x041f) | (self.t & 0x041f);
}
fn copy_vertical(&mut self) {
self.v = (self.v & !0x7be0) | (self.t & 0x7be0);
}
fn fetch_nametable(&mut self) {
let addr = 0x2000 | (self.v & 0x0fff);
self.nt_latch = self.bus_read(addr, MemAttrs::DEFAULT);
}
fn fetch_attribute(&mut self) {
let addr = 0x23c0 | (self.v & 0x0c00) | ((self.v >> 4) & 0x38) | ((self.v >> 2) & 0x07);
let byte = self.bus_read(addr, MemAttrs::DEFAULT);
let shift = ((self.v >> 4) & 4) | (self.v & 2);
self.at_latch = (byte >> shift) & 3;
}
fn pattern_addr(&self, high: bool) -> u16 {
let base = if self.ctrl & CTRL_BG_TABLE != 0 {
0x1000
} else {
0x0000
};
let fine_y = (self.v >> 12) & 7;
base | (u16::from(self.nt_latch) << 4) | fine_y | if high { 8 } else { 0 }
}
fn fetch_pattern_low(&mut self) {
let addr = self.pattern_addr(false);
self.bg_lo_latch = self.bus_read(addr, MemAttrs::DEFAULT);
}
fn fetch_pattern_high(&mut self) {
let addr = self.pattern_addr(true);
self.bg_hi_latch = self.bus_read(addr, MemAttrs::DEFAULT);
}
fn shift_background(&mut self) {
self.bg_shift_lo <<= 1;
self.bg_shift_hi <<= 1;
self.at_shift_lo <<= 1;
self.at_shift_hi <<= 1;
}
fn reload_shifters(&mut self) {
self.bg_shift_lo = (self.bg_shift_lo & 0xff00) | u16::from(self.bg_lo_latch);
self.bg_shift_hi = (self.bg_shift_hi & 0xff00) | u16::from(self.bg_hi_latch);
self.at_shift_lo =
(self.at_shift_lo & 0xff00) | if self.at_latch & 1 != 0 { 0x00ff } else { 0 };
self.at_shift_hi =
(self.at_shift_hi & 0xff00) | if self.at_latch & 2 != 0 { 0x00ff } else { 0 };
}
#[inline]
fn sprite_in_range(&self, y: u8, scanline: u16) -> bool {
let delta = scanline.wrapping_sub(u16::from(y));
delta < u16::from(self.sprite_height())
}
fn secondary_clear_dot(&mut self, dot: u16) {
if dot.is_multiple_of(2) {
self.secondary_oam[(dot / 2 - 1) as usize] = 0xff;
}
if dot == 64 {
self.eval_phase = EvalPhase::Copy;
self.eval_n = 0;
self.eval_m = 0;
self.eval_sec = 0;
self.eval_found = 0;
self.eval_base = self.oam_addr;
self.sprite_zero_next = false;
}
}
#[inline]
fn eval_oam_index(&self) -> usize {
usize::from(
self.eval_base
.wrapping_add(self.eval_n.wrapping_mul(4))
.wrapping_add(self.eval_m),
)
}
fn sprite_eval_dot(&mut self, dot: u16, scanline: u16) {
if !dot.is_multiple_of(2) {
self.eval_latch = self.oam[self.eval_oam_index()];
return;
}
match self.eval_phase {
EvalPhase::Copy => self.eval_copy(scanline),
EvalPhase::Overflow => self.eval_overflow(scanline),
EvalPhase::Idle => {
self.eval_n = self.eval_n.wrapping_add(1) & 63;
}
}
}
fn eval_copy(&mut self, scanline: u16) {
let latch = self.eval_latch;
if self.eval_found < 8 {
self.secondary_oam[usize::from(self.eval_sec) & 31] = latch;
}
if self.eval_m == 0 {
if self.sprite_in_range(latch, scanline) {
if self.eval_n == 0 {
self.sprite_zero_next = true;
}
self.eval_m = 1;
self.eval_sec = self.eval_sec.wrapping_add(1);
} else {
self.eval_n += 1;
if self.eval_n == 64 {
self.eval_n = 0;
self.eval_phase = EvalPhase::Idle;
}
}
return;
}
self.eval_sec = self.eval_sec.wrapping_add(1);
self.eval_m += 1;
if self.eval_m == 4 {
self.eval_m = 0;
self.eval_found += 1;
self.eval_n += 1;
if self.eval_n == 64 {
self.eval_n = 0;
self.eval_phase = EvalPhase::Idle;
} else if self.eval_found == 8 {
self.eval_phase = EvalPhase::Overflow;
}
}
}
fn eval_overflow(&mut self, scanline: u16) {
if self.sprite_in_range(self.eval_latch, scanline) {
self.status |= STATUS_OVERFLOW;
self.eval_m += 1;
if self.eval_m == 4 {
self.eval_m = 0;
self.eval_n += 1;
if self.eval_n == 64 {
self.eval_n = 0;
self.eval_phase = EvalPhase::Idle;
}
}
return;
}
self.eval_n += 1;
self.eval_m = (self.eval_m + 1) & 3;
if self.eval_n == 64 {
self.eval_n = 0;
self.eval_phase = EvalPhase::Idle;
}
}
fn sprite_pattern_addr(&self, scanline: u16, high: bool) -> u16 {
let height = self.sprite_height();
let mut row = (scanline.wrapping_sub(u16::from(self.sp_y_latch)) as u8) & (height - 1);
if self.sp_attr_latch & SPRITE_FLIP_Y != 0 {
row = (height - 1) - row;
}
let plane = if high { 8 } else { 0 };
if height == 16 {
let bank = u16::from(self.sp_tile_latch & 1) << 12;
let mut index = u16::from(self.sp_tile_latch & 0xfe);
if row >= 8 {
index += 1;
row -= 8;
}
bank | (index << 4) | u16::from(row) | plane
} else {
let bank = if self.ctrl & CTRL_SPRITE_TABLE != 0 {
0x1000
} else {
0x0000
};
bank | (u16::from(self.sp_tile_latch) << 4) | u16::from(row) | plane
}
}
fn sprite_fetch_dot(&mut self, dot: u16, scanline: u16) {
let slot = usize::from((dot - 257) / 8);
let base = slot * 4;
match (dot - 257) % 8 {
0 => self.sp_y_latch = self.secondary_oam[base],
1 => self.sp_tile_latch = self.secondary_oam[base + 1],
2 => {
self.sp_attr_latch = self.secondary_oam[base + 2];
self.sprite_attr[slot] = self.sp_attr_latch;
}
3 => self.sprite_x[slot] = self.secondary_oam[base + 3],
5 => {
let addr = self.sprite_pattern_addr(scanline, false);
let mut byte = self.bus_read(addr, MemAttrs::DEFAULT);
if self.sp_attr_latch & SPRITE_FLIP_X != 0 {
byte = byte.reverse_bits();
}
self.sprite_pat_lo[slot] = byte;
}
7 => {
let addr = self.sprite_pattern_addr(scanline, true);
let mut byte = self.bus_read(addr, MemAttrs::DEFAULT);
if self.sp_attr_latch & SPRITE_FLIP_X != 0 {
byte = byte.reverse_bits();
}
self.sprite_pat_hi[slot] = byte;
}
_ => {}
}
}
fn output_pixel(&mut self, x: u16, scanline: u16) {
let emphasis = (self.mask >> 5) & 7;
let index = if self.rendering_enabled() {
self.render_pixel(x)
} else if self.v & 0x3f00 == 0x3f00 {
self.palette_read(self.v)
} else {
self.palette_read(0)
};
let index = if self.mask & MASK_GREYSCALE != 0 {
index & 0x30
} else {
index
};
let offset = usize::from(scanline) * SCREEN_WIDTH + usize::from(x);
self.fb[offset] = Pixel::new(index, emphasis);
}
fn render_pixel(&mut self, x: u16) -> u8 {
let bg_visible = self.mask & MASK_BG != 0 && (x >= 8 || self.mask & MASK_BG_LEFT != 0);
let (bg_pixel, bg_palette) = if bg_visible {
let bit = 15 - u16::from(self.x);
let lo = (self.bg_shift_lo >> bit) & 1;
let hi = (self.bg_shift_hi >> bit) & 1;
let pa_lo = (self.at_shift_lo >> bit) & 1;
let pa_hi = (self.at_shift_hi >> bit) & 1;
(((hi << 1) | lo) as u8, ((pa_hi << 1) | pa_lo) as u8)
} else {
(0, 0)
};
let sp_visible =
self.mask & MASK_SPRITE != 0 && (x >= 8 || self.mask & MASK_SPRITE_LEFT != 0);
let mut sp_pixel = 0u8;
let mut sp_palette = 0u8;
let mut sp_behind = false;
let mut sp_is_zero = false;
if sp_visible {
for slot in 0..usize::from(self.sprite_active) {
let dx = x.wrapping_sub(u16::from(self.sprite_x[slot]));
if dx >= 8 {
continue;
}
let bit = 7 - dx;
let lo = (self.sprite_pat_lo[slot] >> bit) & 1;
let hi = (self.sprite_pat_hi[slot] >> bit) & 1;
let pixel = (hi << 1) | lo;
if pixel == 0 {
continue;
}
sp_pixel = pixel;
sp_palette = self.sprite_attr[slot] & SPRITE_PALETTE;
sp_behind = self.sprite_attr[slot] & SPRITE_BEHIND != 0;
sp_is_zero = slot == 0 && self.sprite_zero_active;
break;
}
}
if sp_is_zero
&& bg_pixel != 0
&& self.status & STATUS_SPRITE0 == 0
&& x != 255
&& (x >= 8 || (self.mask & MASK_BG_LEFT != 0 && self.mask & MASK_SPRITE_LEFT != 0))
{
self.sprite0_pending = true;
}
match (bg_pixel, sp_pixel) {
(0, 0) => self.palette_read(0),
(0, _) => self.palette_read(0x10 | (u16::from(sp_palette) << 2) | u16::from(sp_pixel)),
(_, 0) => self.palette_read((u16::from(bg_palette) << 2) | u16::from(bg_pixel)),
_ if sp_behind => self.palette_read((u16::from(bg_palette) << 2) | u16::from(bg_pixel)),
_ => self.palette_read(0x10 | (u16::from(sp_palette) << 2) | u16::from(sp_pixel)),
}
}
pub fn tick(&mut self) {
let scanline = self.scanline;
let dot = self.dot;
let rendering = self.rendering_enabled();
let visible = scanline < SCREEN_HEIGHT as u16;
let pre_render = scanline == PRE_RENDER_SCANLINE;
if self.sprite0_pending {
self.status |= STATUS_SPRITE0;
self.sprite0_pending = false;
}
if scanline == VBLANK_SCANLINE && dot == 0 {
self.suppress_nmi = false;
}
if scanline == VBLANK_SCANLINE && dot == 1 {
if self.suppress_vblank_set {
self.suppress_vblank_set = false;
} else {
self.status |= STATUS_VBLANK;
self.vblank_set_dot = self.dots;
}
}
if pre_render && dot == 1 {
self.status &= !(STATUS_VBLANK | STATUS_SPRITE0 | STATUS_OVERFLOW);
self.suppress_nmi = false;
self.suppress_vblank_set = false;
}
if pre_render && dot == 0 && rendering && self.oam_addr >= 8 {
let src = usize::from(self.oam_addr & 0xf8);
for i in 0..8 {
self.oam[i] = self.oam[(src + i) & 0xff];
}
}
if rendering && (visible || pre_render) {
self.render_dot(scanline, dot, visible, pre_render);
}
if visible && (1..=256).contains(&dot) {
self.output_pixel(dot - 1, scanline);
}
self.advance_position(pre_render, rendering);
self.dots += 1;
}
fn render_dot(&mut self, scanline: u16, dot: u16, visible: bool, pre_render: bool) {
if (2..=257).contains(&dot) || (322..=337).contains(&dot) {
self.shift_background();
}
if dot % 8 == 1 && ((9..=257).contains(&dot) || dot == 329 || dot == 337) {
self.reload_shifters();
}
if (1..=256).contains(&dot) || (321..=336).contains(&dot) {
match dot % 8 {
1 => self.fetch_nametable(),
3 => self.fetch_attribute(),
5 => self.fetch_pattern_low(),
7 => self.fetch_pattern_high(),
0 => self.increment_coarse_x(),
_ => {}
}
}
if dot == 256 {
self.increment_y();
}
if dot == 257 {
self.copy_horizontal();
}
if pre_render && (280..=304).contains(&dot) {
self.copy_vertical();
}
if dot == 338 || dot == 340 {
self.fetch_nametable();
}
if (1..=64).contains(&dot) {
self.secondary_clear_dot(dot);
} else if visible && (65..=256).contains(&dot) {
self.sprite_eval_dot(dot, scanline);
}
if dot == 257 {
self.sprite_active = self.eval_found;
self.sprite_zero_active = self.sprite_zero_next;
}
if (257..=320).contains(&dot) {
self.oam_addr = 0;
self.sprite_fetch_dot(dot, scanline);
}
}
fn advance_position(&mut self, pre_render: bool, rendering: bool) {
self.dot += 1;
if pre_render && self.dot == 340 && rendering && !self.frame.is_multiple_of(2) {
self.dot = 0;
self.scanline = 0;
self.frame += 1;
return;
}
if self.dot == DOTS_PER_SCANLINE {
self.dot = 0;
self.scanline += 1;
if self.scanline == SCANLINES_PER_FRAME {
self.scanline = 0;
self.frame += 1;
}
}
}
pub fn run_to(&mut self, target: u64, entry: bool) -> bool {
while self.dots < target {
self.tick();
if self.nmi_active() != entry {
return self.dots >= target;
}
}
true
}
pub fn read_register(&mut self, index: u8, debug: bool) -> u8 {
let now = self.dots;
match index & 7 {
PPUSTATUS => {
let value = (self.status & STATUS_DRIVEN) | (self.open_bus(debug) & !STATUS_DRIVEN);
if !debug {
self.status &= !STATUS_VBLANK;
self.w = false;
self.apply_status_read_race();
self.latch.refresh(now, value, STATUS_DRIVEN);
}
value
}
OAMDATA => {
let value = if self.rendering_enabled()
&& self.scanline < SCREEN_HEIGHT as u16
&& (1..=64).contains(&self.dot)
{
0xff
} else {
self.oam[usize::from(self.oam_addr)]
};
if !debug {
self.latch.refresh(now, value, 0xff);
}
value
}
PPUDATA => self.read_data(debug),
_ => self.open_bus(debug),
}
}
fn open_bus(&mut self, debug: bool) -> u8 {
let now = self.dots;
if debug {
self.latch.peek(now)
} else {
self.latch.read(now)
}
}
fn apply_status_read_race(&mut self) {
if self.scanline != VBLANK_SCANLINE {
return;
}
match self.dot {
1 => {
self.suppress_vblank_set = true;
self.suppress_nmi = true;
}
2 | 3 => self.suppress_nmi = true,
_ => {}
}
}
fn read_data(&mut self, debug: bool) -> u8 {
let now = self.dots;
let addr = self.v & 0x3fff;
let attrs = if debug {
MemAttrs::DEBUG
} else {
MemAttrs::DEFAULT
};
if addr >= 0x3f00 {
let mut entry = self.palette_read(addr);
if self.mask & MASK_GREYSCALE != 0 {
entry &= 0x30;
}
let value = (self.open_bus(debug) & 0xc0) | entry;
if !debug {
self.read_buffer = self.bus_read(addr & 0x2fff, attrs);
self.latch.refresh(now, value, 0x3f);
self.increment_data_address();
}
value
} else {
let value = self.read_buffer;
if !debug {
self.read_buffer = self.bus_read(addr, attrs);
self.latch.refresh(now, value, 0xff);
self.increment_data_address();
}
value
}
}
fn increment_data_address(&mut self) {
if self.rendering_enabled() && self.render_line() {
self.increment_coarse_x();
self.increment_y();
} else {
let step = if self.ctrl & CTRL_INCREMENT != 0 {
32
} else {
1
};
self.v = (self.v + step) & 0x7fff;
}
}
pub fn write_register(&mut self, index: u8, value: u8) {
let now = self.dots;
self.latch.refresh(now, value, 0xff);
match index & 7 {
PPUCTRL => {
if self.warm() {
self.ctrl = value;
self.t = (self.t & 0xf3ff) | ((u16::from(value) & 3) << 10);
}
}
PPUMASK => {
if self.warm() {
self.mask = value;
}
}
PPUSTATUS => {}
OAMADDR => self.oam_addr = value,
OAMDATA => self.write_oam_data(value),
PPUSCROLL => {
if self.warm() {
if self.w {
self.t = (self.t & 0x8fff) | ((u16::from(value) & 0x07) << 12);
self.t = (self.t & 0xfc1f) | ((u16::from(value) & 0xf8) << 2);
} else {
self.t = (self.t & 0xffe0) | (u16::from(value) >> 3);
self.x = value & 7;
}
self.w = !self.w;
}
}
PPUADDR => {
if self.warm() {
if self.w {
self.t = (self.t & 0xff00) | u16::from(value);
self.v = self.t;
} else {
self.t = (self.t & 0x00ff) | ((u16::from(value) & 0x3f) << 8);
}
self.w = !self.w;
}
}
_ => self.write_data(value),
}
}
fn write_oam_data(&mut self, value: u8) {
if self.rendering_enabled() && self.render_line() {
self.oam_addr = self.oam_addr.wrapping_add(4);
return;
}
self.write_oam(self.oam_addr, value);
self.oam_addr = self.oam_addr.wrapping_add(1);
}
pub fn write_oam(&mut self, addr: u8, value: u8) {
let value = if addr & 3 == 2 {
value & SPRITE_ATTR_IMPLEMENTED
} else {
value
};
self.oam[usize::from(addr)] = value;
}
fn write_data(&mut self, value: u8) {
let addr = self.v & 0x3fff;
if addr >= 0x3f00 {
self.palette_write(addr, value);
} else {
self.bus_write(addr, value, MemAttrs::DEFAULT);
}
self.increment_data_address();
}
pub fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
w.write_u64(self.dots)?;
w.write_u64(self.frame)?;
w.write_u16(self.scanline)?;
w.write_u16(self.dot)?;
w.write_u8(self.ctrl)?;
w.write_u8(self.mask)?;
w.write_u8(self.status)?;
w.write_u8(self.oam_addr)?;
w.write_u16(self.v)?;
w.write_u16(self.t)?;
w.write_u8(self.x)?;
w.write_bool(self.w)?;
w.write_u8(self.read_buffer)?;
self.latch.save(w)?;
w.write_u8(self.bus_latch)?;
w.write_all(&self.oam)?;
w.write_all(&self.secondary_oam)?;
w.write_all(&self.palette)?;
w.write_u8(self.nt_latch)?;
w.write_u8(self.at_latch)?;
w.write_u8(self.bg_lo_latch)?;
w.write_u8(self.bg_hi_latch)?;
w.write_u16(self.bg_shift_lo)?;
w.write_u16(self.bg_shift_hi)?;
w.write_u16(self.at_shift_lo)?;
w.write_u16(self.at_shift_hi)?;
w.write_u8(self.eval_phase.to_bits())?;
w.write_u8(self.eval_n)?;
w.write_u8(self.eval_m)?;
w.write_u8(self.eval_sec)?;
w.write_u8(self.eval_found)?;
w.write_u8(self.eval_base)?;
w.write_u8(self.eval_latch)?;
w.write_bool(self.sprite_zero_next)?;
w.write_all(&self.sprite_pat_lo)?;
w.write_all(&self.sprite_pat_hi)?;
w.write_all(&self.sprite_attr)?;
w.write_all(&self.sprite_x)?;
w.write_u8(self.sprite_active)?;
w.write_bool(self.sprite_zero_active)?;
w.write_u8(self.sp_y_latch)?;
w.write_u8(self.sp_tile_latch)?;
w.write_u8(self.sp_attr_latch)?;
w.write_bool(self.sprite0_pending)?;
w.write_u64(self.vblank_set_dot)?;
w.write_bool(self.suppress_vblank_set)?;
w.write_bool(self.suppress_nmi)?;
w.write_bool(self.warmup)?;
w.write_seq_len(self.fb.len() as u64)?;
for pixel in self.fb.iter() {
w.write_u16(pixel.0)?;
}
Ok(())
}
pub fn load(&mut self, r: &mut ChunkReader<'_>) -> Result<()> {
self.dots = r.read_u64()?;
self.frame = r.read_u64()?;
self.scanline = r.read_u16()?;
self.dot = r.read_u16()?;
self.ctrl = r.read_u8()?;
self.mask = r.read_u8()?;
self.status = r.read_u8()?;
self.oam_addr = r.read_u8()?;
self.v = r.read_u16()?;
self.t = r.read_u16()?;
self.x = r.read_u8()?;
self.w = r.read_bool()?;
self.read_buffer = r.read_u8()?;
self.latch.load(r)?;
self.bus_latch = r.read_u8()?;
read_exact(r, &mut self.oam)?;
read_exact(r, &mut self.secondary_oam)?;
read_exact(r, &mut self.palette)?;
self.nt_latch = r.read_u8()?;
self.at_latch = r.read_u8()?;
self.bg_lo_latch = r.read_u8()?;
self.bg_hi_latch = r.read_u8()?;
self.bg_shift_lo = r.read_u16()?;
self.bg_shift_hi = r.read_u16()?;
self.at_shift_lo = r.read_u16()?;
self.at_shift_hi = r.read_u16()?;
self.eval_phase = EvalPhase::from_bits(r.read_u8()?);
self.eval_n = r.read_u8()?;
self.eval_m = r.read_u8()?;
self.eval_sec = r.read_u8()?;
self.eval_found = r.read_u8()?;
self.eval_base = r.read_u8()?;
self.eval_latch = r.read_u8()?;
self.sprite_zero_next = r.read_bool()?;
read_exact(r, &mut self.sprite_pat_lo)?;
read_exact(r, &mut self.sprite_pat_hi)?;
read_exact(r, &mut self.sprite_attr)?;
read_exact(r, &mut self.sprite_x)?;
self.sprite_active = r.read_u8()?;
self.sprite_zero_active = r.read_bool()?;
self.sp_y_latch = r.read_u8()?;
self.sp_tile_latch = r.read_u8()?;
self.sp_attr_latch = r.read_u8()?;
self.sprite0_pending = r.read_bool()?;
self.vblank_set_dot = r.read_u64()?;
self.suppress_vblank_set = r.read_bool()?;
self.suppress_nmi = r.read_bool()?;
self.warmup = r.read_bool()?;
let len = r.read_seq_len(2)? as usize;
if len != FRAMEBUFFER_LEN {
return Err(crate::core::Error::State(alloc::format!(
"framebuffer is {len} pixels, expected {FRAMEBUFFER_LEN}"
)));
}
for pixel in self.fb.iter_mut() {
*pixel = Pixel(r.read_u16()?);
}
Ok(())
}
}
fn read_exact(r: &mut ChunkReader<'_>, dst: &mut [u8]) -> Result<()> {
let bytes = r.take(dst.len())?;
dst.copy_from_slice(bytes);
Ok(())
}