pub struct Ppu { /* private fields */ }Expand description
2C02 PPU.
tick(bus) advances one PPU dot. The PPU is the master clock;
rustynes-core calls it three times per CPU cycle (NTSC).
Implementations§
Source§impl Ppu
impl Ppu
Sourcepub const fn set_palette(&mut self, palette: PpuPalette, is_2c05: bool, id: u8)
pub const fn set_palette(&mut self, palette: PpuPalette, is_2c05: bool, id: u8)
Configure the PPU’s hardware variant for Vs. System / PlayChoice-10 arcade carts.
palette selects the output palette (the RGB PPUs replace the 2C02
composite palette with a fixed hardware RGB lookup). is_2c05 enables
the 2C05’s register quirks: a write to $2000 sets MASK and a write to
$2001 sets CTRL (swapped), and a $2002 read ORs id into its low
bits. For a 2C02 (the default NES/Famicom path) this is never called, so
active_palette stays crate::palette::PpuPalette::Composite2C02,
is_2c05 stays false, and behaviour is byte-for-byte unchanged.
Sourcepub const fn set_custom_palette(&mut self, base: Option<[[u8; 3]; 64]>)
pub const fn set_custom_palette(&mut self, base: Option<[[u8; 3]; 64]>)
v1.1.0 beta.1 (T-110-A3) — install (or clear with None) a custom 64-entry
base palette from a loaded .pal file and rebuild the RGBA lookup. None
restores the built-in palette for the active crate::palette::PpuPalette
(byte-identical to default). A frontend presentation override.
Sourcepub const fn set_extra_scanlines(&mut self, lines: u16)
pub const fn set_extra_scanlines(&mut self, lines: u16)
v1.7.0 “Forge” Workstream F3 — set the number of EXTRA blank vblank scanlines to insert per frame (the PPU extra-scanlines overclock).
0 (the default) is stock NES timing and is byte-identical to a PPU
that never calls this. A non-zero value lengthens vblank by that many
idle scanlines each frame (more CPU run-time, no visible change), at the
existing dot resolution. Off by default; a frontend config knob, not part
of the save-state. Distinct from the CPU-multiplier overclock (v2.0).
Changing the count cancels any in-flight insertion for the current
frame: the per-frame countdown (extra_lines_remaining) is
reset to 0 so it cannot remain stale or out-of-bounds relative to
the new lines (e.g. shrinking 8 → 2, or disabling N → 0). The next
frame reloads the countdown from the new value at the insertion point.
Sourcepub const fn extra_scanlines(&self) -> u16
pub const fn extra_scanlines(&self) -> u16
v1.7.0 F3 — the currently-configured extra-scanline count (0 = stock).
Sourcepub const fn set_fast_dotloop(&mut self, enabled: bool)
pub const fn set_fast_dotloop(&mut self, enabled: bool)
v2.1.8 A1 — enable/disable the specialized visible-scanline fast dot
path. Default ON since the v2.2.3 performance pass (was OFF through
v2.2.2); either setting produces the identical frame, so this selects a
code path, not a behaviour. See Self::fast_dotloop and
docs/performance.md.
Sourcepub const fn fast_dotloop(&self) -> bool
pub const fn fast_dotloop(&self) -> bool
v2.1.8 A1 — whether the visible-scanline fast dot path is enabled.
Sourcepub const fn set_oam_decay(&mut self, enabled: bool)
pub const fn set_oam_decay(&mut self, enabled: bool)
v2.1.4 F2.3 — enable or disable the optional OAM-decay accuracy model.
Off by default. When off (the default) OAM reads/writes never consult
the decay state and the deterministic output is byte-identical to a
build without the feature — AccuracyCoin, the commercial oracle, and the
visual/external_real_games regression suites are unaffected. When on, the
PPU refreshes each 8-byte OAM row on every read (sprite evaluation + $2004)
and write; a row that goes un-refreshed for more than OAM_DECAY_CPU_CYCLES
(3000) CPU cycles decays to Mesen2’s canonical garbage pattern on the next
read (oam_decay_on_read). The model is NTSC/Dendy-only (PAL’s refresh
cadence masks decay) — the region gate lives in the hooks, so it is safe to
enable on any region.
A frontend/config knob (re-applied on load like region / active_palette),
not part of the save-state. Turning the model ON re-bases every row’s
timestamp to the current CPU cycle so a freshly-enabled model does not report
every row as instantly decayed; turning it OFF leaves the timestamps as-is
(they are simply no longer consulted).
Sourcepub const fn oam_decay_enabled(&self) -> bool
pub const fn oam_decay_enabled(&self) -> bool
v2.1.4 F2.3 — whether the optional OAM-decay model is currently enabled.
Sourcepub const fn set_revision(&mut self, revision: PpuRevision)
pub const fn set_revision(&mut self, revision: PpuRevision)
v2.1.7 P5 — select the emulated 2C02 die revision (see PpuRevision).
The PpuRevision::default (PpuRevision::Rp2c02H) models no extra
quirks, so at the default this is behaviorally inert and the PPU is
byte-identical to a build without the field. Selecting
PpuRevision::Rp2c02G additionally arms the OAMADDR ($2003)
write-during-rendering OAM corruption glitch. A construction/config knob,
re-applied on load like the region / active palette — not part of the
save-state.
Sourcepub const fn revision(&self) -> PpuRevision
pub const fn revision(&self) -> PpuRevision
v2.1.7 P5 — the currently-selected 2C02 die revision.
Sourcepub const fn apply_power_up_palette(&mut self, init: PaletteInit)
pub const fn apply_power_up_palette(&mut self, init: PaletteInit)
v2.1.7 P5 — apply a power-up palette-RAM pattern (see PaletteInit).
Writes all 32 palette-RAM bytes to the selected pattern and records the
selection so a subsequent power-cycle can re-apply it. The
PaletteInit::default (PaletteInit::Zeroed) writes all-zero — the
established power-up state — so at the default this leaves the PPU
byte-identical. Intended to be called at construction / power-on (palette
RAM is not cleared on a warm reset, matching real hardware). It writes
Self::palette_ram directly, which the snapshot already serializes, so
no snapshot-format change is required.
Sourcepub const fn power_up_palette(&self) -> PaletteInit
pub const fn power_up_palette(&self) -> PaletteInit
v2.1.7 P5 — the currently-selected power-up palette pattern.
Sourcepub fn vram_mut(&mut self) -> &mut [u8] ⓘ
pub fn vram_mut(&mut self) -> &mut [u8] ⓘ
Returns a mutable reference to the internal CIRAM (nametables).
Sourcepub const fn reset(&mut self)
pub const fn reset(&mut self)
Performs a soft-reset of the PPU (warm boot). Per docs/ppu-2c02.md:
- PPUCTRL := 0
- PPUMASK := 0
- w toggle := 0
- PPUSTATUS bits 7 (VBL) unchanged on real hardware (we leave it
as-is for parity with
$2002-race tests) - PPUDATA buffer := 0
- Mask window restarts (writes to $2000/$2001/$2005/$2006 ignored for the documented number of cycles after reset).
Sourcepub const fn take_frame_complete(&mut self) -> bool
pub const fn take_frame_complete(&mut self) -> bool
Consume and return the per-frame “frame complete” latch.
Sourcepub fn framebuffer(&self) -> &[u8] ⓘ
pub fn framebuffer(&self) -> &[u8] ⓘ
Borrow the (possibly partial) framebuffer.
Sourcepub fn debug_set_framebuffer(&mut self, rgba: &[u8])
pub fn debug_set_framebuffer(&mut self, rgba: &[u8])
v1.7.0 “Forge” Workstream B (B3) — overwrite the RGBA8 output framebuffer
(the Lua emu:setScreenBuffer(t) paints output only). Copies up to the
framebuffer length; a short source leaves the tail untouched. Output-only
— it touches only the display buffer the frontend presents, NOT any
register / latch / scroll state, so the determinism contract is
unaffected (a later real frame fully repaints it). debug-hooks-gated and
reached only through the script crate’s gated post-frame path, so the
shipped build is byte-identical.
Sourcepub fn index_framebuffer(&self) -> &[u16]
pub fn index_framebuffer(&self) -> &[u16]
Borrow the parallel per-pixel palette-index framebuffer
(256 × 240 u16s, each (emphasis << 6) | colour, 0..=511) used by the
true composite NES_NTSC filter (T-110-A1). A faithful index-space mirror
of Self::framebuffer; output-only, so the determinism contract holds.
Sourcepub fn hd_tile_source(&self) -> &[HdTileSource]
pub fn hd_tile_source(&self) -> &[HdTileSource]
v1.2.0 beta.2 (Workstream C3) — borrow the per-pixel HD-pack
tile-source buffer (256 × 240 HdTileSource records, parallel to
Self::index_framebuffer). Each entry names the CHR tile that
produced the pixel. Output-only telemetry; the determinism /
AccuracyCoin contract is unaffected. See
docs/ppu-2c02.md §HD-pack tile-source export.
Sourcepub const fn ntsc_phase(&self) -> u8
pub const fn ntsc_phase(&self) -> u8
The per-frame NTSC composite colour phase — the videoPhase the
NES_NTSC filter feeds its signal generator. 0..=2 on NTSC; on
PAL/Dendy it is the frame parity (0..=1). Snapshotted at the last frame
boundary. Cosmetic (drives only the optional filter’s dot-crawl).
Sourcepub const fn debug_registers(&self) -> [u8; 4]
pub const fn debug_registers(&self) -> [u8; 4]
Snapshot of CPU-visible register bytes (for the debugger UI).
Returns [ctrl, mask, status, oam_addr]. Read-only — does NOT clear
VBL or toggle the write latch (unlike cpu_read_register).
Sourcepub const fn debug_scroll(&self) -> (u16, u16, u8, bool)
pub const fn debug_scroll(&self) -> (u16, u16, u8, bool)
Snapshot of loopy scroll registers (v, t, x, w).
Sourcepub const fn hd_bg_scroll(&self) -> (i32, i32)
pub const fn hd_bg_scroll(&self) -> (i32, i32)
v1.8.9 — the frame’s background scroll (x, y) in NES pixels, decoded
from the t (temp VRAM addr) register + fine-X, including the nametable
bits (Mesen HD-pack _scrollX/scrollY). Used by the HD compositor to
offset parallax <background> layers by scroll * ratio. A frame-level
value (the scroll at t), not per-scanline. Output-only.
Sourcepub const fn palette_ram(&self) -> &[u8; 32]
pub const fn palette_ram(&self) -> &[u8; 32]
Borrow the 32-byte palette RAM (read-only).
Sourcepub fn set_write_attribution(&mut self, enabled: bool)
pub fn set_write_attribution(&mut self, enabled: bool)
v2.3.2 “Lucid” — arm or disarm per-byte write attribution.
Arming allocates crate::provenance::WriteAttribution::HEAP_BYTES and
starts stamping every subsequent CIRAM / OAM / palette write with the
writing instruction’s PC and cycle. Disarming frees the store outright, so
re-arming starts from a clean slate rather than resurrecting stale records
from a previous debugging session.
Purely observational — nothing in the render or timing path reads it, so output is bit-identical either way.
Sourcepub fn write_attribution(&self) -> Option<&WriteAttribution>
pub fn write_attribution(&self) -> Option<&WriteAttribution>
The write-attribution store, or None when not armed.
Sourcepub fn clear_write_attribution(&mut self)
pub fn clear_write_attribution(&mut self)
Forget every recorded attribution, keeping the store armed.
The core calls this on power-cycle and on save-state restore: the restored bytes were not written by any instruction this session ran, and reporting the PCs that happened to write those offsets before the restore would be a confidently wrong answer rather than an absent one.
Sourcepub const fn set_attrib_context(&mut self, pc: u16, cycle: u64)
pub const fn set_attrib_context(&mut self, pc: u16, cycle: u64)
Push down the (pc, cycle) of the CPU instruction whose write is about to
land, so the store site can stamp it. Called by the bus immediately before
a $2000-$3FFF register write and before an OAM DMA burst.
A no-op when attribution is not armed, and never read by emulation.
Sourcepub fn set_pixel_provenance(&mut self, enabled: bool)
pub fn set_pixel_provenance(&mut self, enabled: bool)
v2.3.2 “Lucid” phase 2 — arm or disarm per-pixel provenance capture.
Arming allocates
crate::provenance::PixelProvenanceFrame::HEAP_BYTES and starts
recording, for every emitted pixel, the layer that won, the exact palette
address, and the nametable / attribute / pattern addresses of the tile
actually on screen. Disarming frees the frame.
Independent of Self::set_write_attribution: this says which bytes
produced a pixel, that says who wrote those bytes. The panel wants
both, but each is useful alone and neither depends on the other.
Output-only, so emulation is bit-identical either way.
Sourcepub fn pixel_provenance(&self) -> Option<&PixelProvenanceFrame>
pub fn pixel_provenance(&self) -> Option<&PixelProvenanceFrame>
The current frame’s per-pixel provenance, or None when not armed.
Sourcepub fn clear_pixel_provenance(&mut self)
pub fn clear_pixel_provenance(&mut self)
Forget every recorded pixel, keeping the frame armed.
Mirrors Self::clear_write_attribution, and for the same reason: a
restore lands mid-frame, so without this the panel would report tile and
palette addresses from the abandoned timeline for every pixel above the
current scanline, with nothing marking them stale.
Sourcepub const fn take_provenance(&mut self) -> ProvenanceStash
pub const fn take_provenance(&mut self) -> ProvenanceStash
Move both provenance stores out, leaving the PPU unarmed.
Paired with Self::put_provenance to carry the stores across a
same-timeline restore that would otherwise clear them — see
crate::provenance::ProvenanceStash for why run-ahead needs that and
save-state loads and netplay rollback do not.
prov_armed is dropped to false alongside the frame it mirrors, so the
invariant “prov_armed iff prov_frame.is_some()” holds while stashed
and emit_pixel records nothing into the vacated slot.
Sourcepub fn put_provenance(&mut self, stash: ProvenanceStash)
pub fn put_provenance(&mut self, stash: ProvenanceStash)
Put back stores taken by Self::take_provenance.
Overwrites whatever is currently held, which is what the pairing wants: the only thing that can have appeared in between is a restore’s cleared (or absent) store, and the stashed records are the ones the caller means to keep.
Sourcepub const fn latch_dma_attrib_context(&mut self)
pub const fn latch_dma_attrib_context(&mut self)
Freeze the current instruction context as the cause of an OAM DMA burst.
Called by the bus from the $4014 write, i.e. while
Self::set_attrib_context still holds the STA $4014 itself.
The burst cannot use the live context: $4014 only arms the transfer, and
its 513 or 514 cycles are then stolen from the instructions that follow,
so by the time the first OAM byte lands the live context names whichever
instruction is being halted — true about the timing, wrong about the cause.
Sourcepub const fn debug_poke_palette(&mut self, idx: u8, value: u8)
pub const fn debug_poke_palette(&mut self, idx: u8, value: u8)
v1.7.0 “Forge” Workstream A1 — debugger writeback: store one palette-RAM
byte directly (idx masked to 0..32, value masked to the 6-bit palette
width), reusing the same canonical mirroring/masking as the live
$2007 write path. Used only by the debug-hooks editor writeback,
which routes through the gated post-frame poke path — so the default
(no-edit) build never calls it and stays byte-identical.
Sourcepub const fn debug_poke_oam(&mut self, idx: u8, value: u8)
pub const fn debug_poke_oam(&mut self, idx: u8, value: u8)
v1.7.0 “Forge” Workstream A1 — debugger writeback: store one OAM byte
directly. debug-hooks-gated; only reached through the gated post-frame
poke path, so the default build is byte-identical.
Sourcepub const fn debug_poke_ciram(&mut self, phys: usize, value: u8)
pub const fn debug_poke_ciram(&mut self, phys: usize, value: u8)
v1.7.0 “Forge” Workstream A1 — debugger writeback: store one CIRAM byte
at a physical offset (caller resolves mirroring via the mapper).
debug-hooks-gated; only reached through the gated post-frame poke path.
Sourcepub const fn sprite_size_16(&self) -> bool
pub const fn sprite_size_16(&self) -> bool
true when sprites are rendered in 8x16 mode (CTRL bit 5).
Sourcepub const fn bg_pattern_base(&self) -> u16
pub const fn bg_pattern_base(&self) -> u16
Base address of the BG pattern table ($0000 or $1000).
Sourcepub const fn sprite_pattern_base(&self) -> u16
pub const fn sprite_pattern_base(&self) -> u16
Base address of the sprite pattern table (8x8 mode only).
Sourcepub fn oam_dma_write(&mut self, value: u8)
pub fn oam_dma_write(&mut self, value: u8)
OAM DMA byte write: place value at oam[oam_addr] and increment
oam_addr. Used by the bus’s OAM DMA state machine.
Bypasses the OAMADDR-during-rendering corruption modeled by
cpu_write_register for $2004 direct writes — DMA writes always
hit OAM directly per nesdev.
Sourcepub const fn on_cpu_cycle(&mut self)
pub const fn on_cpu_cycle(&mut self)
Notify the PPU that one CPU cycle has elapsed. Used to drive the post-reset masking window and the open-bus decay timers.
Sourcepub fn cpu_read_register<B: PpuBus>(&mut self, reg: u8, bus: &mut B) -> u8
pub fn cpu_read_register<B: PpuBus>(&mut self, reg: u8, bus: &mut B) -> u8
CPU register read at $2000-$3FFF (only the low 3 bits matter).
Source§impl Ppu
impl Ppu
Sourcepub fn snapshot(&self) -> Vec<u8> ⓘ
pub fn snapshot(&self) -> Vec<u8> ⓘ
Encode the PPU’s mutable state into a versioned binary blob.
Sourcepub fn snapshot_slim(&self) -> Vec<u8> ⓘ
pub fn snapshot_slim(&self) -> Vec<u8> ⓘ
v2.3.3 — Ppu::snapshot without the framebuffer. See
PPU_SNAPSHOT_SLIM_FLAG.