1use crate::cartridge::TVSystem;
2use crate::savestate::{SaveStateError, StateReader, StateWriter};
3
4pub const FRAME_WIDTH: usize = 256;
5pub const FRAME_HEIGHT: usize = 240;
6const VISIBLE_FRAME_PIXELS: usize = FRAME_WIDTH * FRAME_HEIGHT;
7
8const STATUS_SPRITE_OVERFLOW: u8 = 0x20;
9const STATUS_SPRITE_ZERO_HIT: u8 = 0x40;
10const STATUS_VBLANK: u8 = 0x80;
11const MASK_GRAYSCALE: u8 = 0x01;
12const MASK_SHOW_BG_LEFTMOST: u8 = 0x02;
13const MASK_SHOW_SPRITES_LEFTMOST: u8 = 0x04;
14const MASK_SHOW_BG: u8 = 0x08;
15const MASK_SHOW_SPRITES: u8 = 0x10;
16const CTRL_SPRITE_TABLE: u8 = 0x08;
17const CTRL_BG_TABLE: u8 = 0x10;
18const CTRL_VRAM_INCREMENT: u8 = 0x04;
19const CTRL_SPRITE_SIZE: u8 = 0x20;
20const CTRL_NMI_ENABLE: u8 = 0x80;
21const DOTS_PER_SCANLINE: u16 = 341;
22const VISIBLE_SCANLINES: i16 = 240;
23
24#[cfg(test)]
25const NES_RGB_PALETTE: [[u8; 3]; 64] = [
26 [84, 84, 84],
27 [0, 30, 116],
28 [8, 16, 144],
29 [48, 0, 136],
30 [68, 0, 100],
31 [92, 0, 48],
32 [84, 4, 0],
33 [60, 24, 0],
34 [32, 42, 0],
35 [8, 58, 0],
36 [0, 64, 0],
37 [0, 60, 0],
38 [0, 50, 60],
39 [0, 0, 0],
40 [0, 0, 0],
41 [0, 0, 0],
42 [152, 150, 152],
43 [8, 76, 196],
44 [48, 50, 236],
45 [92, 30, 228],
46 [136, 20, 176],
47 [160, 20, 100],
48 [152, 34, 32],
49 [120, 60, 0],
50 [84, 90, 0],
51 [40, 114, 0],
52 [8, 124, 0],
53 [0, 118, 40],
54 [0, 102, 120],
55 [0, 0, 0],
56 [0, 0, 0],
57 [0, 0, 0],
58 [236, 238, 236],
59 [76, 154, 236],
60 [120, 124, 236],
61 [176, 98, 236],
62 [228, 84, 236],
63 [236, 88, 180],
64 [236, 106, 100],
65 [212, 136, 32],
66 [160, 170, 0],
67 [116, 196, 0],
68 [76, 208, 32],
69 [56, 204, 108],
70 [56, 180, 204],
71 [60, 60, 60],
72 [0, 0, 0],
73 [0, 0, 0],
74 [236, 238, 236],
75 [168, 204, 236],
76 [188, 188, 236],
77 [212, 178, 236],
78 [236, 174, 236],
79 [236, 174, 212],
80 [236, 180, 176],
81 [228, 196, 144],
82 [204, 210, 120],
83 [180, 222, 120],
84 [168, 226, 144],
85 [152, 226, 180],
86 [160, 214, 228],
87 [160, 162, 160],
88 [0, 0, 0],
89 [0, 0, 0],
90];
91
92pub trait PPUBus {
93 fn ppu_read(&mut self, addr: u16) -> u8;
94 fn ppu_write(&mut self, addr: u16, data: u8);
95 fn check_a12(&mut self, _addr: u16, _ppu_cycle: u64) {}
96 fn notify_scanline(&mut self, _scanline: i16, _rendering_on: bool) {}
97 fn set_ppu_sprite_phase(&mut self, _sprite_phase: bool) {}
98}
99
100#[derive(Clone, Copy)]
101struct SpriteRenderData {
102 tile_id: u8,
103 row: u8,
104 x: u8,
105 attributes: u8,
106 pattern_lo: u8,
107 pattern_hi: u8,
108 sprite_zero: bool,
109}
110
111impl Default for SpriteRenderData {
112 fn default() -> Self {
113 Self {
114 tile_id: 0xFF,
115 row: 0,
116 x: 0xFF,
117 attributes: 0xFF,
118 pattern_lo: 0,
119 pattern_hi: 0,
120 sprite_zero: false,
121 }
122 }
123}
124
125pub struct PPU {
126 scanline: i16,
127 cycles: u16,
128 frame: u64,
129
130 oam: [u8; 256],
131 oam_addr: u8,
132
133 ctrl: u8,
134 mask: u8,
135 status: u8,
136 open_bus: u8,
137 vram_addr: u16,
138 temp_vram_addr: u16,
139 fine_x: u8,
140 write_latch: bool,
141 read_buffer: u8,
142 odd_frame: bool,
143 next_tile_id: u8,
144 next_tile_attr: u8,
145 next_tile_lsb: u8,
146 next_tile_msb: u8,
147 bg_pattern_shift_lo: u16,
148 bg_pattern_shift_hi: u16,
149 bg_attr_shift_lo: u16,
150 bg_attr_shift_hi: u16,
151
152 tv_system: TVSystem,
154 num_scanlines: i16,
155 vblank_lines: i16,
156
157 loopy_v: u16,
158 loopy_t: u16,
159 bit_map: [u8; 0xF000],
160 bg_colors: [u8; 0x100],
161 bg_pixels: [u8; 0x100],
162 sprite_present: [bool; 0x100],
163 sprite_behind_bg: [bool; 0x100],
164 scanline_sprites: [SpriteRenderData; 8],
165 scanline_sprite_count: u8,
166 suppress_vblank: bool,
167
168 even: bool,
169 dot_clock: u64,
170
171 bg_on: bool,
173 sprites_on: bool,
174 rendering_on: bool,
175
176 sprite_zero_hit_predicted: bool,
178 sprite_zero_hit_cache_valid: bool,
179
180 palette_cache: [u8; 32],
182 palette_cache_dirty: bool,
183}
184
185impl PPU {
186 pub fn new() -> Self {
187 Self {
188 oam: [0; 256],
189 oam_addr: 0,
190 frame: 0,
191 ctrl: 0,
192 mask: 0,
193 status: 0,
194 open_bus: 0,
195 vram_addr: 0,
196 temp_vram_addr: 0,
197 fine_x: 0,
198 write_latch: false,
199 read_buffer: 0,
200 scanline: 261,
201 cycles: 0,
202 odd_frame: false,
203 next_tile_id: 0,
204 next_tile_attr: 0,
205 next_tile_lsb: 0,
206 next_tile_msb: 0,
207 bg_pattern_shift_lo: 0,
208 bg_pattern_shift_hi: 0,
209 bg_attr_shift_lo: 0,
210 bg_attr_shift_hi: 0,
211 tv_system: TVSystem::NTSC,
212 num_scanlines: 262,
213 vblank_lines: 241,
214
215 loopy_v: 0,
216 loopy_t: 0,
217 bit_map: [0; 0xF000],
218 bg_colors: [0; 0x100],
219 bg_pixels: [0; 0x100],
220 sprite_present: [false; 0x100],
221 sprite_behind_bg: [false; 0x100],
222 scanline_sprites: [SpriteRenderData::default(); 8],
223 scanline_sprite_count: 0,
224 suppress_vblank: false,
225
226 even: false,
227 dot_clock: 0,
228
229 bg_on: false,
230 sprites_on: false,
231 rendering_on: false,
232
233 sprite_zero_hit_predicted: false,
234 sprite_zero_hit_cache_valid: false,
235
236 palette_cache: [0; 32],
237 palette_cache_dirty: true,
238 }
239 }
240
241 pub fn reset(&mut self) {
242 self.ctrl = 0;
243 self.mask = 0;
244 self.update_rendering_flags();
245 self.status &= !(STATUS_SPRITE_OVERFLOW | STATUS_SPRITE_ZERO_HIT | STATUS_VBLANK);
246 self.open_bus = 0;
247 self.vram_addr = 0;
248 self.temp_vram_addr = 0;
249 self.fine_x = 0;
250 self.write_latch = false;
251 self.read_buffer = 0;
252 self.scanline = 261;
253 self.cycles = 0;
254 self.frame = 0;
255 self.odd_frame = false;
256 self.next_tile_id = 0;
257 self.next_tile_attr = 0;
258 self.next_tile_lsb = 0;
259 self.next_tile_msb = 0;
260 self.bg_pattern_shift_lo = 0;
261 self.bg_pattern_shift_hi = 0;
262 self.bg_attr_shift_lo = 0;
263 self.bg_attr_shift_hi = 0;
264 self.set_current_vram_addr(0);
265 self.set_temp_vram_addr(0);
266 self.bg_pixels = [0; 0x100];
267 self.sprite_present = [false; 0x100];
268 self.sprite_behind_bg = [false; 0x100];
269 self.scanline_sprites = [SpriteRenderData::default(); 8];
270 self.scanline_sprite_count = 0;
271 self.suppress_vblank = false;
272 self.dot_clock = 0;
273 self.invalidate_sprite_zero_hit_cache();
274 self.palette_cache = [0; 32];
275 self.palette_cache_dirty = true;
276 }
277
278 pub fn set_parameters(&mut self, tv_system: TVSystem) {
279 self.tv_system = tv_system;
280 match tv_system {
281 TVSystem::NTSC => {
282 self.num_scanlines = 262;
283 self.vblank_lines = 241;
284 }
285 TVSystem::PAL => {
286 self.num_scanlines = 312;
287 self.vblank_lines = 241;
288 }
289 TVSystem::DENDY => {
290 self.num_scanlines = 312;
291 self.vblank_lines = 241;
292 }
293 }
294 }
295
296 #[cfg(test)]
297 pub fn cpu_read_register(&mut self, bus: &mut impl PPUBus, addr: u16) -> u8 {
298 self.cpu_read_register_timed(bus, addr, 0)
299 }
300
301 pub fn cpu_read_register_timed(
302 &mut self,
303 bus: &mut impl PPUBus,
304 addr: u16,
305 cpu_cycle_offset: u8,
306 ) -> u8 {
307 match addr {
308 0x2002 => self.read_status_timed(cpu_cycle_offset),
309 0x2004 => {
310 let data = self.read_oam_data();
311 self.open_bus = data;
312 data
313 }
314 0x2007 => self.read_data(bus),
315 _ => self.open_bus,
316 }
317 }
318
319 #[cfg(test)]
320 pub fn cpu_write_register(&mut self, bus: &mut impl PPUBus, addr: u16, data: u8) {
321 self.cpu_write_register_timed(bus, addr, data, 0);
322 }
323
324 pub fn cpu_write_register_timed(
325 &mut self,
326 bus: &mut impl PPUBus,
327 addr: u16,
328 data: u8,
329 cpu_cycle_offset: u8,
330 ) {
331 self.open_bus = data;
332 let (future_scanline, _, _) = self.predict_status_timing(u16::from(cpu_cycle_offset) * 3);
333
334 match addr {
335 0x2000 => {
336 self.ctrl = data;
337 self.set_temp_vram_addr(
338 (self.temp_vram_addr & !0x0C00) | (((data as u16) & 0x03) << 10),
339 );
340 }
341 0x2001 => {
342 self.mask = data;
343 self.update_rendering_flags();
344 }
345 0x2003 => self.oam_addr = data,
346 0x2004 => self.write_oam_data_timed(data, future_scanline),
347 0x2005 => self.write_scroll(data),
348 0x2006 => self.write_addr(data),
349 0x2007 => self.write_data_timed(bus, data, future_scanline),
350 _ => {}
351 }
352 }
353
354 pub fn clock(&mut self, bus: &mut impl PPUBus) {
355 if self.scanline >= self.vblank_lines && self.scanline < self.num_scanlines - 1 {
357 if self.scanline == self.vblank_lines && self.cycles == 1 && !self.suppress_vblank {
358 self.status |= STATUS_VBLANK;
359 }
360 self.cycles += 1;
361 if self.cycles >= DOTS_PER_SCANLINE {
362 self.cycles = 0;
363 self.scanline += 1;
364 bus.notify_scanline(self.scanline, false);
365 }
366 self.dot_clock = self.dot_clock.wrapping_add(1);
367 return;
368 }
369
370 let visible_scanline = self.scanline < VISIBLE_SCANLINES;
371 let pre_render_scanline = self.scanline == self.num_scanlines - 1;
372 let render_scanline = visible_scanline || pre_render_scanline;
373 let visible_cycle = self.cycles < 256;
374 let fetch_cycle = visible_cycle || (320..337).contains(&self.cycles);
375
376 if self.scanline == self.num_scanlines - 1 && self.cycles == 1 {
377 self.status &= !(STATUS_SPRITE_OVERFLOW | STATUS_SPRITE_ZERO_HIT | STATUS_VBLANK);
378 self.suppress_vblank = false;
379 self.invalidate_sprite_zero_hit_cache();
381 }
382
383 if render_scanline && self.rendering_on() {
384 if visible_scanline && self.cycles == 0 {
385 self.sprite_present = [false; 0x100];
386 self.sprite_behind_bg = [false; 0x100];
387 self.invalidate_sprite_zero_hit_cache();
389 }
390
391 if self.bg_on() && fetch_cycle {
392 self.update_bg_shifters();
393 bus.set_ppu_sprite_phase(false);
394 self.fetch_bg(bus);
395 }
396
397 if visible_scanline && visible_cycle {
398 self.draw_bg_pixel(self.cycles as i16, bus);
399 self.draw_sprite_pixel(self.cycles as i16, bus);
400 }
401
402 match self.cycles {
403 255 => self.increment_y(),
404 256 => {
405 self.load_bg_shifters();
406 self.transfer_x();
407 if self.rendering_on() && (visible_scanline || pre_render_scanline) {
408 self.eval_sprites(bus);
409 }
410 }
411 279..=303 if pre_render_scanline => self.transfer_y(),
412 _ => {}
413 }
414
415 if (257..321).contains(&self.cycles) {
416 bus.set_ppu_sprite_phase(true);
417 self.fetch_sprite_data(bus);
418 }
419 }
420
421 if self.should_skip_odd_frame_cycle(pre_render_scanline) {
422 self.dot_clock = self.dot_clock.wrapping_add(1);
423 self.start_next_frame();
424 return;
425 }
426
427 self.cycles += 1;
428 if self.cycles >= DOTS_PER_SCANLINE {
429 self.cycles = 0;
430 self.scanline += 1;
431 bus.notify_scanline(self.scanline, self.rendering_on());
432
433 if self.scanline >= self.num_scanlines {
434 self.start_next_frame();
435 }
436 }
437 self.dot_clock = self.dot_clock.wrapping_add(1);
438 }
439
440 pub fn bg_on(&self) -> bool {
441 self.bg_on
442 }
443
444 pub fn sprites_on(&self) -> bool {
445 self.sprites_on
446 }
447
448 pub fn rendering_on(&self) -> bool {
449 self.rendering_on
450 }
451
452 fn update_rendering_flags(&mut self) {
453 self.bg_on = (self.mask & MASK_SHOW_BG) != 0;
454 self.sprites_on = (self.mask & MASK_SHOW_SPRITES) != 0;
455 self.rendering_on = self.bg_on || self.sprites_on;
456 self.invalidate_sprite_zero_hit_cache();
458 }
459
460 fn invalidate_sprite_zero_hit_cache(&mut self) {
461 self.sprite_zero_hit_cache_valid = false;
462 self.sprite_zero_hit_predicted = false;
463 }
464
465 pub fn nmi_line(&self) -> bool {
466 (self.ctrl & CTRL_NMI_ENABLE) != 0 && (self.status & STATUS_VBLANK) != 0
467 }
468
469 pub fn in_vblank(&self) -> bool {
470 (self.status & STATUS_VBLANK) != 0
471 }
472
473 pub fn scanline(&self) -> i16 {
474 self.scanline
475 }
476
477 #[cfg(feature = "debug")]
478 pub fn cycles(&self) -> u16 {
479 self.cycles
480 }
481
482 pub fn frame(&self) -> u64 {
483 self.frame
484 }
485
486 pub fn frame_pixels(&self) -> &[u8] {
487 &self.bit_map[..VISIBLE_FRAME_PIXELS]
488 }
489
490 #[cfg(test)]
491 pub fn frame_rgb(&self) -> Vec<u8> {
492 let mut rgb = Vec::with_capacity(VISIBLE_FRAME_PIXELS * 3);
493 for &pixel in self.frame_pixels() {
494 rgb.extend_from_slice(&palette_index_to_rgb(pixel));
495 }
496 rgb
497 }
498
499 pub fn tv_system(&self) -> TVSystem {
500 self.tv_system
501 }
502
503 pub(crate) fn write_oam_dma(&mut self, data: u8) {
504 self.write_oam_data(data);
505 }
506
507 #[cfg(test)]
508 pub fn oam_byte(&self, index: u8) -> u8 {
509 self.oam[index as usize]
510 }
511
512 pub fn oam_addr(&self) -> u8 {
513 self.oam_addr
514 }
515
516 #[cfg(feature = "debug")]
517 pub fn debug_oam_snapshot(&self) -> &[u8; 256] {
518 &self.oam
519 }
520
521 #[cfg(feature = "debug")]
522 pub fn debug_ctrl(&self) -> u8 {
523 self.ctrl
524 }
525
526 #[cfg(feature = "debug")]
527 pub fn debug_mask(&self) -> u8 {
528 self.mask
529 }
530
531 #[cfg(feature = "debug")]
532 pub fn debug_status(&self) -> u8 {
533 self.status
534 }
535
536 #[cfg(feature = "debug")]
537 pub fn debug_fine_x(&self) -> u8 {
538 self.fine_x
539 }
540
541 #[cfg(feature = "debug")]
542 pub fn debug_vram_addr(&self) -> u16 {
543 self.vram_addr
544 }
545
546 #[cfg(feature = "debug")]
547 pub fn debug_temp_vram_addr(&self) -> u16 {
548 self.temp_vram_addr
549 }
550
551 #[cfg(feature = "debug")]
552 pub fn debug_write_latch(&self) -> bool {
553 self.write_latch
554 }
555
556 #[cfg(feature = "debug")]
557 pub fn debug_odd_frame(&self) -> bool {
558 self.odd_frame
559 }
560
561 pub(crate) fn save_state(&self, writer: &mut StateWriter) {
562 writer.write_i16(self.scanline);
563 writer.write_u16(self.cycles);
564 writer.write_u64(self.frame);
565 writer.write_bytes(&self.oam);
566 writer.write_u8(self.oam_addr);
567 writer.write_u8(self.ctrl);
568 writer.write_u8(self.mask);
569 writer.write_u8(self.status);
570 writer.write_u8(self.open_bus);
571 writer.write_u16(self.vram_addr);
572 writer.write_u16(self.temp_vram_addr);
573 writer.write_u8(self.fine_x);
574 writer.write_bool(self.write_latch);
575 writer.write_u8(self.read_buffer);
576 writer.write_bool(self.odd_frame);
577 writer.write_u8(self.next_tile_id);
578 writer.write_u8(self.next_tile_attr);
579 writer.write_u8(self.next_tile_lsb);
580 writer.write_u8(self.next_tile_msb);
581 writer.write_u16(self.bg_pattern_shift_lo);
582 writer.write_u16(self.bg_pattern_shift_hi);
583 writer.write_u16(self.bg_attr_shift_lo);
584 writer.write_u16(self.bg_attr_shift_hi);
585 writer.write_u8(match self.tv_system {
586 TVSystem::NTSC => 0,
587 TVSystem::PAL => 1,
588 TVSystem::DENDY => 2,
589 });
590 writer.write_i16(self.num_scanlines);
591 writer.write_i16(self.vblank_lines);
592 writer.write_u16(self.loopy_v);
593 writer.write_u16(self.loopy_t);
594 writer.write_bytes(&self.bit_map);
595 writer.write_bytes(&self.bg_colors);
596 writer.write_bytes(&self.bg_pixels);
597 for &present in &self.sprite_present {
598 writer.write_bool(present);
599 }
600 for &behind in &self.sprite_behind_bg {
601 writer.write_bool(behind);
602 }
603 for sprite in &self.scanline_sprites {
604 writer.write_u8(sprite.tile_id);
605 writer.write_u8(sprite.row);
606 writer.write_u8(sprite.x);
607 writer.write_u8(sprite.attributes);
608 writer.write_u8(sprite.pattern_lo);
609 writer.write_u8(sprite.pattern_hi);
610 writer.write_bool(sprite.sprite_zero);
611 }
612 writer.write_u8(self.scanline_sprite_count);
613 writer.write_bool(self.suppress_vblank);
614 writer.write_bool(self.even);
615 writer.write_u64(self.dot_clock);
616 }
617
618 pub(crate) fn load_state(
619 &mut self,
620 reader: &mut StateReader<'_>,
621 ) -> Result<(), SaveStateError> {
622 self.scanline = reader.read_i16()?;
623 self.cycles = reader.read_u16()?;
624 self.frame = reader.read_u64()?;
625 reader.read_bytes_into(&mut self.oam)?;
626 self.oam_addr = reader.read_u8()?;
627 self.ctrl = reader.read_u8()?;
628 self.mask = reader.read_u8()?;
629 self.status = reader.read_u8()?;
630 self.open_bus = reader.read_u8()?;
631 self.vram_addr = reader.read_u16()?;
632 self.temp_vram_addr = reader.read_u16()?;
633 self.fine_x = reader.read_u8()?;
634 self.write_latch = reader.read_bool()?;
635 self.read_buffer = reader.read_u8()?;
636 self.odd_frame = reader.read_bool()?;
637 self.next_tile_id = reader.read_u8()?;
638 self.next_tile_attr = reader.read_u8()?;
639 self.next_tile_lsb = reader.read_u8()?;
640 self.next_tile_msb = reader.read_u8()?;
641 self.bg_pattern_shift_lo = reader.read_u16()?;
642 self.bg_pattern_shift_hi = reader.read_u16()?;
643 self.bg_attr_shift_lo = reader.read_u16()?;
644 self.bg_attr_shift_hi = reader.read_u16()?;
645 self.tv_system = match reader.read_u8()? {
646 0 => TVSystem::NTSC,
647 1 => TVSystem::PAL,
648 2 => TVSystem::DENDY,
649 _ => {
650 return Err(SaveStateError::InvalidData(
651 "invalid TV system in PPU state",
652 ));
653 }
654 };
655 self.num_scanlines = reader.read_i16()?;
656 self.vblank_lines = reader.read_i16()?;
657 self.loopy_v = reader.read_u16()?;
658 self.loopy_t = reader.read_u16()?;
659 reader.read_bytes_into(&mut self.bit_map)?;
660 reader.read_bytes_into(&mut self.bg_colors)?;
661 reader.read_bytes_into(&mut self.bg_pixels)?;
662 for present in &mut self.sprite_present {
663 *present = reader.read_bool()?;
664 }
665 for behind in &mut self.sprite_behind_bg {
666 *behind = reader.read_bool()?;
667 }
668 for sprite in &mut self.scanline_sprites {
669 sprite.tile_id = reader.read_u8()?;
670 sprite.row = reader.read_u8()?;
671 sprite.x = reader.read_u8()?;
672 sprite.attributes = reader.read_u8()?;
673 sprite.pattern_lo = reader.read_u8()?;
674 sprite.pattern_hi = reader.read_u8()?;
675 sprite.sprite_zero = reader.read_bool()?;
676 }
677 self.scanline_sprite_count = reader.read_u8()?;
678 self.suppress_vblank = reader.read_bool()?;
679 self.even = reader.read_bool()?;
680 self.dot_clock = reader.read_u64()?;
681 self.update_rendering_flags();
682 self.palette_cache_dirty = true;
683 Ok(())
684 }
685
686 fn read_status_timed(&mut self, cpu_cycle_offset: u8) -> u8 {
687 let ppu_cycle_offset = u16::from(cpu_cycle_offset) * 3;
688 let (future_scanline, future_cycles, future_status) =
689 self.predict_status_timing(ppu_cycle_offset);
690
691 let mut status_bits = future_status;
692 if cpu_cycle_offset <= 1
696 && (status_bits & STATUS_SPRITE_ZERO_HIT) == 0
697 && self.predict_sprite_zero_hit_within_offset(ppu_cycle_offset)
698 {
699 status_bits |= STATUS_SPRITE_ZERO_HIT;
700 }
701 if future_scanline == self.vblank_lines && future_cycles == 1 {
702 status_bits &= !STATUS_VBLANK;
703 self.suppress_vblank = true;
704 }
705
706 let status = (status_bits & 0xE0) | (self.open_bus & 0x1F);
707 self.status &= !STATUS_VBLANK;
708 self.write_latch = false;
709 self.open_bus = status;
710 status
711 }
712
713 fn predict_status_timing(&self, ppu_cycle_offset: u16) -> (i16, u16, u8) {
714 let mut scanline = self.scanline;
715 let mut cycles = self.cycles;
716 let mut odd_frame = self.odd_frame;
717 let mut status = self.status;
718 let mut suppress_vblank = self.suppress_vblank;
719
720 for _ in 0..ppu_cycle_offset {
721 if scanline == self.vblank_lines && cycles == 1 && !suppress_vblank {
722 status |= STATUS_VBLANK;
723 }
724
725 if scanline == self.num_scanlines - 1 && cycles == 1 {
726 status &= !(STATUS_SPRITE_OVERFLOW | STATUS_SPRITE_ZERO_HIT | STATUS_VBLANK);
727 suppress_vblank = false;
728 }
729
730 let pre_render_scanline = scanline == self.num_scanlines - 1;
731 let skip_odd_frame_cycle = self.num_scanlines == 262
732 && pre_render_scanline
733 && self.rendering_on()
734 && odd_frame
735 && cycles == DOTS_PER_SCANLINE - 2;
736
737 if skip_odd_frame_cycle {
738 scanline = 0;
739 cycles = 0;
740 odd_frame = !odd_frame;
741 continue;
742 }
743
744 cycles += 1;
745 if cycles >= DOTS_PER_SCANLINE {
746 cycles = 0;
747 scanline += 1;
748 if scanline >= self.num_scanlines {
749 scanline = 0;
750 odd_frame = !odd_frame;
751 }
752 }
753 }
754
755 (scanline, cycles, status)
756 }
757
758 fn predict_sprite_zero_hit_within_offset(&mut self, ppu_cycle_offset: u16) -> bool {
759 if (self.status & STATUS_SPRITE_ZERO_HIT) != 0 {
761 return false;
762 }
763
764 if ppu_cycle_offset == 0
765 || !self.bg_on()
766 || !self.sprites_on()
767 || self.scanline < 0
768 || self.scanline >= VISIBLE_SCANLINES
769 {
770 return false;
771 }
772
773 if self.sprite_zero_hit_cache_valid && !self.sprite_zero_hit_predicted {
776 return false;
777 }
778
779 let Some(sprite) = self
780 .scanline_sprites
781 .iter()
782 .take(self.scanline_sprite_count as usize)
783 .find(|sprite| sprite.sprite_zero)
784 .copied()
785 else {
786 return false;
787 };
788
789 let mut cycles = self.cycles;
790 let mut scanline = self.scanline;
791 let mut odd_frame = self.odd_frame;
792
793 let mut bg_pattern_shift_lo = self.bg_pattern_shift_lo;
794 let mut bg_pattern_shift_hi = self.bg_pattern_shift_hi;
795 let mut bg_attr_shift_lo = self.bg_attr_shift_lo;
796 let mut bg_attr_shift_hi = self.bg_attr_shift_hi;
797
798 let show_leftmost_bg = (self.mask & MASK_SHOW_BG_LEFTMOST) != 0;
799 let show_leftmost_sprites = (self.mask & MASK_SHOW_SPRITES_LEFTMOST) != 0;
800
801 for _ in 0..ppu_cycle_offset {
802 let visible_scanline = scanline < VISIBLE_SCANLINES;
803 let pre_render_scanline = scanline == self.num_scanlines - 1;
804 let render_scanline = visible_scanline || pre_render_scanline;
805 let visible_cycle = cycles < 256;
806 let fetch_cycle = visible_cycle || (320..337).contains(&cycles);
807
808 if render_scanline && self.rendering_on() {
809 if visible_scanline && visible_cycle {
810 let x = cycles as usize;
811
812 let bg_pixel = if show_leftmost_bg || x >= 8 {
813 let bit = 0x8000 >> self.fine_x;
814 let lo = u8::from((bg_pattern_shift_lo & bit) != 0);
815 let hi = u8::from((bg_pattern_shift_hi & bit) != 0);
816 (hi << 1) | lo
817 } else {
818 0
819 };
820
821 if (show_leftmost_sprites || x >= 8) && x < 255 {
822 let sprite_x = usize::from(sprite.x);
823 if x >= sprite_x && x < sprite_x + 8 {
824 let sprite_bit = if (sprite.attributes & 0x40) != 0 {
826 (x - sprite_x) as u8
827 } else {
828 7 - (x - sprite_x) as u8
829 };
830 let sprite_pixel = ((sprite.pattern_hi >> sprite_bit) & 0x01) << 1
831 | (sprite.pattern_lo >> sprite_bit) & 0x01;
832 if sprite_pixel != 0 && bg_pixel != 0 {
833 self.sprite_zero_hit_predicted = true;
835 self.sprite_zero_hit_cache_valid = true;
836 return true;
837 }
838 }
839 }
840 }
841
842 if self.bg_on() && fetch_cycle {
843 bg_pattern_shift_lo <<= 1;
844 bg_pattern_shift_hi <<= 1;
845 bg_attr_shift_lo <<= 1;
846 bg_attr_shift_hi <<= 1;
847 }
848
849 if self.bg_on() && fetch_cycle && (cycles & 0x07) == 0 {
850 bg_pattern_shift_lo =
851 (bg_pattern_shift_lo & 0xFF00) | u16::from(self.next_tile_lsb);
852 bg_pattern_shift_hi =
853 (bg_pattern_shift_hi & 0xFF00) | u16::from(self.next_tile_msb);
854
855 let attr_lo = if (self.next_tile_attr & 0x01) != 0 {
856 0xFF
857 } else {
858 0x00
859 };
860 let attr_hi = if (self.next_tile_attr & 0x02) != 0 {
861 0xFF
862 } else {
863 0x00
864 };
865
866 bg_attr_shift_lo = (bg_attr_shift_lo & 0xFF00) | attr_lo;
867 bg_attr_shift_hi = (bg_attr_shift_hi & 0xFF00) | attr_hi;
868 }
869 }
870
871 let skip_odd_frame_cycle = self.num_scanlines == 262
872 && pre_render_scanline
873 && self.rendering_on()
874 && odd_frame
875 && cycles == DOTS_PER_SCANLINE - 2;
876
877 if skip_odd_frame_cycle {
878 scanline = 0;
879 cycles = 0;
880 odd_frame = !odd_frame;
881 continue;
882 }
883
884 cycles += 1;
885 if cycles >= DOTS_PER_SCANLINE {
886 cycles = 0;
887 scanline += 1;
888 if scanline >= self.num_scanlines {
889 scanline = 0;
890 odd_frame = !odd_frame;
891 }
892 }
893 }
894
895 self.sprite_zero_hit_predicted = false;
897 self.sprite_zero_hit_cache_valid = true;
898 false
899 }
900
901 fn read_data(&mut self, bus: &mut impl PPUBus) -> u8 {
902 let addr = self.loopy_v & 0x3FFF;
903 let data = if addr >= 0x3F00 {
904 self.read_buffer = self.ppu_read_bus(bus, addr.wrapping_sub(0x1000));
905
906 let cache_idx = (addr & 0x1F) as usize;
908 let palette_data = if self.palette_cache_dirty {
909 let data = self.ppu_read_bus(bus, addr);
911 let actual_idx = if cache_idx >= 0x10 && cache_idx % 4 == 0 {
913 cache_idx - 0x10
914 } else {
915 cache_idx
916 };
917 self.palette_cache[actual_idx] = data;
918 data
919 } else {
920 self.palette_cache[cache_idx]
921 };
922
923 self.mask_palette_color(palette_data)
924 } else {
925 let buffered = self.read_buffer;
926 self.read_buffer = self.ppu_read_bus_exposed(bus, addr);
927 buffered
928 };
929
930 self.increment_data_access_vram_addr();
931 self.open_bus = data;
932 data
933 }
934
935 fn write_data_timed(&mut self, bus: &mut impl PPUBus, data: u8, effective_scanline: i16) {
936 let addr = self.loopy_v & 0x3FFF;
937 self.ppu_write_bus_exposed(bus, addr, data);
938
939 if addr >= 0x3F00 && addr <= 0x3F1F {
941 let cache_idx = (addr & 0x1F) as usize;
942 let actual_idx = if cache_idx >= 0x10 && cache_idx % 4 == 0 {
944 cache_idx - 0x10
945 } else {
946 cache_idx
947 };
948 self.palette_cache[actual_idx] = data;
949 self.palette_cache_dirty = false;
950 }
951
952 self.increment_data_access_vram_addr_on_scanline(effective_scanline);
953 }
954
955 fn write_scroll(&mut self, data: u8) {
956 if !self.write_latch {
957 self.fine_x = data & 0x07;
958 self.set_temp_vram_addr((self.temp_vram_addr & !0x001F) | ((data as u16) >> 3));
959 self.write_latch = true;
960 } else {
961 self.set_temp_vram_addr(
962 (self.temp_vram_addr & !0x73E0)
963 | ((((data as u16) >> 3) & 0x1F) << 5)
964 | (((data as u16) & 0x07) << 12),
965 );
966 self.write_latch = false;
967 }
968 }
969
970 fn write_addr(&mut self, data: u8) {
971 if !self.write_latch {
972 self.set_temp_vram_addr((self.temp_vram_addr & 0x00FF) | (((data as u16) & 0x3F) << 8));
973 self.write_latch = true;
974 } else {
975 self.set_temp_vram_addr((self.temp_vram_addr & 0x7F00) | data as u16);
976 self.set_current_vram_addr(self.temp_vram_addr);
977 self.write_latch = false;
978 }
979 }
980
981 fn increment_vram_addr(&mut self) {
982 let increment = if (self.ctrl & CTRL_VRAM_INCREMENT) != 0 {
983 32
984 } else {
985 1
986 };
987 self.set_current_vram_addr(self.loopy_v.wrapping_add(increment));
988 }
989
990 fn increment_data_access_vram_addr_on_scanline(&mut self, scanline: i16) {
991 if self.rendering_vram_access_active_on_scanline(scanline) {
992 self.increment_x();
993 self.increment_y();
994 } else {
995 self.increment_vram_addr();
996 }
997 }
998
999 fn increment_data_access_vram_addr(&mut self) {
1000 self.increment_data_access_vram_addr_on_scanline(self.scanline);
1001 }
1002
1003 fn write_oam_data(&mut self, data: u8) {
1004 self.write_oam_data_timed(data, self.scanline);
1005 }
1006
1007 fn write_oam_data_timed(&mut self, data: u8, effective_scanline: i16) {
1008 if self.rendering_oam_access_active_on_scanline(effective_scanline) {
1009 self.oam_addr = self.oam_addr.wrapping_add(4);
1010 return;
1011 }
1012
1013 self.oam[self.oam_addr as usize] = data;
1014 self.oam_addr = self.oam_addr.wrapping_add(1);
1015 self.invalidate_sprite_zero_hit_cache();
1017 }
1018
1019 fn read_oam_data(&self) -> u8 {
1020 if self.rendering_oam_clear_phase() {
1021 return 0xFF;
1022 }
1023
1024 let data = self.oam[self.oam_addr as usize];
1025 if (self.oam_addr & 0x03) == 0x02 {
1026 data & 0xE3
1027 } else {
1028 data
1029 }
1030 }
1031
1032 fn fetch_bg(&mut self, bus: &mut impl PPUBus) {
1033 match self.cycles & 0x07 {
1034 0 => {
1035 self.load_bg_shifters();
1036 self.fetch_nt(bus);
1037 }
1038 2 => {
1039 let addr = 0x23C0
1040 | (self.loopy_v & 0x0C00)
1041 | ((self.loopy_v >> 4) & 0x38)
1042 | ((self.loopy_v >> 2) & 0x07);
1043 let attr = self.ppu_read_bus(bus, addr);
1044 let shift = ((self.loopy_v >> 4) & 0x04) | (self.loopy_v & 0x02);
1045 self.next_tile_attr = (attr >> shift) as u8 & 0x03;
1046 }
1047 4 => {
1048 let addr = self.bg_pattern_addr(self.next_tile_id);
1049 self.next_tile_lsb = self.ppu_read_bus_exposed(bus, addr);
1050 }
1051 6 => {
1052 let addr = self.bg_pattern_addr(self.next_tile_id).wrapping_add(8);
1053 self.next_tile_msb = self.ppu_read_bus_exposed(bus, addr);
1054 }
1055 7 => self.increment_x(),
1056 _ => {}
1057 }
1058 }
1059
1060 fn fetch_nt(&mut self, bus: &mut impl PPUBus) {
1061 let addr = 0x2000 | (self.loopy_v & 0x0FFF);
1062 self.next_tile_id = self.ppu_read_bus(bus, addr);
1063 }
1064
1065 fn eval_sprites(&mut self, _bus: &mut impl PPUBus) {
1066 let target_scanline = if self.scanline == self.num_scanlines - 1 {
1067 0
1068 } else {
1069 (self.scanline as u8).wrapping_add(1)
1070 };
1071
1072 self.scanline_sprites = [SpriteRenderData::default(); 8];
1073 self.scanline_sprite_count = 0;
1074
1075 let sprite_height = self.sprite_height();
1076 let mut overflow_start = None;
1077 for index in 0..64 {
1078 let row = match self.sprite_row_for_scanline(index, target_scanline, sprite_height) {
1079 Some(row) => row,
1080 None => continue,
1081 };
1082
1083 if self.scanline_sprite_count >= 8 {
1084 overflow_start = Some(index);
1085 break;
1086 }
1087
1088 let base = index * 4;
1089 let tile = self.oam[base + 1];
1090 let attributes = self.oam[base + 2];
1091 let x = self.oam[base + 3];
1092
1093 self.scanline_sprites[self.scanline_sprite_count as usize] = SpriteRenderData {
1094 tile_id: tile,
1095 row,
1096 x,
1097 attributes,
1098 pattern_lo: 0,
1099 pattern_hi: 0,
1100 sprite_zero: index == 0,
1101 };
1102 self.scanline_sprite_count += 1;
1103
1104 if self.scanline_sprite_count >= 8 {
1105 overflow_start = Some(index + 1);
1106 break;
1107 }
1108 }
1109
1110 if let Some(start_index) = overflow_start {
1111 if self.sprite_overflow_bugged(start_index, target_scanline, sprite_height) {
1112 self.status |= STATUS_SPRITE_OVERFLOW;
1113 }
1114 }
1115 }
1116
1117 fn sprite_row_for_scanline(
1118 &self,
1119 sprite_index: usize,
1120 target_scanline: u8,
1121 sprite_height: u8,
1122 ) -> Option<u8> {
1123 let sprite_y = self.oam[sprite_index * 4];
1124 let sprite_top = if sprite_y == 0xFF {
1125 0
1126 } else {
1127 u16::from(sprite_y) + 1
1128 };
1129 let target = u16::from(target_scanline);
1130 if target < sprite_top {
1131 None
1132 } else {
1133 let row = target - sprite_top;
1134 if row >= u16::from(sprite_height) {
1135 None
1136 } else {
1137 Some(row as u8)
1138 }
1139 }
1140 }
1141
1142 fn sprite_overflow_bugged(
1143 &self,
1144 start_index: usize,
1145 target_scanline: u8,
1146 sprite_height: u8,
1147 ) -> bool {
1148 let mut n = start_index;
1149 let mut m = 0usize;
1150 while n < 64 {
1151 let value = self.oam[n * 4 + m];
1152 let sprite_top = if value == 0xFF {
1153 0
1154 } else {
1155 u16::from(value) + 1
1156 };
1157 let target = u16::from(target_scanline);
1158 if target >= sprite_top && (target - sprite_top) < u16::from(sprite_height) {
1159 return true;
1160 }
1161
1162 n += 1;
1163 m = (m + 1) & 0x03;
1164 }
1165
1166 false
1167 }
1168
1169 fn fetch_sprite_data(&mut self, bus: &mut impl PPUBus) {
1170 let slot = ((self.cycles - 257) / 8) as usize;
1171 if slot >= 8 {
1172 return;
1173 }
1174
1175 let subcycle = (self.cycles - 257) & 0x07;
1176 match subcycle {
1177 0 | 2 => {
1178 let _ = self.ppu_read_bus(bus, 0x2000 | (self.loopy_v & 0x0FFF));
1179 }
1180 4 => {
1181 let sprite = self.scanline_sprites[slot];
1182 let addr = self.sprite_pattern_addr(sprite.tile_id, sprite.attributes, sprite.row);
1183 self.scanline_sprites[slot].pattern_lo = self.ppu_read_bus_exposed(bus, addr);
1184 }
1185 6 => {
1186 let sprite = self.scanline_sprites[slot];
1187 let addr = self
1188 .sprite_pattern_addr(sprite.tile_id, sprite.attributes, sprite.row)
1189 .wrapping_add(8);
1190 self.scanline_sprites[slot].pattern_hi = self.ppu_read_bus_exposed(bus, addr);
1191 }
1192 _ => {}
1193 }
1194 }
1195
1196 fn draw_bg_pixel(&mut self, offset: i16, bus: &mut impl PPUBus) {
1197 let x = offset as usize;
1198 let y = self.scanline as usize;
1199 if x >= 256 || y >= VISIBLE_SCANLINES as usize {
1200 return;
1201 }
1202
1203 let show_leftmost = (self.mask & MASK_SHOW_BG_LEFTMOST) != 0;
1204 let bg_pixel = if self.bg_on() && (show_leftmost || x >= 8) {
1206 let bit = 0x8000 >> self.fine_x;
1207 let lo = u8::from((self.bg_pattern_shift_lo & bit) != 0);
1208 let hi = u8::from((self.bg_pattern_shift_hi & bit) != 0);
1209 (hi << 1) | lo
1210 } else {
1211 0
1212 };
1213 let bg_palette = if bg_pixel == 0 {
1215 0
1216 } else {
1217 let bit = 0x8000 >> self.fine_x;
1218 let lo = u8::from((self.bg_attr_shift_lo & bit) != 0);
1219 let hi = u8::from((self.bg_attr_shift_hi & bit) != 0);
1220 (hi << 1) | lo
1221 };
1222
1223 let palette_addr = if bg_pixel == 0 {
1224 0x3F00
1225 } else {
1226 0x3F00 | (u16::from(bg_palette) << 2) | u16::from(bg_pixel)
1227 };
1228
1229 let cache_idx = (palette_addr & 0x1F) as usize;
1231 let palette_data = if self.palette_cache_dirty {
1232 let data = self.ppu_read_bus(bus, palette_addr);
1234 let actual_idx = if cache_idx >= 0x10 && cache_idx % 4 == 0 {
1235 cache_idx - 0x10
1236 } else {
1237 cache_idx
1238 };
1239 self.palette_cache[actual_idx] = data;
1240 data
1241 } else {
1242 self.palette_cache[cache_idx]
1243 };
1244
1245 let color = self.mask_palette_color(palette_data);
1246 let pixel_index = y * 256 + x;
1247
1248 let sprite_in_front = self.sprite_present[x] && !self.sprite_behind_bg[x];
1249 let sprite_visible_behind_bg =
1250 self.sprite_present[x] && self.sprite_behind_bg[x] && bg_pixel == 0;
1251 if !sprite_in_front && !sprite_visible_behind_bg {
1252 self.bit_map[pixel_index] = color;
1253 }
1254 self.bg_colors[x] = color;
1255 self.bg_pixels[x] = bg_pixel;
1256 }
1257
1258 fn draw_sprite_pixel(&mut self, offset: i16, bus: &mut impl PPUBus) {
1259 if !self.sprites_on() {
1260 return;
1261 }
1262
1263 let x = offset as usize;
1264 let y = self.scanline as usize;
1265 if x >= 256 || y >= VISIBLE_SCANLINES as usize {
1266 return;
1267 }
1268
1269 if x < 8 && (self.mask & MASK_SHOW_SPRITES_LEFTMOST) == 0 {
1270 return;
1271 }
1272
1273 for sprite in self
1274 .scanline_sprites
1275 .iter()
1276 .take(self.scanline_sprite_count as usize)
1277 .copied()
1278 {
1279 let sprite_x = usize::from(sprite.x);
1280 if x < sprite_x || x >= sprite_x + 8 {
1281 continue;
1282 }
1283
1284 let sprite_bit = if (sprite.attributes & 0x40) != 0 {
1286 (x - sprite_x) as u8
1287 } else {
1288 7 - (x - sprite_x) as u8
1289 };
1290 let sprite_pixel = ((sprite.pattern_hi >> sprite_bit) & 0x01) << 1
1291 | (sprite.pattern_lo >> sprite_bit) & 0x01;
1292 if sprite_pixel == 0 {
1293 continue;
1294 }
1295
1296 let bg_pixel_for_hit = if !self.bg_on() {
1298 0
1299 } else {
1300 let show_leftmost_bg = (self.mask & MASK_SHOW_BG_LEFTMOST) != 0;
1301 if !show_leftmost_bg && x < 8 {
1302 0
1303 } else {
1304 let bit = 0x8000 >> self.fine_x;
1305 let lo = u8::from((self.bg_pattern_shift_lo & bit) != 0);
1306 let hi = u8::from((self.bg_pattern_shift_hi & bit) != 0);
1307 (hi << 1) | lo
1308 }
1309 };
1310 if sprite.sprite_zero && bg_pixel_for_hit != 0 && x < 255 {
1311 self.status |= STATUS_SPRITE_ZERO_HIT;
1312 self.invalidate_sprite_zero_hit_cache();
1314 }
1315
1316 let bg_pixel_visible = self.bg_pixels[x];
1318 let behind_background = (sprite.attributes & 0x20) != 0;
1319 if behind_background && bg_pixel_visible != 0 {
1320 break;
1321 }
1322
1323 let palette = sprite.attributes & 0x03;
1324 let palette_addr = 0x3F10 | (u16::from(palette) << 2) | u16::from(sprite_pixel);
1325
1326 let cache_idx = (palette_addr & 0x1F) as usize;
1328 let palette_data = if self.palette_cache_dirty {
1329 let data = self.ppu_read_bus(bus, palette_addr);
1331 let actual_idx = if cache_idx >= 0x10 && cache_idx % 4 == 0 {
1332 cache_idx - 0x10
1333 } else {
1334 cache_idx
1335 };
1336 self.palette_cache[actual_idx] = data;
1337 data
1338 } else {
1339 self.palette_cache[cache_idx]
1340 };
1341
1342 let color = self.mask_palette_color(palette_data);
1343 self.bit_map[y * 256 + x] = color;
1344 self.sprite_present[x] = true;
1345 self.sprite_behind_bg[x] = behind_background;
1346 break;
1347 }
1348 }
1349
1350 fn set_current_vram_addr(&mut self, addr: u16) {
1351 self.vram_addr = addr;
1352 self.loopy_v = addr;
1353 }
1354
1355 fn set_temp_vram_addr(&mut self, addr: u16) {
1356 self.temp_vram_addr = addr;
1357 self.loopy_t = addr;
1358 }
1359
1360 fn should_skip_odd_frame_cycle(&self, pre_render_scanline: bool) -> bool {
1361 self.num_scanlines == 262
1362 && pre_render_scanline
1363 && self.rendering_on()
1364 && self.odd_frame
1365 && self.cycles == DOTS_PER_SCANLINE - 2
1366 }
1367
1368 fn rendering_vram_access_active_on_scanline(&self, scanline: i16) -> bool {
1369 self.rendering_on() && (scanline < VISIBLE_SCANLINES || scanline == self.num_scanlines - 1)
1370 }
1371
1372 fn rendering_oam_access_active(&self) -> bool {
1373 self.rendering_oam_access_active_on_scanline(self.scanline)
1374 }
1375
1376 fn rendering_oam_access_active_on_scanline(&self, scanline: i16) -> bool {
1377 self.rendering_on() && scanline < VISIBLE_SCANLINES
1378 }
1379
1380 fn rendering_oam_clear_phase(&self) -> bool {
1381 self.rendering_oam_access_active() && (1..=64).contains(&self.cycles)
1382 }
1383
1384 fn start_next_frame(&mut self) {
1385 self.scanline = 0;
1386 self.cycles = 0;
1387 self.frame += 1;
1388 self.odd_frame = !self.odd_frame;
1389 self.even = !self.even;
1390 }
1391
1392 fn ppu_read_bus(&mut self, bus: &mut impl PPUBus, addr: u16) -> u8 {
1393 let addr = addr & 0x3FFF;
1394 self.observe_mapper_a12_line(bus, addr);
1395 bus.ppu_read(addr)
1396 }
1397
1398 #[inline]
1399 fn ppu_read_bus_exposed(&mut self, bus: &mut impl PPUBus, addr: u16) -> u8 {
1400 let addr = addr & 0x3FFF;
1401 self.observe_mapper_a12_line(bus, addr);
1402 bus.ppu_read(addr)
1403 }
1404
1405 fn ppu_write_bus_exposed(&mut self, bus: &mut impl PPUBus, addr: u16, data: u8) {
1406 let addr = addr & 0x3FFF;
1407 self.observe_mapper_a12_line(bus, addr);
1408 bus.ppu_write(addr, data);
1409 }
1410
1411 fn observe_mapper_a12_line(&mut self, bus: &mut impl PPUBus, addr: u16) {
1412 match addr {
1413 0x0000..=0x1FFF => bus.check_a12(addr, self.dot_clock),
1414 0x2000..=0x2FFF => bus.check_a12(0x0000, self.dot_clock),
1417 _ => {}
1418 }
1419 }
1420
1421 fn bg_pattern_addr(&self, tile_id: u8) -> u16 {
1422 let table = if (self.ctrl & CTRL_BG_TABLE) != 0 {
1423 0x1000
1424 } else {
1425 0x0000
1426 };
1427 let fine_y = (self.loopy_v >> 12) & 0x0007;
1428 table | (u16::from(tile_id) << 4) | fine_y
1429 }
1430
1431 fn sprite_pattern_addr(&self, tile_id: u8, attributes: u8, row: u8) -> u16 {
1432 let mut fine_y = u16::from(row);
1433 let sprite_height = u16::from(self.sprite_height());
1434 if (attributes & 0x80) != 0 {
1435 fine_y = sprite_height - 1 - fine_y;
1436 }
1437
1438 if sprite_height == 16 {
1439 let table = u16::from(tile_id & 0x01) << 12;
1440 let tile = u16::from(tile_id & 0xFE) + (fine_y >> 3);
1441 table | (tile << 4) | (fine_y & 0x07)
1442 } else {
1443 let table = if (self.ctrl & CTRL_SPRITE_TABLE) != 0 {
1444 0x1000
1445 } else {
1446 0x0000
1447 };
1448 table | (u16::from(tile_id) << 4) | fine_y
1449 }
1450 }
1451
1452 fn sprite_height(&self) -> u8 {
1453 if (self.ctrl & CTRL_SPRITE_SIZE) != 0 {
1454 16
1455 } else {
1456 8
1457 }
1458 }
1459
1460 fn mask_palette_color(&self, color: u8) -> u8 {
1461 let color = color & 0x3F;
1462 if (self.mask & MASK_GRAYSCALE) != 0 {
1463 color & 0x30
1464 } else {
1465 color
1466 }
1467 }
1468
1469 fn update_bg_shifters(&mut self) {
1470 if !self.bg_on() {
1471 return;
1472 }
1473
1474 self.bg_pattern_shift_lo <<= 1;
1475 self.bg_pattern_shift_hi <<= 1;
1476 self.bg_attr_shift_lo <<= 1;
1477 self.bg_attr_shift_hi <<= 1;
1478 }
1479
1480 fn load_bg_shifters(&mut self) {
1481 self.bg_pattern_shift_lo =
1482 (self.bg_pattern_shift_lo & 0xFF00) | u16::from(self.next_tile_lsb);
1483 self.bg_pattern_shift_hi =
1484 (self.bg_pattern_shift_hi & 0xFF00) | u16::from(self.next_tile_msb);
1485
1486 let attr_lo = if (self.next_tile_attr & 0x01) != 0 {
1487 0xFF
1488 } else {
1489 0x00
1490 };
1491 let attr_hi = if (self.next_tile_attr & 0x02) != 0 {
1492 0xFF
1493 } else {
1494 0x00
1495 };
1496
1497 self.bg_attr_shift_lo = (self.bg_attr_shift_lo & 0xFF00) | attr_lo;
1498 self.bg_attr_shift_hi = (self.bg_attr_shift_hi & 0xFF00) | attr_hi;
1499 }
1500
1501 fn increment_x(&mut self) {
1502 if !self.rendering_on() {
1503 return;
1504 }
1505
1506 if (self.loopy_v & 0x001F) == 31 {
1507 self.loopy_v &= !0x001F;
1508 self.loopy_v ^= 0x0400;
1509 } else {
1510 self.loopy_v = self.loopy_v.wrapping_add(1);
1511 }
1512
1513 self.vram_addr = self.loopy_v;
1514 }
1515
1516 fn increment_y(&mut self) {
1517 if !self.rendering_on() {
1518 return;
1519 }
1520
1521 if (self.loopy_v & 0x7000) != 0x7000 {
1522 self.loopy_v = self.loopy_v.wrapping_add(0x1000);
1523 } else {
1524 self.loopy_v &= !0x7000;
1525 let mut coarse_y = (self.loopy_v & 0x03E0) >> 5;
1526 if coarse_y == 29 {
1527 coarse_y = 0;
1528 self.loopy_v ^= 0x0800;
1529 } else if coarse_y == 31 {
1530 coarse_y = 0;
1531 } else {
1532 coarse_y += 1;
1533 }
1534 self.loopy_v = (self.loopy_v & !0x03E0) | (coarse_y << 5);
1535 }
1536
1537 self.vram_addr = self.loopy_v;
1538 }
1539
1540 fn transfer_x(&mut self) {
1541 if !self.rendering_on() {
1542 return;
1543 }
1544
1545 self.loopy_v = (self.loopy_v & !0x041F) | (self.loopy_t & 0x041F);
1546 self.vram_addr = self.loopy_v;
1547 }
1548
1549 fn transfer_y(&mut self) {
1550 if !self.rendering_on() {
1551 return;
1552 }
1553
1554 self.loopy_v = (self.loopy_v & !0x7BE0) | (self.loopy_t & 0x7BE0);
1555 self.vram_addr = self.loopy_v;
1556 }
1557}
1558
1559#[cfg(test)]
1560pub(crate) fn palette_index_to_rgb(index: u8) -> [u8; 3] {
1561 NES_RGB_PALETTE[(index & 0x3F) as usize]
1562}
1563
1564impl Default for PPU {
1565 fn default() -> Self {
1566 Self::new()
1567 }
1568}
1569
1570#[cfg(test)]
1571mod tests;