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::region::{BORDER_BLACK, Geometry, Region};
use super::regs::*;
pub const DOTS_PER_SCANLINE: u16 = 341;
pub const SCREEN_WIDTH: usize = 256;
pub const SCREEN_HEIGHT: usize = 240;
pub const FRAMEBUFFER_LEN: usize = SCREEN_WIDTH * SCREEN_HEIGHT;
pub const SCANLINES_PER_FRAME: u16 = Region::Ntsc.geometry().scanlines_per_frame;
pub const DOTS_PER_FRAME: u64 = Region::Ntsc.geometry().dots_per_frame;
pub const VBLANK_SCANLINE: u16 = Region::Ntsc.geometry().vblank_scanline;
pub const PRE_RENDER_SCANLINE: u16 = Region::Ntsc.geometry().pre_render_scanline;
pub const WARMUP_DOTS: u64 = Region::Ntsc.geometry().warmup_dots;
pub const DEFAULT_DECAY_DOTS: u64 = 3_221_591;
const MASK_WRITE_DELAY_DOTS: u8 = 3;
const DATA_SM_START: u8 = 5;
const DATA_SM_ALE: u8 = 2;
const ADDR_WRITE_DELAY_DOTS: u8 = 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,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Phase {
Ale,
Read,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum FetchOp {
Nt,
At,
BgLo,
BgHi,
SpNt,
SpLo(u8),
SpHi(u8),
}
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) ale_latch: u8,
pub(crate) data_sm: u8,
pub(crate) data_sm_addr: u16,
pub(crate) v_pending: u16,
pub(crate) v_delay: 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) eval_copy_left: u8,
pub(crate) eval_wrote: 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) sec_addr: u8,
pub(crate) corrupt_row: Option<u8>,
pub(crate) sprite_halted: u8,
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) mask_pending: u8,
pub(crate) mask_delay: u8,
pub(crate) vblank_set_dot: u64,
pub(crate) suppress_vblank_set: bool,
pub(crate) suppress_nmi: bool,
pub(crate) nmi_out: bool,
pub(crate) nmi_delay: bool,
pub(crate) rendered_last: bool,
pub(crate) region: Region,
pub(crate) geom: Geometry,
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("region", &self.region)
.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(region: Region, 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,
ale_latch: 0,
data_sm: 0,
data_sm_addr: 0,
v_pending: 0,
v_delay: 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,
eval_copy_left: 0,
eval_wrote: false,
sprite_pat_lo: [0; 8],
sprite_pat_hi: [0; 8],
sprite_attr: [0; 8],
sprite_x: [0; 8],
sec_addr: 0,
corrupt_row: None,
sprite_halted: 0,
sprite_active: 0,
sprite_zero_active: false,
sp_y_latch: 0,
sp_tile_latch: 0,
sp_attr_latch: 0,
mask_pending: 0,
mask_delay: 0,
sprite0_pending: false,
vblank_set_dot: 0,
suppress_vblank_set: false,
suppress_nmi: false,
nmi_out: false,
nmi_delay: false,
rendered_last: false,
region,
geom: region.geometry(),
warmup,
fb: vec![Pixel::default(); FRAMEBUFFER_LEN].into_boxed_slice(),
bus: None,
};
engine.reset_cold();
engine
}
#[inline]
pub const fn tv_region(&self) -> Region {
self.region
}
#[inline]
pub const fn geometry(&self) -> Geometry {
self.geom
}
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.ale_latch = 0;
self.data_sm = 0;
self.data_sm_addr = 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.data_sm = 0;
self.data_sm_addr = 0;
self.v_pending = 0;
self.v_delay = 0;
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.eval_copy_left = 0;
self.eval_wrote = 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;
self.nmi_delay = false;
self.rendered_last = false;
}
#[inline]
pub fn rendering_enabled(&self) -> bool {
self.mask & MASK_RENDERING != 0
}
#[inline]
fn render_line(&self) -> bool {
self.scanline < self.geom.visible_scanlines
|| self.scanline == self.geom.pre_render_scanline
}
#[inline]
fn nmi_raw(&self) -> bool {
self.status & STATUS_VBLANK != 0 && self.ctrl & CTRL_NMI != 0 && !self.suppress_nmi
}
#[inline]
pub fn nmi_active(&self) -> bool {
self.nmi_out
}
#[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 >= self.geom.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 ale(&mut self, addr: u16) {
self.ale_latch = addr as u8;
}
fn read_dot(&mut self, addr: u16) -> u8 {
let full = (addr & 0x3f00) | u16::from(self.ale_latch);
self.bus_read(full, MemAttrs::DEFAULT)
}
fn cadence_op(&self, dot: u16) -> Option<(Phase, FetchOp)> {
match dot {
0 => Some((Phase::Ale, FetchOp::BgHi)),
1..=256 | 321..=336 => Some(match dot % 8 {
1 => (Phase::Ale, FetchOp::Nt),
2 => (Phase::Read, FetchOp::Nt),
3 => (Phase::Ale, FetchOp::At),
4 => (Phase::Read, FetchOp::At),
5 => (Phase::Ale, FetchOp::BgLo),
6 => (Phase::Read, FetchOp::BgLo),
7 => (Phase::Ale, FetchOp::BgHi),
_ => (Phase::Read, FetchOp::BgHi),
}),
257..=320 => {
let slot = ((dot - 257) / 8) as u8;
Some(match (dot - 257) % 8 {
0 => (Phase::Ale, FetchOp::SpNt),
1 => (Phase::Read, FetchOp::SpNt),
2 => (Phase::Ale, FetchOp::SpNt),
3 => (Phase::Read, FetchOp::SpNt),
4 => (Phase::Ale, FetchOp::SpLo(slot)),
5 => (Phase::Read, FetchOp::SpLo(slot)),
6 => (Phase::Ale, FetchOp::SpHi(slot)),
_ => (Phase::Read, FetchOp::SpHi(slot)),
})
}
337 | 339 => Some((Phase::Ale, FetchOp::Nt)),
338 | 340 => Some((Phase::Read, FetchOp::Nt)),
_ => None,
}
}
fn cadence_addr(&self, op: FetchOp, scanline: u16) -> u16 {
match op {
FetchOp::Nt | FetchOp::SpNt => 0x2000 | (self.v & 0x0fff),
FetchOp::At => {
0x23c0 | (self.v & 0x0c00) | ((self.v >> 4) & 0x38) | ((self.v >> 2) & 0x07)
}
FetchOp::BgLo => self.pattern_addr(false),
FetchOp::BgHi => self.pattern_addr(true),
FetchOp::SpLo(_) => self.sprite_pattern_addr(scanline, false),
FetchOp::SpHi(_) => self.sprite_pattern_addr(scanline, true),
}
}
fn cadence_store(&mut self, op: FetchOp, byte: u8) {
match op {
FetchOp::Nt | FetchOp::SpNt => self.nt_latch = byte,
FetchOp::At => {
let shift = ((self.v >> 4) & 4) | (self.v & 2);
self.at_latch = (byte >> shift) & 3;
}
FetchOp::BgLo => self.bg_lo_latch = byte,
FetchOp::BgHi => self.bg_hi_latch = byte,
FetchOp::SpLo(slot) => {
let byte = if self.sp_attr_latch & SPRITE_FLIP_X != 0 {
byte.reverse_bits()
} else {
byte
};
self.sprite_pat_lo[usize::from(slot)] = byte;
}
FetchOp::SpHi(slot) => {
let byte = if self.sp_attr_latch & SPRITE_FLIP_X != 0 {
byte.reverse_bits()
} else {
byte
};
self.sprite_pat_hi[usize::from(slot)] = byte;
}
}
}
fn data_sm_dot(&mut self) -> Option<Phase> {
if self.data_sm == 0 {
return None;
}
self.data_sm -= 1;
match self.data_sm {
DATA_SM_ALE => Some(Phase::Ale),
0 => Some(Phase::Read),
_ => None,
}
}
fn memory_dot(&mut self, cadence: Option<(Phase, FetchOp)>, scanline: u16) {
let sm = self.data_sm_dot();
match (cadence, sm) {
(None, None) => {}
(Some((Phase::Ale, op)), None) => {
let addr = self.cadence_addr(op, scanline);
self.ale(addr);
}
(Some((Phase::Read, op)), None) => {
let addr = self.cadence_addr(op, scanline);
let byte = self.read_dot(addr);
self.cadence_store(op, byte);
}
(None, Some(Phase::Ale)) => {
let addr = self.data_sm_addr;
self.ale(addr);
}
(None, Some(Phase::Read)) => {
let addr = self.data_sm_addr;
self.read_buffer = self.read_dot(addr);
}
(Some((Phase::Ale, op)), Some(Phase::Ale)) => {
let addr = self.cadence_addr(op, scanline);
self.ale(addr);
}
(Some((Phase::Ale, op)), Some(Phase::Read)) => {
let addr = self.cadence_addr(op, scanline);
let byte = self.read_dot(addr);
self.read_buffer = byte;
self.ale_latch = byte;
}
(Some((Phase::Read, op)), Some(Phase::Ale)) => {
let addr = self.cadence_addr(op, scanline);
let byte = self.read_dot(addr);
self.cadence_store(op, byte);
let addr = self.data_sm_addr;
self.ale(addr);
}
(Some((Phase::Read, op)), Some(Phase::Read)) => {
let addr = self.cadence_addr(op, scanline);
let byte = self.read_dot(addr);
self.cadence_store(op, byte);
self.read_buffer = byte;
}
}
if sm == Some(Phase::Read) {
self.increment_data_address();
}
}
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 shift_background(&mut self) {
self.bg_shift_lo <<= 1;
self.bg_shift_hi = (self.bg_shift_hi << 1) | 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;
}
self.eval_wrote = self.eval_phase == EvalPhase::Copy;
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 enter_eval_idle(&mut self) {
self.eval_phase = EvalPhase::Idle;
self.eval_m = 0;
}
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.enter_eval_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.enter_eval_idle();
} else if self.eval_found == 8 {
self.eval_phase = EvalPhase::Overflow;
}
}
}
fn eval_overflow(&mut self, scanline: u16) {
if self.eval_copy_left > 0 {
self.eval_copy_left -= 1;
self.advance_eval_pointer();
if self.eval_copy_left == 0 {
self.enter_eval_idle();
}
return;
}
if self.sprite_in_range(self.eval_latch, scanline) {
self.status |= STATUS_OVERFLOW;
self.eval_copy_left = 3;
self.advance_eval_pointer();
return;
}
self.eval_n += 1;
self.eval_m = (self.eval_m + 1) & 3;
if self.eval_n == 64 {
self.eval_n = 0;
self.enter_eval_idle();
}
}
fn advance_eval_pointer(&mut self) {
self.eval_m += 1;
if self.eval_m == 4 {
self.eval_m = 0;
self.eval_n = (self.eval_n + 1) & 63;
}
}
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 secondary_addr(&self, dot: u16) -> u8 {
match dot {
1..=64 => ((dot - 1) / 2) as u8,
65..=256 => (self.eval_sec.wrapping_add(3)) & 0x1c,
257..=320 => {
let slot = ((dot - 257) / 8) as u8;
let byte = ((dot - 257) % 8).min(3) as u8;
(slot * 4 + byte) & 31
}
_ => 0,
}
}
fn corrupt_oam(&mut self) {
let Some(row) = self.corrupt_row.take() else {
return;
};
let dst = usize::from(row & 31) * 8;
let src = usize::from(self.oam_addr & 0xf8);
if dst == src {
return;
}
for i in 0..8 {
let byte = self.oam[(src + i) & 0xff];
self.write_oam(((dst + i) & 0xff) as u8, byte);
}
self.secondary_oam[usize::from(row & 31)] = self.secondary_oam[0];
}
fn oam_read_bus(&self) -> Option<u8> {
if !self.rendering_enabled() || !self.render_line() {
return None;
}
let dot = if self.dot == 0 {
DOTS_PER_SCANLINE - 1
} else {
self.dot - 1
};
Some(match dot {
1..=64 => 0xff,
65..=256 => {
if !dot.is_multiple_of(2) || self.eval_wrote {
self.eval_latch
} else {
self.secondary_oam[usize::from(self.eval_sec) & 31]
}
}
257..=320 => {
let slot = usize::from((dot - 257) / 8);
let byte = usize::from((dot - 257) % 8).min(3);
self.secondary_oam[(slot * 4 + byte) & 31]
}
_ => self.secondary_oam[0],
})
}
fn sprite_fetch_dot(&mut self, dot: 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],
_ => {}
}
}
fn sprite_output_dot(&mut self, rendering: bool) {
for slot in 0..8 {
if self.sprite_halted & (1 << slot) != 0 {
if rendering {
self.sprite_pat_lo[slot] <<= 1;
self.sprite_pat_hi[slot] <<= 1;
}
} else {
self.sprite_x[slot] -= 1;
}
}
}
fn sprite_arm(&mut self) {
for slot in 0..8 {
if self.sprite_x[slot] == 0 {
self.sprite_halted |= 1 << slot;
}
}
}
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 index = if scanline < self.geom.top_border_lines() {
BORDER_BLACK
} 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) {
if self.sprite_halted & (1 << slot) == 0 {
continue;
}
let lo = (self.sprite_pat_lo[slot] >> 7) & 1;
let hi = (self.sprite_pat_hi[slot] >> 7) & 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) {
self.nmi_out = self.nmi_delay;
self.nmi_delay = self.nmi_raw();
if self.mask_delay > 0 {
self.mask_delay -= 1;
if self.mask_delay == 0 {
let was = self.rendering_enabled();
self.mask = self.mask_pending;
if was && !self.rendering_enabled() && self.render_line() {
self.corrupt_row = Some(self.sec_addr);
}
}
}
if self.v_delay > 0 {
self.v_delay -= 1;
if self.v_delay == 0 {
self.v = self.v_pending;
}
}
let scanline = self.scanline;
let dot = self.dot;
let rendering = self.rendering_enabled();
let visible = scanline < self.geom.visible_scanlines;
let pre_render = scanline == self.geom.pre_render_scanline;
if self.sprite0_pending {
self.status |= STATUS_SPRITE0;
self.sprite0_pending = false;
}
if scanline == self.geom.vblank_scanline && dot == 0 {
self.suppress_nmi = false;
}
if scanline == self.geom.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.corrupt_row.is_none() {
self.corrupt_row = Some(0);
}
if rendering && (visible || pre_render) {
self.corrupt_oam();
}
if rendering && (visible || pre_render) {
self.render_dot(scanline, dot, visible, pre_render);
} else {
self.memory_dot(None, scanline);
}
if visible && (1..=256).contains(&dot) {
self.sprite_arm();
}
if visible && (1..=256).contains(&dot) {
self.output_pixel(dot - 1, scanline);
}
if visible && (1..=256).contains(&dot) {
self.sprite_output_dot(rendering);
}
self.rendered_last = rendering && (visible || pre_render);
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.rendered_last
{
self.reload_shifters();
}
self.memory_dot(self.cadence_op(dot), scanline);
if dot.is_multiple_of(8) && ((8..=256).contains(&dot) || dot == 328 || dot == 336) {
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();
}
self.sec_addr = self.secondary_addr(dot);
if dot == 339 {
self.sprite_halted = 0;
}
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);
}
}
fn advance_position(&mut self, pre_render: bool, rendering: bool) {
self.dot += 1;
if self.geom.odd_frame_skip
&& pre_render
&& self.dot == DOTS_PER_SCANLINE - 1
&& 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 == self.geom.scanlines_per_frame {
self.scanline = 0;
self.frame += 1;
}
}
}
pub fn next_event_dot(&self) -> u64 {
let ahead = |line: u16, at: u16| -> u64 {
let here = u64::from(self.scanline) * DOTS_PER_SCANLINE as u64 + u64::from(self.dot);
let there = u64::from(line) * DOTS_PER_SCANLINE as u64 + u64::from(at);
let frame = self.geom.dots_per_frame;
self.dots + 1 + (there + frame - here - 1) % frame
};
let vblank_set = ahead(self.geom.vblank_scanline, 4);
let vblank_clear = ahead(self.geom.pre_render_scanline, 4);
let next_line = self.dots + u64::from(DOTS_PER_SCANLINE - self.dot);
vblank_set.min(vblank_clear).min(next_line)
}
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 mut status = self.status;
if self.scanline == self.geom.pre_render_scanline && self.dot == 1 {
status &= !(STATUS_SPRITE0 | STATUS_OVERFLOW);
}
let value = (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 = match self.oam_read_bus() {
Some(byte) => byte,
None => 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 != self.geom.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.start_data_fetch(addr & 0x2fff, attrs);
self.latch.refresh(now, value, 0x3f);
}
value
} else {
let value = self.read_buffer;
if !debug {
self.start_data_fetch(addr, attrs);
self.latch.refresh(now, value, 0xff);
}
value
}
}
fn start_data_fetch(&mut self, addr: u16, attrs: MemAttrs) {
if self.data_sm != 0 {
let stale = self.data_sm_addr;
self.read_buffer = self.bus_read(stale, attrs);
self.increment_data_address();
}
self.data_sm = DATA_SM_START;
self.data_sm_addr = addr;
}
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_pending = value;
self.mask_delay = MASK_WRITE_DELAY_DOTS;
}
}
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_pending = self.t;
self.v_delay = ADDR_WRITE_DELAY_DOTS;
} 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) & 0xfc;
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.nmi_out)?;
w.write_u8(self.sprite_halted)?;
w.write_u8(self.sec_addr)?;
w.write_bool(self.corrupt_row.is_some())?;
w.write_u8(self.corrupt_row.unwrap_or(0))?;
w.write_u8(self.mask_pending)?;
w.write_u8(self.mask_delay)?;
w.write_bool(self.warmup)?;
w.write_u8(self.ale_latch)?;
w.write_u8(self.data_sm)?;
w.write_u16(self.data_sm_addr)?;
w.write_u16(self.v_pending)?;
w.write_u8(self.v_delay)?;
w.write_u8(self.eval_copy_left)?;
w.write_bool(self.eval_wrote)?;
w.write_bool(self.rendered_last)?;
w.write_bool(self.nmi_delay)?;
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.nmi_out = r.read_bool()?;
self.sprite_halted = r.read_u8()?;
self.sec_addr = r.read_u8()?;
let pending = r.read_bool()?;
let row = r.read_u8()?;
self.corrupt_row = pending.then_some(row);
self.mask_pending = r.read_u8()?;
self.mask_delay = r.read_u8()?;
self.warmup = r.read_bool()?;
self.ale_latch = r.read_u8()?;
self.data_sm = r.read_u8()?;
self.data_sm_addr = r.read_u16()?;
self.v_pending = r.read_u16()?;
self.v_delay = r.read_u8()?;
self.eval_copy_left = r.read_u8()?;
self.eval_wrote = r.read_bool()?;
self.rendered_last = r.read_bool()?;
self.nmi_delay = 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(())
}