cge_nes 0.1.1

Cycle-accurate NES (Nintendo Entertainment System) emulator library: CPU, PPU, cartridge, input, and iNES ROM loading.
Documentation
//! NES Picture Processing Unit (PPU) emulation.
//!
//! This crate implements the NES's PPU, which is responsible for generating
//! video output. The PPU operates independently from the CPU, working on a
//! different clock frequency (3 PPU cycles for each CPU cycle).
//!
//! # Features
//!
//! * Complete PPU timing emulation
//! * Background rendering
//! * Sprite rendering with priorities
//! * Palette handling
//! * Memory-mapped register interface
//! * NMI (Non-Maskable Interrupt) generation
//!
//! # PPU Rendering System
//!
//! The PPU generates frames at 60 Hz by:
//! * Processing 262 scanlines per frame
//! * Each scanline is 341 PPU cycles
//! * 240 visible scanlines (0-239)
//! * 1 pre-render scanline (240)
//! * 21 vblank scanlines (241-260)
//!
//! # Memory Map
//!
//! The PPU has its own address space and registers:
//! * Pattern Tables: $0000-$1FFF (CHR-ROM/RAM from cartridge)
//! * Name Tables: $2000-$2FFF (VRAM)
//! * Palette RAM: $3F00-$3FFF
//!
//! # Example
//!
//! ```no_run
//! use cge_nes::ppu::{Ppu, PpuCartMemorySpace};
//!
//! struct DummyCart;
//! impl PpuCartMemorySpace for DummyCart {
//!     fn read(&mut self, addr: u16) -> u8 { 0 }
//!     fn write(&mut self, data: u8, addr: u16) {}
//! }
//!
//! let mut ppu = Ppu::default();
//! ```

#![deny(missing_docs)]

mod frame_event;
mod oam;
mod palette;
mod ppu_cycle;
mod ppu_impl;
mod ppu_registers;
mod ppu_render_pixel;
mod prepare_scanline;
mod registers;
mod scanline_pos;
mod sprite;

#[cfg(test)]
mod tests;
mod vram_read_buffer;

use crate::ppu::oam::OamArray;
use arrayvec::ArrayVec;
use registers::Registers;

pub use palette::Color;
use ppu_render_pixel::BgTileToShow;
use prepare_scanline::SpriteStartForScanline;
pub use registers::Register;
use scanline_pos::ScanlinePos;
use sprite::SpriteData;

const SCREEN_WIDTH: u16 = 256;
const SCREEN_HEIGHT: u16 = 240;
const SCREEN_PIXEL_COUNT: usize = (SCREEN_WIDTH * SCREEN_HEIGHT) as usize;
#[allow(dead_code)]
const RIGHTMOST_PIXEL_X: u8 = (SCREEN_WIDTH - 1) as u8;
#[allow(dead_code)]
const BOTTOMMOST_PIXEL_Y: u8 = (SCREEN_HEIGHT - 1) as u8;
const MAX_SPRITES_IN_SCANLINE: usize = 8;
const TILE_DIM: u8 = 8;
const SMALL_SPRITE_HEIGHT: u8 = TILE_DIM;
const LARGE_SPRITE_HEIGHT: u8 = 16;

type SpriteDataArray = ArrayVec<SpriteData, MAX_SPRITES_IN_SCANLINE>;
type SpriteStartArray = ArrayVec<SpriteStartForScanline, { MAX_SPRITES_IN_SCANLINE * 2 }>;
type ScreenColors = ArrayVec<Color, SCREEN_PIXEL_COUNT>;

/// Events that can occur during PPU frame rendering.
///
/// Used to signal important points in the PPU's operation, such as the end of a scanline,
/// when the frame is ready to be presented, or the end of a frame.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum FrameEvent {
    /// No event.
    None,
    /// End of the current scanline.
    EndOfScanline,
    /// The frame is ready to be presented to the display.
    ReadyToPresent,
    /// End of the current frame.
    EndOfFrame,
}

/// Internal events emitted while fetching background tile data into the VRAM pipeline.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum VramReadEvent {
    /// No VRAM read-related event for this cycle.
    None,
    /// Nametable tile address computed and stored.
    TileAddrSet,
    /// Nametable tile byte read.
    TileDataRead,
    /// Attribute address computed and stored.
    AttrAddrSet,
    /// Attribute byte read.
    AttrDataRead,
    /// Background low pattern address computed and stored.
    BackgroundLowAddrSet,
    /// Background low pattern byte read.
    BackgroundLowDataRead,
    /// Background high pattern address computed and stored.
    BackgroundHighAddrSet,
    /// Background high pattern byte read.
    BackgroundHighDataRead,

    SpriteAddrSet {
        /// Which of the up-to-eight sprites being fetched this cycle (0-7).
        sprite_index: u8,
        /// `true` for the high-bitplane fetch, `false` for the low-bitplane fetch.
        is_high_byte: bool,
    },
    SpriteDataRead {
        /// Which of the up-to-eight sprites being fetched this cycle (0-7).
        sprite_index: u8,
        /// `true` for the high-bitplane read, `false` for the low-bitplane read.
        is_high_byte: bool,
    },
}

#[cfg(feature = "show_name_table_change")]
/// Events related to scanline background rendering (for debugging/visualization).
#[derive(PartialEq, Eq, Default)]
enum ScanlineEvent {
    /// No event.
    #[default]
    None,
    /// Background is being shown.
    ShowBg,
    /// Background is not being shown.
    DontShowBg,
}

/// NES Picture Processing Unit (PPU) state and logic.
///
/// This struct encapsulates all PPU state, including VRAM, OAM, palette RAM,
/// internal rendering state, and scanline/cycle tracking. It provides methods
/// for emulating PPU behavior, rendering, and memory-mapped register access.
pub struct Ppu {
    /// PPU registers (control, mask, status, etc.)
    regs: Registers,
    /// Object Attribute Memory (OAM) for sprite data.
    oam: OamArray,
    /// Palette RAM for background and sprite colors.
    palette: palette::Ram,

    // Internal State
    /// Sprite data for the current scanline.
    sprites_for_scanline: SpriteDataArray,
    /// Sprite data for the next scanline.
    sprites_for_next_scanline: SpriteDataArray,
    /// Sprite start positions for the current scanline.
    sprites_start_col: SpriteStartArray,
    /// Sprites currently being rendered at the current pixel.
    sprites_at_current_pixel: SpriteDataArray,
    /// Internal read buffer for delayed PPU data reads.
    read_buffer: u8,
    /// Current scanline and cycle position.
    scanline_pos: ScanlinePos,
    /// Background tile data to be shown at the current pixel.
    bg_tile: Option<BgTileToShow>,
    /// Framebuffer storing the colors for the current frame.
    screen_colors: ScreenColors,
    /// Read buffer for name table data.
    vram_read_buffer: vram_read_buffer::VramReadBuffer,
    /// When true, sprite pattern data is fetched via the cycle-accurate sprite
    /// fetch window (cycles 257..=320 of the visible scanline), one byte per
    /// cycle, instead of being read eagerly during scanline preparation.
    ///
    /// Cartridges that need the extra CHR reads to drive a scanline counter
    /// (e.g. MMC3) request this mode via [`crate::cartridge::Cartridge::requires_cycle_accurate_sprites`]
    /// at cartridge-insertion time, and the NES console wires it into this field.
    pub cycle_accurate_sprites_enabled: bool,

    sprite_addr: u16,

    /// X position of sprite zero (for sprite zero hit detection).
    sprite_zero_pos: u8,

    #[cfg(test)]
    /// Number of PPU cycles since last reset (test/debug only).
    cycles_since_reset: usize,

    /// Whether an NMI (Non-Maskable Interrupt) should be signaled.
    nmi_signal: bool,

    #[cfg(feature = "show_name_table_change")]
    /// Last scanline event (for debugging/visualization).
    last_event: ScanlineEvent,
}

/// Trait for cartridge memory space accessible by the PPU.
///
/// Implement this trait for any cartridge or mapper that provides CHR-ROM/RAM
/// or other PPU-visible memory. The PPU uses this trait to read and write
/// pattern table data and other memory-mapped resources.
pub trait PpuCartMemorySpace {
    /// Read a byte from the cartridge's PPU memory space at the given address.
    fn read(&mut self, addr: u16) -> u8;
    /// Write a byte to the cartridge's PPU memory space at the given address.
    fn write(&mut self, data: u8, addr: u16);
    /// Notifies the cartridge that the PPU VRAM address has changed, so it can
    /// detect A12 transitions caused by the `$2007` auto-increment. The default
    /// implementation does nothing.
    fn notify_addr_change(&mut self, _old_addr: u16, _new_addr: u16) {}
}