Skip to main content

Ppu

Struct Ppu 

Source
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

Source

pub fn new(region: PpuRegion) -> Self

New PPU in power-on state.

Source

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.

Source

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.

Source

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.

Source

pub const fn extra_scanlines(&self) -> u16

v1.7.0 F3 — the currently-configured extra-scanline count (0 = stock).

Source

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.

Source

pub const fn fast_dotloop(&self) -> bool

v2.1.8 A1 — whether the visible-scanline fast dot path is enabled.

Source

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).

Source

pub const fn oam_decay_enabled(&self) -> bool

v2.1.4 F2.3 — whether the optional OAM-decay model is currently enabled.

Source

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.

Source

pub const fn revision(&self) -> PpuRevision

v2.1.7 P5 — the currently-selected 2C02 die revision.

Source

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.

Source

pub const fn power_up_palette(&self) -> PaletteInit

v2.1.7 P5 — the currently-selected power-up palette pattern.

Source

pub fn vram_ref(&self) -> &[u8]

Returns a reference to the internal CIRAM (nametables).

Source

pub fn vram_mut(&mut self) -> &mut [u8]

Returns a mutable reference to the internal CIRAM (nametables).

Source

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).
Source

pub const fn nmi_line(&self) -> bool

Returns true if the PPU is asserting the NMI line.

Source

pub const fn take_frame_complete(&mut self) -> bool

Consume and return the per-frame “frame complete” latch.

Source

pub fn framebuffer(&self) -> &[u8]

Borrow the (possibly partial) framebuffer.

Source

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.

Source

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.

Source

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.

Source

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).

Source

pub const fn dot(&self) -> u16

Current dot (0..=340).

Source

pub const fn scanline(&self) -> i16

Current scanline.

Source

pub const fn frame(&self) -> u64

Current frame counter.

Source

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).

Source

pub const fn debug_scroll(&self) -> (u16, u16, u8, bool)

Snapshot of loopy scroll registers (v, t, x, w).

Source

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.

Source

pub const fn palette_ram(&self) -> &[u8; 32]

Borrow the 32-byte palette RAM (read-only).

Source

pub fn oam(&self) -> &[u8]

Borrow OAM (256 bytes = 64 sprites x 4 bytes).

Source

pub fn ciram(&self) -> &[u8]

Borrow nametable CIRAM (2 KiB).

Source

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.

Source

pub fn write_attribution(&self) -> Option<&WriteAttribution>

The write-attribution store, or None when not armed.

Source

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.

Source

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.

Source

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.

Source

pub fn pixel_provenance(&self) -> Option<&PixelProvenanceFrame>

The current frame’s per-pixel provenance, or None when not armed.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub const fn sprite_size_16(&self) -> bool

true when sprites are rendered in 8x16 mode (CTRL bit 5).

Source

pub const fn bg_pattern_base(&self) -> u16

Base address of the BG pattern table ($0000 or $1000).

Source

pub const fn sprite_pattern_base(&self) -> u16

Base address of the sprite pattern table (8x8 mode only).

Source

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.

Source

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.

Source

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

pub fn cpu_write_register<B: PpuBus>(&mut self, reg: u8, value: u8, bus: &mut B)

CPU register write.

Source

pub fn tick<B: PpuBus>(&mut self, bus: &mut B)

Tick exactly one dot.

Source§

impl Ppu

Source

pub fn snapshot(&self) -> Vec<u8>

Encode the PPU’s mutable state into a versioned binary blob.

Source

pub fn snapshot_slim(&self) -> Vec<u8>

v2.3.3 — Ppu::snapshot without the framebuffer. See PPU_SNAPSHOT_SLIM_FLAG.

Source

pub fn restore(&mut self, data: &[u8]) -> Result<(), PpuSnapshotError>

Decode a previously Ppu::snapshoted blob.

§Errors

Returns PpuSnapshotError on a malformed blob.

Trait Implementations§

Source§

impl Debug for Ppu

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl Freeze for Ppu

§

impl RefUnwindSafe for Ppu

§

impl Send for Ppu

§

impl Sync for Ppu

§

impl Unpin for Ppu

§

impl UnsafeUnpin for Ppu

§

impl UnwindSafe for Ppu

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.