Skip to main content

nes_ppu/
lib.rs

1// Copyright foobles 2023.
2//
3// This work is licensed under the Creative Commons Attribution-NonCommercial 4.0
4// International License. To view a copy of this license, visit
5// http://creativecommons.org/licenses/by-nc/4.0/ or send a letter to Creative
6// Commons, PO Box 1866, Mountain View, CA 94042, USA.
7
8//! A NES graphics emulator with a Rust interface.
9//!
10//! This library emulates the NTSC NES PPU (2c02), and provides a generic interface mimicking the
11//! interface that exists on actual NES hardware. In addition, the interface also contains some
12//! conveniences not available on the NES in order to make programming more ergonomic.
13//!
14//! ## Features
15//! * `no_std` support
16//! * Cycle-based emulation, including accurate timings for sprite processing and mapper accesses
17//! * Emulation of all PPU registers (0x2000-0x2007)
18//! * Support for arbitrary custom memory mappers and output formats
19//! * Most system quirks are properly emulated:
20//!   * Garbage nametable fetches
21//!   * Different total cycle counts on even vs. odd frames
22//!   * Buggy overflow flag behavior
23//!   * Reading OAMDATA during rendering snoops on internal sprite processing state
24//!   * Incorrect color output during forced blanking when vram address indexes palette ram
25//!   * Etc.
26//!
27//! ## Limitations
28//! * PPU register accesses happen "instantaneously," and do not cause the PPU to tick forwards
29//!   despite reads/writes taking multiple cycles on real hardware
30//!     * For truly accurate graphics, users must therefore be careful to weigh how much work is
31//!       being done relative to when/how often they tick the PPU
32//! * No support for PAL or Dendy PPUs
33//! * No emulation of open bus behavior
34//! * Mapper reads take 1 cycle to resolve, instead of 2 like on real hardware (the timings of when
35//!   the reads start are still accurate)
36//!
37//! ## Example
38//! ```
39//! use nes_ppu::{Ppu, Mapper, PixelBuffer, Color, ColorEmphasis};
40//!
41//! struct ExamplePixelBuffer {
42//!     /* ... */
43//! }
44//!
45//! impl ExamplePixelBuffer {
46//!     fn render_to_screen(&self) {
47//!         /* ... */
48//!     }
49//! }
50//!
51//! impl PixelBuffer for ExamplePixelBuffer {
52//!     fn set_color(&mut self, x: u8, y: u8, color: Color, emphasis: ColorEmphasis) {
53//!         // write color information into internal screen buffer to be rendered when the frame
54//!         // is complete
55//!         /* ... */
56//!     }
57//! }
58//!
59//! struct ExampleMapper {
60//!     /* ... */
61//! }
62//!
63//! impl Mapper for ExampleMapper {
64//!     fn read(&mut self, addr: u16) -> u8 {
65//!         /* ... */
66//! #       0
67//!     }
68//!
69//!     fn write(&mut self, addr: u16, value: u8) {
70//!         /* ... */
71//!     }
72//! }
73//!
74//! fn main_loop() {
75//!     let mut ppu = Ppu::new();
76//!     let mut mapper = ExampleMapper { /* ... */ };
77//!     let mut buf = ExamplePixelBuffer { /* ... */ };
78//!
79//!     loop {
80//!         // run main game logic here
81//!
82//!         ppu.tick_to_next_sprite_0_hit(&mut mapper, &mut buf);
83//!         // add sprite 0 hit raster effect here
84//!
85//!         ppu.tick_to_next_vblank(&mut mapper, &mut buf);
86//!         buf.render_to_screen();
87//!
88//!         // modify vram and set up scroll position for next frame here
89//!     }
90//! }
91//! ```
92
93#![no_std]
94#[cfg(test)]
95mod tests;
96use bytemuck::{Pod, Zeroable};
97
98/// A number corresponding to a color.
99///
100/// Each color the [`Ppu`] can output corresponds to a 6-bit `Color` value.
101/// The low 4 bits specify the hue, and the high 2 bits specify the brightness.
102///
103/// Color values are only valid in the range `0x00-0x3F`. Any value outside that range will
104/// be truncated to fit when writing to [`Ppu`] color palette memory.
105///
106/// Warning: using color value `13`/`0x0d` on actual Nintendo hardware generates
107/// invalid NTSC signals that can cause televisions to display the frame incorrectly.
108/// Avoid using color value `13`, though it is still allowed for authenticity.
109///
110/// Here is an example palette demonstrating what color each number should correspond to
111/// (numbers given in hex):
112/// ![Table showing 64 colored squares with overlayed hex values](https://i.imgur.com/4JRZBye.png)
113///
114/// Note that different TVs display NES output differently, so this palette is not exact. This
115/// image is just a rough example.
116pub type Color = u8;
117
118/// A set of colors usable by tiles and sprites.
119///
120/// Tiles and sprites each have a bit depth of 2 bits per pixel, meaning that any given
121/// tile or sprite can only use 4 total colors. For both tiles and sprites, the color index 0
122/// always corresponds to transparency. The remaining indicies (1, 2, and 3) correspond to
123/// the colors in a `Palette`.
124///
125/// The [`Ppu`] supports 4 different palettes for tiles, and a separate 4 palettes for sprites.
126#[derive(Debug, Copy, Clone, Default)]
127pub struct Palette {
128    /// The array of 3 colors in the palette.
129    pub colors: [Color; 3],
130}
131
132const SPRITE_SIZE: u8 = 4;
133
134/// A floating graphic is rendered separately from tiles.
135///
136/// A sprite can be drawn to any position on the screen, and is either 8x8px or 8x16px in size.
137/// Each sprite uses one of 4 sprite palettes for color, which are separate from the 4 tile
138/// palettes.
139///
140/// [Read more about sprites on the NESdev Wiki.](https://www.nesdev.org/wiki/PPU_OAM)
141#[derive(Debug, Copy, Clone, Default, Pod, Zeroable)]
142#[repr(C)]
143pub struct Sprite {
144    /// Y coordinate of the sprite's top-left corner.
145    pub y: u8,
146    /// The index for the sprite's graphics. In 8x8px mode, this is relative to the
147    /// sprite pattern table selected in the PPU [ctrl](Ppu::write_ctrl) register. In 8x16px mode,
148    /// The least significant bit sets the bank, and the most significant 7 bits index into pairs
149    /// of graphics within that bank.
150    pub pattern_index: u8,
151    /// A bit field configuring whether the sprite is flipped along the x or y axes,
152    /// whether it is rendered in front of or behind tiles, and the sprite's palette index.
153    pub attributes: u8,
154    /// X coordinate of the sprite's top-left corner.
155    pub x: u8,
156}
157
158/// The NES Picture Processing Unit.
159///
160/// The Picture Processing Unit, or PPU, renders each frame of video line-by-line,
161/// pixel-by-pixel. It starts in the top left corner of the screen, moves to the right, and then
162/// continues from the leftmost pixel of the next row underneath the previous.
163///
164/// ## Scanlines
165/// Each row of pixels is referred to as a scanline. After a full row of 256 pixels is rendered,
166/// the PPU has to wait a short amount of time (85 ticks) before it can begin outputting pixels for
167/// the next scanline. During this waiting period, the PPU fetches graphics data from memory
168/// that it will need for the next scanline. This waiting period at the end of each scanline is
169/// referred to as **horizontal blanking** or **hblank**.
170///
171/// Because the PPU is not rendering during hblank, and thus some parts of its operation are idle,
172/// hblank provides a short window for programs to interfere with the state of rendering in the
173/// middle of a frame, albeit in a limited capacity.
174///
175/// ## Frames
176/// Each frame of video the NES outputs is 256x240 pixels. After the 240th scanline is complete,
177/// the PPU enters a new blanking period called **vertical blanking** or **vblank**, which lasts
178/// for about 20 scanlines' worth of time (depending on how you count). During this period, the
179/// PPU is completely idle, meaning this is the span of time where programs should write data
180/// into VRAM, copy new sprite information into OAM, etc.
181///
182/// ## OAM
183/// Object Attribute Memory, or OAM, is a 256 byte array stored internally within the PPU
184/// containing information about all the sprites currently on screen. Each sprite takes up 4 bytes,
185/// so OAM can alternately be thought of as an array of 64 sprites.
186///
187/// Each scanline, the PPU reads through OAM to see which sprites should be rendered on that line,
188/// which it determines by comparing their y-coordinates, the sprite size configured in the
189/// [ctrl](Ppu::write_ctrl) register, and the current scanline number. It picks the first 8
190/// candidates it finds, and renders them on the next scanline.
191///
192/// This means that there are two important things to consider when placing sprite data into OAM:
193/// * Sprites are rendered one pixel lower than their specified y-coordinate.
194/// * Only 8 sprites are rendered per scanline, with ones coming later in OAM being ignored.
195///
196/// A common strategy on the NES to prevent sprites from "disappearing" when too many appear
197/// on a single row of pixels is to shuffle the order that sprites appear in OAM every frame.
198/// This way, instead of some sprites disappearing, all the sprites on that row "flicker," because
199/// the sprites that get ignored are different every frame.
200///
201/// There is no way to disable a sprite; all 64 sprites are always active at once.
202/// However, you can hide a sprite by setting its y-coordinate below the visible area of the
203/// screen, i.e., to any value ≥ 240, most commonly 0xFF.
204#[derive(Debug)]
205pub struct Ppu {
206    oam: [Sprite; 64],
207    sprite_palettes: [Palette; 4],
208    tile_palettes: [Palette; 4],
209    zero_colors: [Color; 4],
210
211    ctrl: u8,
212    mask: u8,
213    status: u8,
214
215    t_reg: u16,
216    v_reg: u16,
217    fine_x_scroll: u8, // 3 bits,
218    w_latch: bool,
219    read_buffer: u8,
220
221    sprite_0_cur_line: bool,
222    sprite_0_next_line: bool,
223
224    secondary_oam: [Sprite; 8],
225    sprite_render_states: [SpriteRenderState; 8],
226    oam_mdr: u8,
227    oam_evaluation_index: u8,
228    secondary_oam_evaluation_index: u8,
229    sprite_evaluation_state: SpriteEvaluationState,
230    oam_evaluation_index_overflow: bool,
231    temp_sprite_pattern_lo: u8,
232
233    tile_pattern_shift_reg: u32,   // four 8-bit shift registers
234    tile_attribute_shift_reg: u16, // two 8-bit shift registers
235    tile_attribute_latch: u8,      // 2 bits
236
237    temp_tile_pattern_index: u8,
238    temp_tile_attribute: u8,
239    temp_tile_pattern_lo: u8,
240    temp_tile_pattern_hi: u8,
241
242    cur_scanline: u16,
243    cur_dot: u16,
244    is_even_frame: bool,
245}
246
247#[derive(Debug, Copy, Clone, Default)]
248struct SpriteRenderState {
249    pattern_shift_reg: u16, // two 8-bit shift registers
250    x_counter: u8,
251    attributes: u8,
252}
253
254#[derive(Eq, PartialEq)]
255enum RenderMode {
256    Normal,
257    PreRender,
258}
259
260#[derive(Debug, Eq, PartialEq)]
261enum SpriteEvaluationState {
262    CheckingNormal,
263    CheckingOverflow,
264    CopyingNormal(u8),
265    CopyingOverflow(u8),
266    Complete,
267}
268
269/// Memory map used by the PPU to access video memory.
270///
271/// This trait enables the [`Ppu`] to read from video memory while rendering, in order to fetch
272/// tile and pattern data. In addition, internal palette data is accessible to the user by
273/// using certain address ranges.
274///
275/// The PPU uses the following address ranges to look up the corresponding data:
276/// * `0x0000..=0x0FFF`: Pattern table 1
277/// * `0x1000..=0x1FFF`: Pattern table 2
278/// * `0x2000..=0x23FF`: Nametable 1
279/// * `0x2400..=0x27FF`: Nametable 2
280/// * `0x2800..=0x2BFF`: Nametable 3
281/// * `0x2C00..=0x2FFF`: Nametable 4
282///
283/// Addresses outside of these ranges can be mapped to anything. Furthermore, you may
284/// map addresses to overlapping regions of memory. For example, it is common to map the
285/// addresses for nametables 1 and 2 to the same memory that the addresses for nametables 3 and 4
286/// are mapped to.
287///
288/// The address range `0x3F00..=0x3F1F` and all subsequent addresses up to `0x3FFF` always
289/// map to internal palette memory. On its own, the PPU will never access the mapper to read or
290/// write to addresses in this range. The only situation where the mapper can be accessed with an
291/// address in `0x3F00..=0x3FFF` is via a call to [`Ppu::read_data()`].
292///
293/// Read more on the NESdev Wiki:
294/// * [PPU memory map](https://www.nesdev.org/wiki/PPU_memory_map)
295/// * [Pattern tables](https://www.nesdev.org/wiki/PPU_pattern_tables)
296/// * [Nametables](https://www.nesdev.org/wiki/PPU_nametables)
297/// * [Palette memory](https://www.nesdev.org/wiki/PPU_palettes#Memory_Map)
298pub trait Mapper {
299    /// Returns the value mapped to the provided 14-bit address.
300    ///
301    /// This function is allowed to have side effects and consecutive reads of the same
302    /// address do not need to return the same value.
303    ///
304    /// A memory read does not actually need to occur.
305    fn read(&mut self, addr: u16) -> u8;
306
307    /// Writes to the value mapped to the provided 14-bit address.
308    ///
309    /// This function is allowed to have side effects beyond the expressed memory write,
310    /// and subsequent reads from the same address do not need to return `value`.
311    ///
312    /// A memory write does not actually need to occur.
313    fn write(&mut self, addr: u16, value: u8);
314}
315
316/// Receives pixel information from the PPU as it draws.
317///
318/// This trait allows for the [`Ppu`] to be used with any graphical (or otherwise) frontend.
319pub trait PixelBuffer {
320    /// Sets the color of the pixel at the given x/y coordinate to that specified by `color`,
321    /// and modulated by `emphasis`.
322    ///
323    /// `x` is always in the range `0..=255` and specifies distance from the left side of the screen.
324    /// `y` is always in the range `0..=239` and specifies distance from the top of the screen.
325    fn set_color(&mut self, x: u8, y: u8, color: Color, emphasis: ColorEmphasis);
326}
327
328/// Automatic increment of VRAM addr (0: 1; 1: 32).
329pub const PPUCTRL_ADDR_INC: u8 = 1 << 2;
330/// Sprite pattern table (0: 0x0000; 1: 0x1000). Ignored if sprite size is 8x16px.
331pub const PPUCTRL_SPRITE_PATTERN_TABLE: u8 = 1 << 3;
332/// Tile pattern table (0: 0x0000; 1: 0x1000).
333pub const PPUCTRL_TILE_PATTERN_TABLE: u8 = 1 << 4;
334/// Sprite size (0: 8x8px; 1: 8x16px).
335pub const PPUCTRL_SPRITE_SIZE: u8 = 1 << 5;
336/// EXT pin behavior (0: background read from EXT; 1: color output on EXT) - DO NOT USE.
337///
338/// Note that setting this bit in the [ctrl] register causes a short circuit on NES hardware,
339/// which this library emulates via panicking.
340///
341/// [ctrl]: Ppu::write_ctrl
342pub const PPUCTRL_MSS: u8 = 1 << 6;
343/// Interrupt when PPU enters vblank (0: off; 1: on).
344///
345/// This emulator does not emulate interrupts, so this is ignored.
346pub const PPUCTRL_NMI_ENABLE: u8 = 1 << 7;
347
348/// Greyscale color.
349pub const PPUMASK_GREYSCALE: u8 = 1 << 0;
350/// Show tiles in the leftmost 8 pixels of the screen.
351pub const PPUMASK_SHOW_COLUMN_0_TILES: u8 = 1 << 1;
352/// Show sprites in the leftmost 8 pixels of the screen.
353pub const PPUMASK_SHOW_COLUMN_0_SPRITES: u8 = 1 << 2;
354/// Show tiles.
355pub const PPUMASK_SHOW_TILES: u8 = 1 << 3;
356/// Show sprites.
357pub const PPUMASK_SHOW_SPRITES: u8 = 1 << 4;
358/// Emphasize red color output.
359pub const PPUMASK_EMPH_RED: u8 = 1 << 5;
360/// Emphasize green color output.
361pub const PPUMASK_EMPH_GREEN: u8 = 1 << 6;
362/// Emphasize blue color output.
363pub const PPUMASK_EMPH_BLUE: u8 = 1 << 7;
364
365/// Sprite dropout has occurred this frame (bugged).
366pub const PPUSTATUS_OVERFLOW: u8 = 1 << 5;
367/// A non-transparent pixel of sprite 0 has overlapped a non-transparent pixel of a tile this frame.
368pub const PPUSTATUS_SPRITE_0_HIT: u8 = 1 << 6;
369/// The PPU is in vblank.
370pub const PPUSTATUS_VBLANK: u8 = 1 << 7;
371
372/// Mask for sprite palette index bits.
373pub const SPRITE_PALETTE_MASK: u8 = 0b00000011;
374/// Render sprite in front or behind tiles (0: in front; 1: behind).
375pub const SPRITE_PRIORITY: u8 = 0b00100000;
376/// Flip sprite along the x axis.
377pub const SPRITE_FLIP_X: u8 = 0b01000000;
378/// Flip sprite along the y axis.
379pub const SPRITE_FLIP_Y: u8 = 0b10000000;
380
381const X_SCROLL_MASK: u16 = 0b000_01_00000_11111;
382const Y_SCROLL_MASK: u16 = 0b111_10_11111_00000;
383
384const COARSE_X_SCROLL_MASK: u16 = 0b000_00_00000_11111;
385const FINE_Y_SCROLL_MASK: u16 = 0b111_00_00000_00000;
386const COARSE_Y_SCROLL_MASK: u16 = 0b000_00_11111_00000;
387
388const COARSE_X_OFFSET: u16 = 0;
389const FINE_Y_OFFSET: u16 = 12;
390const COARSE_Y_OFFSET: u16 = 5;
391
392const NAMETABLE_SELECT_MASK: u16 = 0b000_11_00000_00000;
393// const NAMETABLE_X_SELECT_MASK: u16 = 0b000_01_00000_00000;
394const NAMETABLE_Y_SELECT_MASK: u16 = 0b000_10_00000_00000;
395
396const NAMETABLE_SELECT_OFFSET: u16 = 10;
397
398const NAMETABLE_BASE_ADDRESS: u16 = 0b10_00_0000_000000;
399const ATTRIBUTE_TABLE_OFFSET: u16 = 0b00_00_1111_000000;
400
401/// Red, green, and/or blue color emphasis information.
402///
403/// This type is only used to communicate how a [`PixelBuffer`] should modulate
404/// its color output when it receives pixel information.
405///
406/// The the `bits` field is a bitset with 3 fields in its least significant bits:
407/// ```text
408/// 7 6 5 4 3 2 1 0
409/// x x x x x B G R
410///           | | |
411///           | | +- Red color emphasis.
412///           | +--- Blue color emphasis.
413///           +----- Green color emphasis.
414/// ```
415/// The high 5 bits will never be set.
416#[derive(Debug, Copy, Clone)]
417pub struct ColorEmphasis {
418    pub bits: u8,
419}
420#[repr(u16)]
421enum BitPlane {
422    Lo = 0,
423    Hi = 8,
424}
425
426#[derive(Debug, Copy, Clone)]
427struct PixelInfo {
428    color: Color,
429    sprite_0_hit: bool,
430}
431
432fn pattern_table_base(b: u8) -> u16 {
433    match b {
434        0 => 0x0000,
435        _ => 0x1000,
436    }
437}
438
439// Used for combining two 8-bit shift register values into a single
440// 16 bit value that is shifted down 2 bits at a time.
441fn morton_encode_16(lo: u8, hi: u8) -> u16 {
442    let mut x = u16::from(lo);
443    x = (x | (x << 4)) & 0x0F0F;
444    x = (x | (x << 2)) & 0x3333;
445    x = (x | (x << 1)) & 0x5555;
446
447    let mut y = u16::from(hi);
448    y = (y | (y << 4)) & 0x0F0F;
449    y = (y | (y << 2)) & 0x3333;
450    y = (y | (y << 1)) & 0x5555;
451
452    x | (y << 1)
453}
454
455fn palette_address_color_index(addr: u16) -> usize {
456    (addr & 0b11) as usize
457}
458
459fn palette_address_palette_index(addr: u16) -> usize {
460    ((addr >> 2) & 0b11) as usize
461}
462
463fn is_palette_address_tile_color(addr: u16) -> bool {
464    (addr & 0b10000) == 0
465}
466
467impl Ppu {
468    /// Creates a new PPU instance in an unspecified state.
469    pub fn new() -> Self {
470        Ppu {
471            oam: [Sprite::default(); 64],
472            sprite_palettes: [Palette::default(); 4],
473            tile_palettes: [Palette::default(); 4],
474            zero_colors: [0; 4],
475            ctrl: 0,
476            mask: 0,
477            status: 0,
478            t_reg: 0,
479            v_reg: 0,
480            fine_x_scroll: 0,
481            w_latch: false,
482            read_buffer: 0,
483            sprite_0_cur_line: false,
484            sprite_0_next_line: false,
485            secondary_oam: [Sprite::default(); 8],
486            sprite_render_states: [SpriteRenderState::default(); 8],
487            oam_mdr: 0,
488            oam_evaluation_index: 0,
489            secondary_oam_evaluation_index: 0,
490            sprite_evaluation_state: SpriteEvaluationState::CheckingNormal,
491            oam_evaluation_index_overflow: false,
492            temp_sprite_pattern_lo: 0,
493            tile_pattern_shift_reg: 0,
494            tile_attribute_shift_reg: 0,
495            tile_attribute_latch: 0,
496            temp_tile_pattern_index: 0,
497            temp_tile_attribute: 0,
498            temp_tile_pattern_lo: 0,
499            temp_tile_pattern_hi: 0,
500            cur_scanline: 261,
501            cur_dot: 0,
502            is_even_frame: false,
503        }
504    }
505
506    /// Run the PPU for 1 cycle. This may induce memory accesses through the `mapper`, as well as
507    /// outputting pixel information to the `buffer`.
508    pub fn tick<M: Mapper, B: PixelBuffer>(&mut self, mapper: &mut M, buffer: &mut B) {
509        match self.cur_scanline {
510            0..=239 => self.tick_render(mapper, buffer, RenderMode::Normal), // render
511            240..=260 => self.tick_vblank(),                                 // vblank
512            261 => self.tick_render(mapper, buffer, RenderMode::PreRender),  // pre render line
513            _ => unreachable!(),
514        }
515
516        match self.cur_dot {
517            339 if self.cur_scanline == 261 => {
518                if !self.is_even_frame && self.is_rendering_enabled() {
519                    self.cur_dot = 0;
520                    self.cur_scanline = 0;
521                } else {
522                    self.cur_dot += 1;
523                }
524                self.is_even_frame = !self.is_even_frame;
525            }
526            0..=339 => {
527                self.cur_dot += 1;
528            }
529            340 => {
530                self.cur_dot = 0;
531                self.cur_scanline = (self.cur_scanline + 1) % 262;
532            }
533            _ => unreachable!(),
534        }
535    }
536
537    /// Run the PPU until it would emit an interrupt signalling that it has entered the vertical
538    /// blanking period. If the PPU is already in vblank, this will tick until the
539    /// vertical blank of the next frame.
540    ///
541    /// Just like [`Ppu::tick()`], this function may induce memory accesses through `mapper` and
542    /// output pixel information to `buffer`.
543    ///
544    /// Once this function returns, a full frame will have been rendered to `buffer`, which is a
545    /// good time to output its contents to the screen.
546    pub fn tick_to_next_vblank<M: Mapper, B: PixelBuffer>(
547        &mut self,
548        mapper: &mut M,
549        buffer: &mut B,
550    ) {
551        while self.status & PPUSTATUS_VBLANK != 0 {
552            self.tick(mapper, buffer);
553        }
554        while self.status & PPUSTATUS_VBLANK == 0 {
555            self.tick(mapper, buffer);
556        }
557    }
558
559    /// Run the PPU until the sprite-0-hit flag is set in the status register. If the flag is
560    /// already set, this will run until it is cleared and then set again.
561    ///
562    /// Just like [`Ppu::tick()`], this function may induce memory accesses through `mapper` and
563    /// output pixel information to `buffer`.
564    ///
565    /// Once this function returns, the PPU will have just output the first pixel where a
566    /// non-transparent pixel of the sprite in slot 0 overlaps a non-transparent pixel of a
567    /// tile. This is a good way to time mid-frame raster effects like split scrolling.
568    ///
569    pub fn tick_to_next_sprite_0_hit<M: Mapper, B: PixelBuffer>(
570        &mut self,
571        mapper: &mut M,
572        buffer: &mut B,
573    ) {
574        while self.status & PPUSTATUS_SPRITE_0_HIT != 0 {
575            self.tick(mapper, buffer);
576        }
577        while self.status & PPUSTATUS_SPRITE_0_HIT == 0 {
578            self.tick(mapper, buffer);
579        }
580    }
581
582    /// Overwrites the contents of [OAM] with the given sprite array.
583    ///
584    /// This is a convenience utility meant to partially replicate the behavior of OAM DMA on the
585    /// actual NES, but it is not emulated precisely.
586    ///
587    /// [OAM]: Ppu#oam
588    pub fn set_oam(&mut self, sprites: [Sprite; 64]) {
589        self.oam = sprites;
590    }
591
592    /// Overwrites the contents of [OAM] with the given byte array.
593    ///
594    /// This is a convenience utility meant to partially replicate the behavior of OAM DMA on the
595    /// actual NES, but it is not emulated precisely.
596    ///
597    /// [OAM]: Ppu#oam
598    pub fn set_oam_bytes(&mut self, bytes: [u8; 256]) {
599        self.oam_bytes_mut().copy_from_slice(&bytes);
600    }
601
602    /// Sets the high or low byte of the address for VRAM data accesses.
603    ///
604    /// Calling `write_addr()` the first time will set the high 6 bits of the VRAM address (the
605    /// high 2 bits of the input are ignored),
606    /// and calling it again will set the low 8 bits. Thus, `write_addr()` is usually called
607    /// twice in succession.
608    ///
609    /// Writing to either half of the address writes to the internal T register.
610    /// When writing to the low 8 bits, the internal V register is set to the
611    /// new value of T afterwards. This means that the effective address that VRAM data accesses
612    /// will use is only updated after setting the low bits.
613    /// ```
614    /// # use nes_ppu::*;
615    /// let mut ppu = Ppu::new();
616    /// ppu.write_addr(0x20);       // sets the high 6 bits of the address
617    /// ppu.write_addr(0x01);       // sets the low 8 bits of the address & updates effective address
618    /// // the address now contained in T and V is 0x2001.
619    /// ```
620    /// Whether or not `write_addr()` updates the high or low bits of the address is dependent
621    /// on the internal W latch, which is also modified by [`Ppu::write_scroll()`] and
622    /// [`Ppu::read_status()`]. Here is an example of interfering with W in the middle of writing
623    /// an address:
624    /// ```
625    /// # use nes_ppu::*;
626    /// let mut ppu = Ppu::new();   // W latch = 0
627    /// ppu.write_addr(0x20);       // W = 0, so sets the high bits of the address (now W = 1)
628    /// _ = ppu.read_status();      // clears W, now W = 0
629    /// ppu.write_addr(0x24);       // W = 0, so sets the high bits again (now W = 1)
630    /// ppu.write_addr(0x00);       // W = 1, so sets the low bits and sets V to T (now W = 0)
631    /// ```
632    ///
633    /// [Read more about the addr register on the NESdev Wiki.](https://www.nesdev.org/wiki/PPU_registers#PPUADDR)
634    ///
635    /// [Read more about how setting the address interacts with W, T, and V on the NESdev wiki.](https://www.nesdev.org/wiki/PPU_scrolling#PPU_internal_registers)
636    pub fn write_addr(&mut self, b: u8) {
637        let b = u16::from(b);
638        if !self.w_latch {
639            self.t_reg &= 0x00FF;
640            self.t_reg |= (b & 0x3F) << 8;
641        } else {
642            self.t_reg &= 0xFF00;
643            self.t_reg |= b;
644            self.v_reg = self.t_reg;
645        }
646
647        self.w_latch = !self.w_latch;
648    }
649
650    /// Sets the value of x or y scrolling relative to the current nametable selected in the
651    /// [ctrl](Ppu::write_ctrl) register.
652    ///
653    /// Calling `write_scroll()` the first time will set the x scroll, and calling it again
654    /// will set the y scroll. Thus, `write_scroll()` is usually called twice in succession:
655    /// ```
656    /// # use nes_ppu::*;
657    /// let mut ppu = Ppu::new();
658    /// ppu.write_scroll(100);      // sets the x scroll
659    /// ppu.write_scroll(50);       // sets the y scroll
660    /// ```
661    /// Note that x and y scroll values modify the value of the internal T register. Also,
662    /// nametables are only 240 pixels tall, so setting the y scroll to a number ≥240 will cause
663    /// garbage tiles to be displayed.
664    ///
665    /// Whether or not this function updates the x or y scroll is dependent on the internal W latch.
666    /// Both [`Ppu::write_addr()`] and [`Ppu::read_status()`] also affect this latch.
667    ///
668    /// Here is an example where W is interfered with between the two writes:
669    /// ```
670    /// # use nes_ppu::*;
671    /// let mut ppu = Ppu::new();   // W latch = 0
672    /// ppu.write_scroll(100);      // because W = 0, sets the x scroll (now W = 1)
673    /// _ = ppu.read_status();      // clears W, now W = 0
674    /// ppu.write_scroll(100);      // because W = 0, sets the x scroll again (now W = 1)
675    /// ppu.write_scroll(50);       // because W = 1, sets the y scroll (now W = 0 again)
676    ///```
677    ///
678    /// [Read more about the scroll register on the NESdev Wiki.](https://www.nesdev.org/wiki/PPU_registers#PPUSCROLL)
679    ///
680    /// [Read about how scroll interacts with W and T on the NESdev Wiki.](https://www.nesdev.org/wiki/PPU_scrolling#PPU_internal_registers)
681    pub fn write_scroll(&mut self, b: u8) {
682        let fine = b & 0b00000111;
683        let coarse = b >> 3;
684        if !self.w_latch {
685            self.fine_x_scroll = fine;
686            self.t_reg &= !COARSE_X_SCROLL_MASK | NAMETABLE_SELECT_MASK;
687            self.t_reg |= u16::from(coarse) << COARSE_X_OFFSET;
688        } else {
689            self.t_reg &= !Y_SCROLL_MASK | NAMETABLE_SELECT_MASK;
690            self.t_reg |= u16::from(fine) << FINE_Y_OFFSET;
691            self.t_reg |= u16::from(coarse) << COARSE_Y_OFFSET;
692        }
693
694        self.w_latch = !self.w_latch;
695    }
696
697    /// Sets the value of the ctrl register.
698    ///
699    /// This updates the current nametable, the sprite pattern table, the tile pattern table,
700    /// sprite sizes, and the automatic address increment. The nametable select bits are also
701    /// copied into the internal T register.
702    ///
703    /// Note that, to emulate the short-circuiting behavior of the NES when bit 6 of ctrl
704    /// is set, calling `write_ctrl()` with a value that has bit 6 set will induce a panic.
705    ///
706    /// [Read more about the ctrl register on the NESdev Wiki.](https://www.nesdev.org/wiki/PPU_registers#PPUCTRL)
707    pub fn write_ctrl(&mut self, b: u8) {
708        // TODO: bit 0 race condition
709        assert_eq!((b & PPUCTRL_MSS), 0, "PPUCTRL bit 6 set; system short");
710        self.ctrl = b;
711        let nametable_bits = u16::from(b & 0b00000011);
712        self.t_reg &= !NAMETABLE_SELECT_MASK;
713        self.t_reg |= nametable_bits << NAMETABLE_SELECT_OFFSET;
714    }
715
716    /// Sets the value of the mask register.
717    ///
718    /// This controls greyscale, sprite rendering, tile rendering, sprite
719    /// rendering in the leftmost 8 pixels, tile rendering in the leftmost 8 pixels,
720    /// and color emphasis.
721    ///
722    /// [Read more about the mask register on the NESdev Wiki.](https://www.nesdev.org/wiki/PPU_registers#PPUMASK)
723    pub fn write_mask(&mut self, b: u8) {
724        self.mask = b;
725    }
726
727    /// Get info about the current PPU status for the frame.
728    ///
729    /// Returns a bitset containing 3 flags. The remaining 5 bits have unspecified values.
730    /// The flags are as follows:
731    /// * Bit 5: Supposed to indicate that this frame, the PPU has evaluated a scanline where
732    /// more than 8 sprites would have to be drawn, resulting in dropout. This flag is bugged and
733    /// does not work intuitively, which this library emulates.
734    /// * Bit 6: Indicates that this frame, a non-transparent pixel of the sprite in OAM index 0
735    /// has overlapped with a non-transparent pixel of a tile.
736    /// * Bit 7: Indicates whether the PPU currently in vblank. This flag is cleared after calling
737    /// `read_status()`. Additionally, due to race conditions, this flag is bugged on actual NES
738    /// hardware. For authenticity, try to rely on [`Ppu::tick_to_next_vblank()`] instead.
739    ///
740    /// Calling `read_status()` also clears the W latch, which affects future calls to
741    /// [`Ppu::write_addr()`] and [`Ppu::write_scroll()`].
742    ///
743    /// [Read more about the status register on the NESdev Wiki.](https://www.nesdev.org/wiki/PPU_registers#PPUSTATUS)
744    ///
745    /// [Read more about the bugged sprite overflow flag on the NESdev Wiki.](https://www.nesdev.org/wiki/PPU_sprite_evaluation#Cause_of_the_sprite_overflow_bug)
746    ///
747    /// [Read more about the W latch on the NESdev Wiki.](https://www.nesdev.org/wiki/PPU_scrolling#PPU_internal_registers)
748    pub fn read_status(&mut self) -> u8 {
749        let ret = self.status;
750        self.status &= !PPUSTATUS_VBLANK;
751        self.w_latch = false;
752        ret
753    }
754
755    /// Reads the value at the currently stored address.
756    ///
757    /// This function reads the value at the location specified by the current address stored
758    /// in the PPU, but does not necessarily return that value. The address is usually
759    /// set up by calling [`Ppu::write_addr()`]. If the current address is in the
760    /// range 0x0000..=0x3EFF, then this function will return the value stored in an internal read
761    /// buffer, then perform the memory fetch through the [`Mapper::read()`] method of `mapper`,
762    /// and update the internal read buffer with the result.
763    ///
764    /// After the read is performed, the current address will automatically increment by either
765    /// 1 or 32, depending on the value in the [ctrl](Ppu::write_ctrl) register.
766    ///
767    /// ```
768    /// # use nes_ppu::*;
769    /// # fn example(mut ppu: Ppu, mut mapper: impl Mapper) {
770    /// ppu.write_ctrl(0); // set automatic increment to 1
771    /// // set address to 0x2000
772    /// ppu.write_addr(0x20);
773    /// ppu.write_addr(0x00);
774    ///
775    /// // Discard whatever is currently in the read buffer.
776    /// // Then update read buffer with value in 0x2000 and increment address to 0x2001.
777    /// _ = ppu.read_data(&mut mapper);
778    ///
779    /// // Set n to the value fetched from 0x2000 in the previous read.
780    /// // Then update read buffer with value in 0x2001 and increment address to 0x2002
781    /// let n = ppu.read_data(&mut mapper);
782    ///
783    /// // Set m to the value fetched from 0x2001 in the previous read.
784    /// // Then update read buffer with value in 0x2002 and increment address to 0x2003
785    /// let m = ppu.read_data(&mut mapper);
786    /// # }
787    /// ```
788    ///
789    /// If the address is in the range 0x3F00..=0x3FFF, then this function will directly
790    /// return the value stored within internal palette memory at that address. However, the
791    /// internal read buffer is still updated with the value obtained from [`Mapper::read()`]
792    /// on `mapper`.
793    /// ```
794    /// # use nes_ppu::*;
795    /// # fn example(mut ppu: Ppu, mut mapper: impl Mapper) {
796    /// ppu.write_ctrl(0); // set automatic increment to 1
797    /// // set address to 0x3F00
798    /// ppu.write_addr(0x3F);
799    /// ppu.write_addr(0x00);
800    ///
801    /// // Set n to value in palette memory at 0x3F00.
802    /// // Update read buffer to mapper.read(0x3F00), and increment address to 0x3F01.
803    /// let n = ppu.read_data(&mut mapper);
804    /// # }
805    /// ```
806    /// The address that is actually accessed is the low 14 bits of the internal V register.
807    /// The 15th bit is completely ignored by `read_data()`, and the automatic address increment
808    /// will not carry into the 15th bit.
809    ///
810    /// [Read more about the data register on the NESdev Wiki.](https://www.nesdev.org/wiki/PPU_registers#PPUDATA)
811    ///
812    /// [Read more about the internal V register on the NESdev Wiki.](https://www.nesdev.org/wiki/PPU_scrolling#PPU_internal_registers)
813    pub fn read_data<M: Mapper>(&mut self, mapper: &mut M) -> u8 {
814        let effective_addr = self.v_reg & 0x3FFF;
815        self.increment_address();
816        let data = core::mem::replace(&mut self.read_buffer, mapper.read(effective_addr));
817        self.access_palette_ram(effective_addr).unwrap_or(data)
818    }
819
820    /// Writes to the value at the currently stored address.
821    ///
822    /// This function will write `value` to the location specified by the current address
823    /// stored in the PPU. The address is usually set up using [`Ppu::write_addr()`].
824    /// If the current address is in the range 0x0000..=0x3EFF, the write will invoke
825    /// the [`Mapper::write()`] method of `mapper`. Otherwise, if the address is in the range
826    /// 0x3F00..=0x3FFF, the mapper will not be accessed and the write will instead be directed
827    /// into internal palette memory.
828    ///
829    /// After the write is performed, the current address will automatically increment by either
830    /// 1 or 32, depending on the value in the [ctrl](Ppu::write_ctrl) register.
831    ///
832    /// ```
833    /// # use nes_ppu::*;
834    /// # fn example(mut ppu: Ppu, mut mapper: impl Mapper) {
835    /// ppu.write_ctrl(0); // set automatic increment to 1
836    /// // set address to 0x2000
837    /// ppu.write_addr(0x20);
838    /// ppu.write_addr(0x00);
839    ///
840    /// ppu.write_data(&mut mapper, 10); // write 10 at address 0x2000
841    /// ppu.write_data(&mut mapper, 15); // write 15 at address 0x2001
842    /// # }
843    /// ```
844    /// The address that is actually accessed is the low 14 bits of the internal V register.
845    /// The 15th bit is completely ignored by `write_data()`, and the automatic address increment
846    /// will not carry into the 15th bit.
847    ///
848    /// [Read more about the data register on the NESdev Wiki.](https://www.nesdev.org/wiki/PPU_registers#PPUDATA)
849    ///
850    /// [Read more about the internal V register on the NESdev Wiki.](https://www.nesdev.org/wiki/PPU_scrolling#PPU_internal_registers)
851    pub fn write_data<M: Mapper>(&mut self, mapper: &mut M, value: u8) {
852        let effective_addr = self.v_reg & 0x3FFF;
853        self.increment_address();
854        if let Some(color) = self.access_palette_ram_mut(effective_addr) {
855            *color = value & 0x3F;
856        } else {
857            mapper.write(effective_addr, value);
858        }
859    }
860
861    /// Writes a sequence of bytes starting at the current address.
862    ///
863    /// Repeatedly calls [`Ppu::write_data()`] for each element of `values`. Because `write_data()`
864    /// automatically increments the current address, this will write each value into sequential
865    /// memory locations. Note that depending on the value in the [ctrl](Ppu::write_ctrl) register,
866    /// the values may be written consecutively or spaced apart by 32 bytes each.
867    ///
868    /// This is a convenience utility not actually present when programming for the NES.
869    /// ```
870    /// # use nes_ppu::*;
871    /// # fn example(mut ppu: Ppu, mut mapper: impl Mapper) {
872    /// ppu.write_ctrl(0b100); // set automatic increment to 32
873    /// // set address to 0x2000
874    /// ppu.write_addr(0x20);
875    /// ppu.write_addr(0x00);
876    ///
877    /// // Write to addresses 0x2000, 0x2020, 0x2040, 0x2060, and 0x2080.
878    /// ppu.write_data_iter(&mut mapper, [1, 2, 3, 4, 5]);
879    /// # }
880    /// ```
881    pub fn write_data_iter<M: Mapper, I>(&mut self, mapper: &mut M, values: I)
882    where
883        I: IntoIterator<Item = u8>,
884    {
885        for value in values {
886            self.write_data(mapper, value);
887        }
888    }
889
890    /// Sets the current oam address.
891    ///
892    /// This function sets the PPU's current 8-bit address into [OAM](Ppu#oam).
893    /// Since this address is used internally during rendering to evaluate which sprites are
894    /// visible, writing to the OAM address in the middle of rendering can corrupt which sprites are
895    /// drawn. The current OAM address is also automatically reset to 0 towards the end of vblank.
896    ///
897    /// Due to internal hardware issues, writing to the OAM address can corrupt the
898    /// contents of OAM itself, which this library emulates. As a result, this function is useless
899    /// in a majority of cases.
900    ///
901    /// This function is most commonly used in conjunction with [`Ppu::write_oam_data()`] or
902    /// [`Ppu::read_oam_data()`] to access the contents of OAM. However, due to the aforementioned
903    /// issue where calling this function corrupts OAM, this is rarely practical.
904    /// ```
905    /// # use nes_ppu::*;
906    /// # fn example(mut ppu: Ppu) {
907    /// ppu.write_oam_addr(0x80);
908    /// ppu.write_oam_data(0xFF); // write value 0xFF to address 0x80
909    /// let n = ppu.read_oam_data(); // read value at address 0x81
910    /// # }
911    /// ```
912    /// In almost all cases, to write to OAM, it is better to use [`Ppu::set_oam()`] or
913    /// [`Ppu::set_oam_bytes()`] instead.
914    ///
915    /// [Read about the OAM addr register on the NESdev Wiki.](https://www.nesdev.org/wiki/PPU_registers#OAMADDR)
916    ///
917    /// [Read about the OAM memory layout on the NESdev Wiki.](https://www.nesdev.org/wiki/PPU_OAM)
918    pub fn write_oam_addr(&mut self, addr: u8) {
919        // Corrupt OAM unconditionally.
920        // While these corruptions don't happen 100% of the time on real hardware,
921        // this should force users to be careful.
922        for i in 0..8 {
923            let src_index = (0x20 + i) as usize;
924            let dest_index = self.oam_evaluation_index.wrapping_add(i) as usize;
925            let val = self.oam_bytes()[src_index];
926            self.oam_bytes_mut()[dest_index] = val;
927        }
928
929        self.oam_evaluation_index = addr;
930    }
931
932    /// Reads the value pointed to by the current OAM address.
933    ///
934    /// If the PPU is in vblank or rendering is disabled, then this function returns the value
935    /// referred to by the currently stored OAM address. Otherwise, the PPU is in a busy state
936    /// and this function will return the value most recently loaded by the PPU during
937    /// its internal sprite processing routine. Unlike [`Ppu::write_oam_data()`], this function
938    /// does not automatically increment the OAM address.
939    ///
940    /// ```
941    /// # use nes_ppu::*;
942    /// # fn example(mut ppu: Ppu) {
943    /// // assume ppu is in vblank
944    ///
945    /// // set current address to 0x55 (possibly causing OAM corruption)
946    /// ppu.write_oam_addr(0x55);
947    /// // read the value in OAM at address 0x55
948    /// let n = ppu.read_oam_data();
949    /// # }
950    /// ```
951    ///
952    /// ```
953    /// # use nes_ppu::*;
954    /// # fn example(mut ppu: Ppu) {
955    /// // assume ppu is currently rendering
956    ///
957    /// // snoop the value the PPU most recently read from OAM during internal sprite processing
958    /// let n = ppu.read_oam_data();
959    /// # }
960    /// ```
961    ///
962    /// See also: [`Ppu::write_oam_addr()`], [`Ppu::write_oam_data()`].
963    ///
964    /// [Read more about the OAM data register on the NESdev Wiki.](https://www.nesdev.org/wiki/PPU_registers#OAMDATA)
965    pub fn read_oam_data(&mut self) -> u8 {
966        if self.is_rendering() {
967            self.oam_mdr
968        } else {
969            self.cur_oam_byte()
970        }
971    }
972
973    /// Writes data into OAM at the current address.
974    ///
975    /// If the PPU is in vblank or rendering is disabled, this function writes `value` into OAM
976    /// at the current OAM address, then increments the current address by 1. This increment may
977    /// cause an overflow from 0xFF back to address 0x00.
978    /// If the PPU is currently rendering, then no write is performed, and the current OAM address
979    /// is instead incremented by 4.
980    /// ```
981    /// # use nes_ppu::*;
982    /// # fn example(mut ppu: Ppu) {
983    /// // assume ppu is in vblank
984    ///
985    /// // set current address to 0x55 (possibly causing OAM corruption)
986    /// ppu.write_oam_addr(0x55);
987    /// // set the value in OAM at address 0x55 to 0xAA
988    /// ppu.write_oam_data(0xAA);
989    /// // set the value in OAM at address 0x56 to 0xBB
990    /// ppu.write_oam_data(0xBB);
991    /// # }
992    /// ```
993    ///
994    /// ```
995    /// # use nes_ppu::*;
996    /// # fn example(mut ppu: Ppu) {
997    /// // assume ppu is currently rendering
998    ///
999    /// // perform no write and increment OAM address by 4 (this will interfere with sprite processing)
1000    /// ppu.write_oam_data(0xAA);
1001    /// # }
1002    /// ```
1003    ///
1004    /// See also: [`Ppu::write_oam_addr()`], [`Ppu::read_oam_data()`].
1005    ///
1006    /// [Read more about the OAM data register on the NESdev Wiki.](https://www.nesdev.org/wiki/PPU_registers#OAMDATA)
1007    pub fn write_oam_data(&mut self, value: u8) {
1008        if self.is_rendering() {
1009            self.increment_oam_evaluation_index(SPRITE_SIZE);
1010        } else {
1011            let index = self.oam_evaluation_index as usize;
1012            self.oam_bytes_mut()[index] = value;
1013            self.increment_oam_evaluation_index(1);
1014        }
1015    }
1016
1017    fn access_palette_ram(&mut self, addr: u16) -> Option<u8> {
1018        if addr < 0x3F00 {
1019            return None;
1020        }
1021
1022        let color_index = palette_address_color_index(addr);
1023        let palette_index = palette_address_palette_index(addr);
1024        Some(if color_index == 0 {
1025            self.zero_colors[palette_index]
1026        } else if is_palette_address_tile_color(addr) {
1027            self.tile_palettes[palette_index].colors[color_index - 1]
1028        } else {
1029            self.sprite_palettes[palette_index].colors[color_index - 1]
1030        })
1031    }
1032
1033    fn access_palette_ram_mut(&mut self, addr: u16) -> Option<&mut u8> {
1034        if addr < 0x3F00 {
1035            return None;
1036        }
1037
1038        let color_index = palette_address_color_index(addr);
1039        let palette_index = palette_address_palette_index(addr);
1040        Some(if color_index == 0 {
1041            &mut self.zero_colors[palette_index]
1042        } else if is_palette_address_tile_color(addr) {
1043            &mut self.tile_palettes[palette_index].colors[color_index - 1]
1044        } else {
1045            &mut self.sprite_palettes[palette_index].colors[color_index - 1]
1046        })
1047    }
1048
1049    fn increment_address(&mut self) {
1050        let new_addr = self.v_reg
1051            + match self.ctrl & PPUCTRL_ADDR_INC {
1052                0 => 1,
1053                _ => 32,
1054            };
1055
1056        self.v_reg ^= new_addr;
1057        self.v_reg &= 0xC000;
1058        self.v_reg ^= new_addr;
1059    }
1060
1061    fn sprite_height(&self) -> u8 {
1062        match self.ctrl & PPUCTRL_SPRITE_SIZE {
1063            0 => 8,
1064            _ => 16,
1065        }
1066    }
1067
1068    fn oam_bytes(&self) -> &[u8; 256] {
1069        <&[u8; 256]>::try_from(bytemuck::bytes_of(&self.oam)).unwrap()
1070    }
1071
1072    fn oam_bytes_mut(&mut self) -> &mut [u8; 256] {
1073        <&mut [u8; 256]>::try_from(bytemuck::bytes_of_mut(&mut self.oam)).unwrap()
1074    }
1075
1076    fn secondary_oam_bytes(&self) -> &[u8; 32] {
1077        <&[u8; 32]>::try_from(bytemuck::bytes_of(&self.secondary_oam)).unwrap()
1078    }
1079
1080    fn secondary_oam_bytes_mut(&mut self) -> &mut [u8; 32] {
1081        <&mut [u8; 32]>::try_from(bytemuck::bytes_of_mut(&mut self.secondary_oam)).unwrap()
1082    }
1083
1084    fn sprite_pattern_table_base(&self) -> u16 {
1085        pattern_table_base(self.ctrl & PPUCTRL_SPRITE_PATTERN_TABLE)
1086    }
1087
1088    fn tile_pattern_table_base(&self) -> u16 {
1089        pattern_table_base(self.ctrl & PPUCTRL_TILE_PATTERN_TABLE)
1090    }
1091
1092    fn increment_coarse_x(&mut self) {
1093        let coarse_x = (self.v_reg & X_SCROLL_MASK).wrapping_add(!X_SCROLL_MASK + 1);
1094        self.v_reg ^= coarse_x;
1095        self.v_reg &= !X_SCROLL_MASK;
1096        self.v_reg ^= coarse_x;
1097    }
1098
1099    fn increment_y(&mut self) {
1100        if self.v_reg & FINE_Y_SCROLL_MASK != FINE_Y_SCROLL_MASK {
1101            self.v_reg += 1 << FINE_Y_OFFSET;
1102        } else {
1103            self.v_reg &= !FINE_Y_SCROLL_MASK;
1104            let coarse_y = (self.v_reg & COARSE_Y_SCROLL_MASK) >> COARSE_Y_OFFSET;
1105            self.v_reg = match coarse_y {
1106                29 => (self.v_reg & !COARSE_Y_SCROLL_MASK) ^ NAMETABLE_Y_SELECT_MASK,
1107                31 => self.v_reg & !COARSE_Y_SCROLL_MASK,
1108                _ => self.v_reg + (1 << COARSE_Y_OFFSET),
1109            }
1110        }
1111    }
1112
1113    fn nametable_address(&self) -> u16 {
1114        NAMETABLE_BASE_ADDRESS | (self.v_reg & 0b000_11_11111_11111)
1115    }
1116
1117    fn attribute_table_address(&self) -> u16 {
1118        // move high 3 bits of coarse x scroll into bits 0-2
1119        let attr_x_component = (self.v_reg >> 2) & 0b000_00_0000_000111;
1120        // move high 3 bits of coarse y scroll into bits 3-5
1121        let attr_y_component = (self.v_reg >> 4) & 0b000_00_0000_111000;
1122        // nametable base address + nametable select + attribute table offset
1123        NAMETABLE_BASE_ADDRESS
1124            | (self.v_reg & NAMETABLE_SELECT_MASK)
1125            | ATTRIBUTE_TABLE_OFFSET
1126            | attr_x_component
1127            | attr_y_component
1128    }
1129
1130    fn fine_y_scroll(&self) -> u8 {
1131        (self.v_reg >> FINE_Y_OFFSET) as u8
1132    }
1133
1134    fn tile_pattern_address(&self, index: u8, plane: BitPlane) -> u16 {
1135        let tile_offset = u16::from(index) * 16;
1136        let row_offset = u16::from(self.fine_y_scroll());
1137        self.tile_pattern_table_base() + tile_offset + row_offset + plane as u16
1138    }
1139
1140    fn sprite_pattern_address(&self, sprite: Sprite, plane: BitPlane) -> u16 {
1141        // wrapping arithmetic so that empty Secondary OAM slots with unexpected Y values
1142        // don't trigger unexpected overflows.
1143        let distance_from_sprite_top = if sprite.attributes & SPRITE_FLIP_Y == 0 {
1144            self.cur_scanline.wrapping_sub(u16::from(sprite.y))
1145        } else {
1146            (u16::from(sprite.y) + u16::from(self.sprite_height() - 1))
1147                .wrapping_sub(self.cur_scanline)
1148        };
1149
1150        if self.ctrl & PPUCTRL_SPRITE_SIZE == 0 {
1151            let tile_offset = u16::from(sprite.pattern_index) * 16;
1152            let row_offset = distance_from_sprite_top & 0b111;
1153            self.sprite_pattern_table_base() + tile_offset + row_offset + plane as u16
1154        } else {
1155            let table_base = pattern_table_base(sprite.pattern_index & 1);
1156            let tile_pair_offset = u16::from(sprite.pattern_index & 0b11111110) * 16;
1157            let tile_select = (distance_from_sprite_top & 0b1000) << 1;
1158            let row_offset = distance_from_sprite_top & 0b111;
1159            table_base + tile_pair_offset + tile_select + row_offset + plane as u16
1160        }
1161    }
1162
1163    fn flush_horizontal_scroll(&mut self) {
1164        self.v_reg ^= self.t_reg;
1165        self.v_reg &= Y_SCROLL_MASK;
1166        self.v_reg ^= self.t_reg;
1167    }
1168
1169    fn flush_vertical_scroll(&mut self) {
1170        self.v_reg ^= self.t_reg;
1171        self.v_reg &= X_SCROLL_MASK;
1172        self.v_reg ^= self.t_reg;
1173    }
1174
1175    fn tile_attribute_bits_from_temp(&self) -> u8 {
1176        let x_bit = (self.v_reg >> 4) & 0b100;
1177        let y_bit = self.v_reg & 0b010;
1178        self.temp_tile_attribute >> (x_bit | y_bit) as u8
1179    }
1180
1181    fn are_sprites_visible(&self) -> bool {
1182        let in_column_0 = self.cur_dot < 8;
1183        (self.mask & PPUMASK_SHOW_SPRITES != 0)
1184            && !(in_column_0 && self.mask & PPUMASK_SHOW_COLUMN_0_SPRITES == 0)
1185    }
1186
1187    fn are_tiles_visible(&self) -> bool {
1188        let in_column_0 = self.cur_dot < 8;
1189        (self.mask & PPUMASK_SHOW_TILES != 0)
1190            && !(in_column_0 && self.mask & PPUMASK_SHOW_COLUMN_0_TILES == 0)
1191    }
1192
1193    fn is_rendering_enabled(&self) -> bool {
1194        self.mask & PPUMASK_SHOW_TILES != 0 || self.mask & PPUMASK_SHOW_SPRITES != 0
1195    }
1196
1197    fn is_rendering(&self) -> bool {
1198        self.is_rendering_enabled() && (self.cur_scanline < 240 || self.cur_scanline == 261)
1199    }
1200
1201    fn greyscale_mask(&self) -> u8 {
1202        if self.mask & PPUMASK_GREYSCALE == 0 {
1203            0x3F
1204        } else {
1205            0x30
1206        }
1207    }
1208
1209    fn background_color(&self) -> Color {
1210        self.zero_colors[0]
1211    }
1212
1213    fn calculate_cur_pixel(&self) -> PixelInfo {
1214        let tile_attribute = (self.tile_attribute_shift_reg >> (2 * self.fine_x_scroll)) & 0b11;
1215        let tile_color_index = if self.are_tiles_visible() {
1216            (self.tile_pattern_shift_reg >> (2 * self.fine_x_scroll)) & 0b11
1217        } else {
1218            0
1219        };
1220        let tile_attribute = tile_attribute as usize;
1221        let tile_color_index = tile_color_index as usize;
1222
1223        let (visible_sprite_index, visible_sprite) = self
1224            .sprite_render_states
1225            .iter()
1226            .enumerate()
1227            .filter(|&(_, s)| {
1228                self.are_sprites_visible() && s.x_counter == 0 && s.pattern_shift_reg & 0b11 != 0
1229            })
1230            .next()
1231            .unzip();
1232
1233        let color = match (visible_sprite, tile_color_index) {
1234            (Some(s), _) if tile_color_index == 0 || s.attributes & SPRITE_PRIORITY == 0 => {
1235                let sprite_palette_index = (s.attributes & SPRITE_PALETTE_MASK) as usize;
1236                let sprite_color_index = (s.pattern_shift_reg & 0b11) as usize;
1237                self.sprite_palettes[sprite_palette_index].colors[sprite_color_index - 1]
1238            }
1239            (_, 0) => self.background_color(),
1240            (_, _) => self.tile_palettes[tile_attribute].colors[tile_color_index - 1],
1241        };
1242        let sprite_0_hit = self.sprite_0_cur_line
1243            && visible_sprite_index == Some(0)
1244            && tile_color_index != 0
1245            && self.cur_dot < 255;
1246
1247        PixelInfo {
1248            color,
1249            sprite_0_hit,
1250        }
1251    }
1252
1253    fn output_pixel<B: PixelBuffer>(&self, buffer: &mut B, color: Color) {
1254        let color = color & self.greyscale_mask();
1255        let emphasis = ColorEmphasis {
1256            bits: self.mask >> 5,
1257        };
1258        buffer.set_color(self.cur_dot as u8, self.cur_scanline as u8, color, emphasis);
1259    }
1260
1261    fn tick_tile_pipeline<M: Mapper>(&mut self, mapper: &mut M) {
1262        match (self.cur_dot - 1) & 0b111 {
1263            0b000 => {
1264                self.temp_tile_pattern_index = mapper.read(self.nametable_address());
1265            }
1266            0b010 => {
1267                self.temp_tile_attribute = mapper.read(self.attribute_table_address());
1268            }
1269            0b100 => {
1270                let tile_pattern = self.temp_tile_pattern_index;
1271                self.temp_tile_pattern_lo = mapper
1272                    .read(self.tile_pattern_address(tile_pattern, BitPlane::Lo))
1273                    .reverse_bits();
1274            }
1275            0b110 => {
1276                let tile_pattern = self.temp_tile_pattern_index;
1277                self.temp_tile_pattern_hi = mapper
1278                    .read(self.tile_pattern_address(tile_pattern, BitPlane::Hi))
1279                    .reverse_bits();
1280            }
1281            0b111 => {
1282                let packed_pattern =
1283                    morton_encode_16(self.temp_tile_pattern_lo, self.temp_tile_pattern_hi);
1284                self.tile_pattern_shift_reg |= u32::from(packed_pattern) << 16;
1285                self.tile_attribute_latch = self.tile_attribute_bits_from_temp();
1286                self.increment_coarse_x();
1287            }
1288            _ => {}
1289        }
1290    }
1291
1292    fn cur_oam_byte(&self) -> u8 {
1293        self.oam_bytes()[self.oam_evaluation_index as usize]
1294    }
1295
1296    fn cur_secondary_oam_byte_mut(&mut self) -> &mut u8 {
1297        let i = self.secondary_oam_evaluation_index as usize;
1298        &mut self.secondary_oam_bytes_mut()[i]
1299    }
1300
1301    fn is_secondary_oam_full(&self) -> bool {
1302        self.secondary_oam_evaluation_index as usize == self.secondary_oam_bytes().len()
1303    }
1304
1305    fn increment_oam_evaluation_index(&mut self, n: u8) {
1306        (
1307            self.oam_evaluation_index,
1308            self.oam_evaluation_index_overflow,
1309        ) = self.oam_evaluation_index.overflowing_add(n);
1310    }
1311
1312    fn is_sprite_y_in_range(&self, y: u8) -> bool {
1313        let sprite_height = self.sprite_height();
1314        let cur_y = self.cur_scanline as u8;
1315        y <= cur_y && (cur_y - y) < sprite_height
1316    }
1317
1318    fn tick_clear_secondary_oam(&mut self) {
1319        if self.cur_dot & 1 == 0 {
1320            // writes only occur on even cycles, starting on cycle 1.
1321            // Therefore, cycle 2 is the first cycle where a write occurs.
1322            // So we can calculate the index by dividing cur_dot by 2 and subtracting 1.
1323            let i = ((self.cur_dot - 1) >> 1) as usize;
1324            self.secondary_oam_bytes_mut()[i] = self.oam_mdr;
1325        } else {
1326            self.oam_mdr = 0xFF;
1327        }
1328    }
1329
1330    fn complete_sprite_evaluation(&mut self) -> SpriteEvaluationState {
1331        self.oam_evaluation_index &= !0b11;
1332        SpriteEvaluationState::Complete
1333    }
1334
1335    fn tick_sprite_evaluation_check_normal(&mut self) {
1336        let sprite_y = self.oam_mdr;
1337        let sprite_in_range = self.is_sprite_y_in_range(sprite_y);
1338        *self.cur_secondary_oam_byte_mut() = sprite_y;
1339        if sprite_in_range {
1340            self.sprite_0_next_line |= self.oam_evaluation_index == 0;
1341            self.sprite_evaluation_state = SpriteEvaluationState::CopyingNormal(2);
1342            self.secondary_oam_evaluation_index += 1;
1343            self.increment_oam_evaluation_index(1);
1344        } else {
1345            self.increment_oam_evaluation_index(SPRITE_SIZE);
1346            if self.oam_evaluation_index_overflow {
1347                self.sprite_evaluation_state = self.complete_sprite_evaluation();
1348            }
1349        }
1350    }
1351
1352    fn tick_sprite_evaluation_check_overflow(&mut self) {
1353        let sprite_y = self.oam_mdr;
1354        let sprite_in_range = self.is_sprite_y_in_range(sprite_y);
1355        if sprite_in_range {
1356            self.status |= PPUSTATUS_OVERFLOW;
1357            self.sprite_evaluation_state = SpriteEvaluationState::CopyingOverflow(2);
1358            self.increment_oam_evaluation_index(1);
1359        } else {
1360            // emulate overflow increment bug
1361            let inc = 0b101 - (((self.oam_evaluation_index & 0b11) + 1) & 0b100);
1362            self.increment_oam_evaluation_index(inc);
1363            if self.oam_evaluation_index_overflow {
1364                self.sprite_evaluation_state = self.complete_sprite_evaluation();
1365            }
1366        }
1367    }
1368
1369    fn tick_sprite_evaluation_copy_normal(&mut self, remaining: u8) {
1370        if !self.is_secondary_oam_full() {
1371            *self.cur_secondary_oam_byte_mut() = self.oam_mdr;
1372            self.secondary_oam_evaluation_index += 1;
1373        }
1374        self.increment_oam_evaluation_index(1);
1375
1376        self.sprite_evaluation_state = if remaining > 0 {
1377            SpriteEvaluationState::CopyingNormal(remaining - 1)
1378        } else if self.oam_evaluation_index_overflow {
1379            self.complete_sprite_evaluation()
1380        } else if self.is_secondary_oam_full() {
1381            SpriteEvaluationState::CheckingOverflow
1382        } else {
1383            SpriteEvaluationState::CheckingNormal
1384        };
1385    }
1386
1387    fn tick_sprite_evaluation_copy_overflow(&mut self, remaining: u8) {
1388        self.increment_oam_evaluation_index(1);
1389
1390        self.sprite_evaluation_state = if remaining > 0 {
1391            SpriteEvaluationState::CopyingOverflow(remaining - 1)
1392        } else {
1393            self.complete_sprite_evaluation()
1394        };
1395    }
1396
1397    fn tick_sprite_evaluation_complete(&mut self) {
1398        self.increment_oam_evaluation_index(SPRITE_SIZE);
1399    }
1400
1401    fn tick_sprite_evaluation(&mut self) {
1402        use SpriteEvaluationState::*;
1403        if self.cur_dot & 1 == 0 {
1404            match self.sprite_evaluation_state {
1405                CheckingNormal => self.tick_sprite_evaluation_check_normal(),
1406                CheckingOverflow => self.tick_sprite_evaluation_check_overflow(),
1407                CopyingNormal(remaining) => self.tick_sprite_evaluation_copy_normal(remaining),
1408                CopyingOverflow(remaining) => self.tick_sprite_evaluation_copy_overflow(remaining),
1409                Complete => self.tick_sprite_evaluation_complete(),
1410            }
1411        } else {
1412            self.oam_mdr = self.cur_oam_byte();
1413        }
1414    }
1415
1416    fn fetch_sprite_pattern<M: Mapper>(
1417        &self,
1418        mapper: &mut M,
1419        sprite: Sprite,
1420        plane: BitPlane,
1421    ) -> u8 {
1422        let addr = self.sprite_pattern_address(sprite, plane);
1423        let pattern = mapper.read(addr);
1424
1425        if !self.is_sprite_y_in_range(sprite.y) {
1426            0x00
1427        } else if sprite.attributes & SPRITE_FLIP_X == 0 {
1428            pattern.reverse_bits()
1429        } else {
1430            pattern
1431        }
1432    }
1433
1434    fn tick_sprite_fetches<M: Mapper>(&mut self, mapper: &mut M) {
1435        // each sprite fetch takes a total of 8 cycles
1436        // sprite fetches begin on cycle 257, so subtract 1 and shift down 3
1437        // to get index of current sprite.
1438        let sprite_index = (((self.cur_dot - 1) >> 3) & 0b111) as usize;
1439        let sprite = self.secondary_oam[sprite_index];
1440
1441        // default read value used for cycles 4-8
1442        self.oam_mdr = sprite.x;
1443
1444        match (self.cur_dot - 1) & 0b111 {
1445            0b000 => {
1446                // dummy read
1447                mapper.read(self.nametable_address());
1448                self.oam_mdr = sprite.y;
1449            }
1450            0b001 => {
1451                self.oam_mdr = sprite.pattern_index;
1452            }
1453            0b010 => {
1454                // dummy read
1455                mapper.read(self.nametable_address());
1456                self.oam_mdr = sprite.attributes;
1457                self.sprite_render_states[sprite_index].attributes = sprite.attributes;
1458            }
1459            0b011 => {
1460                self.sprite_render_states[sprite_index].x_counter = sprite.x;
1461            }
1462            0b100 => {
1463                self.temp_sprite_pattern_lo =
1464                    self.fetch_sprite_pattern(mapper, sprite, BitPlane::Lo);
1465            }
1466            0b110 => {
1467                let pattern_hi = self.fetch_sprite_pattern(mapper, sprite, BitPlane::Hi);
1468                let pattern = morton_encode_16(self.temp_sprite_pattern_lo, pattern_hi);
1469                self.sprite_render_states[sprite_index].pattern_shift_reg = pattern;
1470            }
1471            _ => {}
1472        }
1473    }
1474
1475    fn tick_sprites<M: Mapper>(&mut self, mapper: &mut M) {
1476        match self.cur_dot {
1477            0 => {
1478                self.secondary_oam_evaluation_index = 0;
1479                self.sprite_evaluation_state = SpriteEvaluationState::CheckingNormal;
1480                self.oam_evaluation_index_overflow = false;
1481                self.sprite_0_cur_line = self.sprite_0_next_line;
1482                self.sprite_0_next_line = false;
1483            }
1484            1..=64 => {
1485                self.tick_clear_secondary_oam();
1486            }
1487            65..=256 => {
1488                self.tick_sprite_evaluation();
1489            }
1490            257..=320 => {
1491                self.oam_evaluation_index = 0;
1492                self.tick_sprite_fetches(mapper);
1493            }
1494            321..=340 => {
1495                self.oam_mdr = self.secondary_oam_bytes()[0];
1496            }
1497            _ => {}
1498        }
1499    }
1500
1501    fn update_tile_shift_regs(&mut self) {
1502        self.tile_pattern_shift_reg >>= 2;
1503        self.tile_attribute_shift_reg >>= 2;
1504        self.tile_attribute_shift_reg |= u16::from(self.tile_attribute_latch) << 14;
1505    }
1506
1507    fn update_sprite_counters_and_shift_regs(&mut self) {
1508        for render_state in &mut self.sprite_render_states {
1509            if render_state.x_counter > 0 {
1510                render_state.x_counter -= 1;
1511            } else {
1512                render_state.pattern_shift_reg >>= 2;
1513            }
1514        }
1515    }
1516
1517    fn tick_render<M: Mapper, B: PixelBuffer>(
1518        &mut self,
1519        mapper: &mut M,
1520        buffer: &mut B,
1521        mode: RenderMode,
1522    ) {
1523        if !self.is_rendering_enabled() {
1524            if self.cur_dot < 256 && mode == RenderMode::Normal {
1525                let color = self
1526                    .access_palette_ram(self.v_reg & 0x3FFF)
1527                    .unwrap_or(self.background_color());
1528                self.output_pixel(buffer, color);
1529            }
1530            return;
1531        }
1532
1533        if matches!(self.cur_dot, 1..=256 | 321..=336) {
1534            self.update_tile_shift_regs();
1535            self.tick_tile_pipeline(mapper);
1536        }
1537
1538        if mode == RenderMode::Normal {
1539            self.tick_sprites(mapper);
1540            if self.cur_dot < 256 {
1541                let pixel_info = self.calculate_cur_pixel();
1542                if pixel_info.sprite_0_hit {
1543                    self.status |= PPUSTATUS_SPRITE_0_HIT;
1544                }
1545                self.output_pixel(buffer, pixel_info.color);
1546                self.update_sprite_counters_and_shift_regs();
1547            }
1548        } else if self.cur_dot > 256 {
1549            // garbage sprite fetching during pre-render scanline
1550            self.tick_sprites(mapper);
1551        }
1552
1553        match self.cur_dot {
1554            1 if mode == RenderMode::PreRender => {
1555                self.status &= !(PPUSTATUS_SPRITE_0_HIT | PPUSTATUS_OVERFLOW | PPUSTATUS_VBLANK);
1556            }
1557            256 => {
1558                self.increment_y();
1559            }
1560            257 => {
1561                self.flush_horizontal_scroll();
1562            }
1563            280..=304 if mode == RenderMode::PreRender => {
1564                self.flush_vertical_scroll();
1565            }
1566            337 | 339 => {
1567                // dummy reads
1568                mapper.read(self.nametable_address());
1569            }
1570            _ => {}
1571        }
1572    }
1573
1574    fn tick_vblank(&mut self) {
1575        if self.cur_scanline == 241 && self.cur_dot == 1 {
1576            self.status |= PPUSTATUS_VBLANK;
1577        }
1578    }
1579}