Skip to main content

nes_sim/
ppu.rs

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    // parameters
153    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    // Cached rendering state (updated when mask register is written)
172    bg_on: bool,
173    sprites_on: bool,
174    rendering_on: bool,
175
176    // Sprite-0 hit prediction cache
177    sprite_zero_hit_predicted: bool,
178    sprite_zero_hit_cache_valid: bool,
179
180    // Palette cache to reduce RAM reads
181    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        // Vblank 快速路径:跳过所有渲染检查
356        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            // 清除 sprite-0-hit 状态时也清除预测缓存
380            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                // 新 scanline 开始,清除预测缓存
388                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        // Mask 变化可能影响 sprite-0-hit 条件,清除缓存
457        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        // Only apply imminent sprite-0-hit lookahead for sub-cycle peeks.
693        // Wider lookahead windows were causing unstable split timing in games
694        // that poll PPUSTATUS at instruction-level granularity.
695        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        // 如果当前 scanline 已经发生了 sprite-0-hit,预测未来没有意义
760        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        // 检查缓存:如果缓存有效且结果为 false,直接返回 false
774        //(如果结果为 true,需要重新检查因为可能已经过期)
775        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                            // 内联 sprite_pixel()
825                            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                                // 缓存预测结果
834                                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        // 缓存预测结果
896        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            // 使用 palette 缓存
907            let cache_idx = (addr & 0x1F) as usize;
908            let palette_data = if self.palette_cache_dirty {
909                // 缓存无效,重新读取并更新缓存
910                let data = self.ppu_read_bus(bus, addr);
911                // Palette 0x3F10 和 0x3F00 是镜像的
912                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        // 更新 palette 缓存
940        if addr >= 0x3F00 && addr <= 0x3F1F {
941            let cache_idx = (addr & 0x1F) as usize;
942            // Palette 0x3F10 和 0x3F00 是镜像的
943            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        // OAM 数据变化,清除 sprite-0-hit 预测缓存
1016        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        // 内联 current_bg_pixel()
1205        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        // 内联 current_bg_palette()
1214        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        // 使用 palette 缓存
1230        let cache_idx = (palette_addr & 0x1F) as usize;
1231        let palette_data = if self.palette_cache_dirty {
1232            // 缓存无效,重新读取并更新缓存
1233            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            // 内联 sprite_pixel()
1285            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            // 内联 bg_pixel_visible_for_sprite_zero_hit()
1297            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                // 实际发生 sprite-0-hit,清除预测缓存
1313                self.invalidate_sprite_zero_hit_cache();
1314            }
1315
1316            // 内联 bg_pixel_visible_to_sprite()
1317            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            // 使用 palette 缓存
1327            let cache_idx = (palette_addr & 0x1F) as usize;
1328            let palette_data = if self.palette_cache_dirty {
1329                // 缓存无效,重新读取并更新缓存
1330                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            // MMC3 watches the PPU A12 line itself, so nametable/attribute/garbage fetches
1415            // still drive the line low even though they must not create CHR high pulses.
1416            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;