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/// 
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}