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
177impl PPU {
178 pub fn new() -> Self {
179 Self {
180 oam: [0; 256],
181 oam_addr: 0,
182 frame: 0,
183 ctrl: 0,
184 mask: 0,
185 status: 0,
186 open_bus: 0,
187 vram_addr: 0,
188 temp_vram_addr: 0,
189 fine_x: 0,
190 write_latch: false,
191 read_buffer: 0,
192 scanline: 261,
193 cycles: 0,
194 odd_frame: false,
195 next_tile_id: 0,
196 next_tile_attr: 0,
197 next_tile_lsb: 0,
198 next_tile_msb: 0,
199 bg_pattern_shift_lo: 0,
200 bg_pattern_shift_hi: 0,
201 bg_attr_shift_lo: 0,
202 bg_attr_shift_hi: 0,
203 tv_system: TVSystem::NTSC,
204 num_scanlines: 262,
205 vblank_lines: 241,
206
207 loopy_v: 0,
208 loopy_t: 0,
209 bit_map: [0; 0xF000],
210 bg_colors: [0; 0x100],
211 bg_pixels: [0; 0x100],
212 sprite_present: [false; 0x100],
213 sprite_behind_bg: [false; 0x100],
214 scanline_sprites: [SpriteRenderData::default(); 8],
215 scanline_sprite_count: 0,
216 suppress_vblank: false,
217
218 even: false,
219 dot_clock: 0,
220
221 bg_on: false,
222 sprites_on: false,
223 rendering_on: false,
224 }
225 }
226
227 pub fn reset(&mut self) {
228 self.ctrl = 0;
229 self.mask = 0;
230 self.update_rendering_flags();
231 self.status &= !(STATUS_SPRITE_OVERFLOW | STATUS_SPRITE_ZERO_HIT | STATUS_VBLANK);
232 self.open_bus = 0;
233 self.vram_addr = 0;
234 self.temp_vram_addr = 0;
235 self.fine_x = 0;
236 self.write_latch = false;
237 self.read_buffer = 0;
238 self.scanline = 261;
239 self.cycles = 0;
240 self.frame = 0;
241 self.odd_frame = false;
242 self.next_tile_id = 0;
243 self.next_tile_attr = 0;
244 self.next_tile_lsb = 0;
245 self.next_tile_msb = 0;
246 self.bg_pattern_shift_lo = 0;
247 self.bg_pattern_shift_hi = 0;
248 self.bg_attr_shift_lo = 0;
249 self.bg_attr_shift_hi = 0;
250 self.set_current_vram_addr(0);
251 self.set_temp_vram_addr(0);
252 self.bg_pixels = [0; 0x100];
253 self.sprite_present = [false; 0x100];
254 self.sprite_behind_bg = [false; 0x100];
255 self.scanline_sprites = [SpriteRenderData::default(); 8];
256 self.scanline_sprite_count = 0;
257 self.suppress_vblank = false;
258 self.dot_clock = 0;
259 }
260
261 pub fn set_parameters(&mut self, tv_system: TVSystem) {
262 self.tv_system = tv_system;
263 match tv_system {
264 TVSystem::NTSC => {
265 self.num_scanlines = 262;
266 self.vblank_lines = 241;
267 }
268 TVSystem::PAL => {
269 self.num_scanlines = 312;
270 self.vblank_lines = 241;
271 }
272 TVSystem::DENDY => {
273 self.num_scanlines = 312;
274 self.vblank_lines = 241;
275 }
276 }
277 }
278
279 #[cfg(test)]
280 pub fn cpu_read_register(&mut self, bus: &mut impl PPUBus, addr: u16) -> u8 {
281 self.cpu_read_register_timed(bus, addr, 0)
282 }
283
284 pub fn cpu_read_register_timed(
285 &mut self,
286 bus: &mut impl PPUBus,
287 addr: u16,
288 cpu_cycle_offset: u8,
289 ) -> u8 {
290 match addr {
291 0x2002 => self.read_status_timed(cpu_cycle_offset),
292 0x2004 => {
293 let data = self.read_oam_data();
294 self.open_bus = data;
295 data
296 }
297 0x2007 => self.read_data(bus),
298 _ => self.open_bus,
299 }
300 }
301
302 #[cfg(test)]
303 pub fn cpu_write_register(&mut self, bus: &mut impl PPUBus, addr: u16, data: u8) {
304 self.cpu_write_register_timed(bus, addr, data, 0);
305 }
306
307 pub fn cpu_write_register_timed(
308 &mut self,
309 bus: &mut impl PPUBus,
310 addr: u16,
311 data: u8,
312 cpu_cycle_offset: u8,
313 ) {
314 self.open_bus = data;
315 let (future_scanline, _, _) = self.predict_status_timing(u16::from(cpu_cycle_offset) * 3);
316
317 match addr {
318 0x2000 => {
319 self.ctrl = data;
320 self.set_temp_vram_addr(
321 (self.temp_vram_addr & !0x0C00) | (((data as u16) & 0x03) << 10),
322 );
323 }
324 0x2001 => {
325 self.mask = data;
326 self.update_rendering_flags();
327 }
328 0x2003 => self.oam_addr = data,
329 0x2004 => self.write_oam_data_timed(data, future_scanline),
330 0x2005 => self.write_scroll(data),
331 0x2006 => self.write_addr(data),
332 0x2007 => self.write_data_timed(bus, data, future_scanline),
333 _ => {}
334 }
335 }
336
337 pub fn clock(&mut self, bus: &mut impl PPUBus) {
338 if self.scanline >= self.vblank_lines && self.scanline < self.num_scanlines - 1 {
340 if self.scanline == self.vblank_lines && self.cycles == 1 && !self.suppress_vblank {
341 self.status |= STATUS_VBLANK;
342 }
343 self.cycles += 1;
344 if self.cycles >= DOTS_PER_SCANLINE {
345 self.cycles = 0;
346 self.scanline += 1;
347 bus.notify_scanline(self.scanline, false);
348 }
349 self.dot_clock = self.dot_clock.wrapping_add(1);
350 return;
351 }
352
353 let visible_scanline = self.scanline < VISIBLE_SCANLINES;
354 let pre_render_scanline = self.scanline == self.num_scanlines - 1;
355 let render_scanline = visible_scanline || pre_render_scanline;
356 let visible_cycle = self.cycles < 256;
357 let fetch_cycle = visible_cycle || (320..337).contains(&self.cycles);
358
359 if self.scanline == self.num_scanlines - 1 && self.cycles == 1 {
360 self.status &= !(STATUS_SPRITE_OVERFLOW | STATUS_SPRITE_ZERO_HIT | STATUS_VBLANK);
361 self.suppress_vblank = false;
362 }
363
364 if render_scanline && self.rendering_on() {
365 if visible_scanline && self.cycles == 0 {
366 self.sprite_present = [false; 0x100];
367 self.sprite_behind_bg = [false; 0x100];
368 }
369
370 if self.bg_on() && fetch_cycle {
371 self.update_bg_shifters();
372 bus.set_ppu_sprite_phase(false);
373 self.fetch_bg(bus);
374 }
375
376 if visible_scanline && visible_cycle {
377 self.draw_bg_pixel(self.cycles as i16, bus);
378 self.draw_sprite_pixel(self.cycles as i16, bus);
379 }
380
381 match self.cycles {
382 255 => self.increment_y(),
383 256 => {
384 self.load_bg_shifters();
385 self.transfer_x();
386 if self.rendering_on() && (visible_scanline || pre_render_scanline) {
387 self.eval_sprites(bus);
388 }
389 }
390 279..=303 if pre_render_scanline => self.transfer_y(),
391 _ => {}
392 }
393
394 if (257..321).contains(&self.cycles) {
395 bus.set_ppu_sprite_phase(true);
396 self.fetch_sprite_data(bus);
397 }
398 }
399
400 if self.should_skip_odd_frame_cycle(pre_render_scanline) {
401 self.dot_clock = self.dot_clock.wrapping_add(1);
402 self.start_next_frame();
403 return;
404 }
405
406 self.cycles += 1;
407 if self.cycles >= DOTS_PER_SCANLINE {
408 self.cycles = 0;
409 self.scanline += 1;
410 bus.notify_scanline(self.scanline, self.rendering_on());
411
412 if self.scanline >= self.num_scanlines {
413 self.start_next_frame();
414 }
415 }
416 self.dot_clock = self.dot_clock.wrapping_add(1);
417 }
418
419 pub fn bg_on(&self) -> bool {
420 self.bg_on
421 }
422
423 pub fn sprites_on(&self) -> bool {
424 self.sprites_on
425 }
426
427 pub fn rendering_on(&self) -> bool {
428 self.rendering_on
429 }
430
431 fn update_rendering_flags(&mut self) {
432 self.bg_on = (self.mask & MASK_SHOW_BG) != 0;
433 self.sprites_on = (self.mask & MASK_SHOW_SPRITES) != 0;
434 self.rendering_on = self.bg_on || self.sprites_on;
435 }
436
437 pub fn nmi_line(&self) -> bool {
438 (self.ctrl & CTRL_NMI_ENABLE) != 0 && (self.status & STATUS_VBLANK) != 0
439 }
440
441 pub fn in_vblank(&self) -> bool {
442 (self.status & STATUS_VBLANK) != 0
443 }
444
445 pub fn scanline(&self) -> i16 {
446 self.scanline
447 }
448
449 pub fn frame(&self) -> u64 {
450 self.frame
451 }
452
453 pub fn frame_pixels(&self) -> &[u8] {
454 &self.bit_map[..VISIBLE_FRAME_PIXELS]
455 }
456
457 #[cfg(test)]
458 pub fn frame_rgb(&self) -> Vec<u8> {
459 let mut rgb = Vec::with_capacity(VISIBLE_FRAME_PIXELS * 3);
460 for &pixel in self.frame_pixels() {
461 rgb.extend_from_slice(&palette_index_to_rgb(pixel));
462 }
463 rgb
464 }
465
466 pub fn tv_system(&self) -> TVSystem {
467 self.tv_system
468 }
469
470 pub(crate) fn write_oam_dma(&mut self, data: u8) {
471 self.write_oam_data(data);
472 }
473
474 #[cfg(test)]
475 pub fn oam_byte(&self, index: u8) -> u8 {
476 self.oam[index as usize]
477 }
478
479 pub fn oam_addr(&self) -> u8 {
480 self.oam_addr
481 }
482
483 pub(crate) fn save_state(&self, writer: &mut StateWriter) {
484 writer.write_i16(self.scanline);
485 writer.write_u16(self.cycles);
486 writer.write_u64(self.frame);
487 writer.write_bytes(&self.oam);
488 writer.write_u8(self.oam_addr);
489 writer.write_u8(self.ctrl);
490 writer.write_u8(self.mask);
491 writer.write_u8(self.status);
492 writer.write_u8(self.open_bus);
493 writer.write_u16(self.vram_addr);
494 writer.write_u16(self.temp_vram_addr);
495 writer.write_u8(self.fine_x);
496 writer.write_bool(self.write_latch);
497 writer.write_u8(self.read_buffer);
498 writer.write_bool(self.odd_frame);
499 writer.write_u8(self.next_tile_id);
500 writer.write_u8(self.next_tile_attr);
501 writer.write_u8(self.next_tile_lsb);
502 writer.write_u8(self.next_tile_msb);
503 writer.write_u16(self.bg_pattern_shift_lo);
504 writer.write_u16(self.bg_pattern_shift_hi);
505 writer.write_u16(self.bg_attr_shift_lo);
506 writer.write_u16(self.bg_attr_shift_hi);
507 writer.write_u8(match self.tv_system {
508 TVSystem::NTSC => 0,
509 TVSystem::PAL => 1,
510 TVSystem::DENDY => 2,
511 });
512 writer.write_i16(self.num_scanlines);
513 writer.write_i16(self.vblank_lines);
514 writer.write_u16(self.loopy_v);
515 writer.write_u16(self.loopy_t);
516 writer.write_bytes(&self.bit_map);
517 writer.write_bytes(&self.bg_colors);
518 writer.write_bytes(&self.bg_pixels);
519 for &present in &self.sprite_present {
520 writer.write_bool(present);
521 }
522 for &behind in &self.sprite_behind_bg {
523 writer.write_bool(behind);
524 }
525 for sprite in &self.scanline_sprites {
526 writer.write_u8(sprite.tile_id);
527 writer.write_u8(sprite.row);
528 writer.write_u8(sprite.x);
529 writer.write_u8(sprite.attributes);
530 writer.write_u8(sprite.pattern_lo);
531 writer.write_u8(sprite.pattern_hi);
532 writer.write_bool(sprite.sprite_zero);
533 }
534 writer.write_u8(self.scanline_sprite_count);
535 writer.write_bool(self.suppress_vblank);
536 writer.write_bool(self.even);
537 writer.write_u64(self.dot_clock);
538 }
539
540 pub(crate) fn load_state(
541 &mut self,
542 reader: &mut StateReader<'_>,
543 ) -> Result<(), SaveStateError> {
544 self.scanline = reader.read_i16()?;
545 self.cycles = reader.read_u16()?;
546 self.frame = reader.read_u64()?;
547 reader.read_bytes_into(&mut self.oam)?;
548 self.oam_addr = reader.read_u8()?;
549 self.ctrl = reader.read_u8()?;
550 self.mask = reader.read_u8()?;
551 self.status = reader.read_u8()?;
552 self.open_bus = reader.read_u8()?;
553 self.vram_addr = reader.read_u16()?;
554 self.temp_vram_addr = reader.read_u16()?;
555 self.fine_x = reader.read_u8()?;
556 self.write_latch = reader.read_bool()?;
557 self.read_buffer = reader.read_u8()?;
558 self.odd_frame = reader.read_bool()?;
559 self.next_tile_id = reader.read_u8()?;
560 self.next_tile_attr = reader.read_u8()?;
561 self.next_tile_lsb = reader.read_u8()?;
562 self.next_tile_msb = reader.read_u8()?;
563 self.bg_pattern_shift_lo = reader.read_u16()?;
564 self.bg_pattern_shift_hi = reader.read_u16()?;
565 self.bg_attr_shift_lo = reader.read_u16()?;
566 self.bg_attr_shift_hi = reader.read_u16()?;
567 self.tv_system = match reader.read_u8()? {
568 0 => TVSystem::NTSC,
569 1 => TVSystem::PAL,
570 2 => TVSystem::DENDY,
571 _ => {
572 return Err(SaveStateError::InvalidData(
573 "invalid TV system in PPU state",
574 ));
575 }
576 };
577 self.num_scanlines = reader.read_i16()?;
578 self.vblank_lines = reader.read_i16()?;
579 self.loopy_v = reader.read_u16()?;
580 self.loopy_t = reader.read_u16()?;
581 reader.read_bytes_into(&mut self.bit_map)?;
582 reader.read_bytes_into(&mut self.bg_colors)?;
583 reader.read_bytes_into(&mut self.bg_pixels)?;
584 for present in &mut self.sprite_present {
585 *present = reader.read_bool()?;
586 }
587 for behind in &mut self.sprite_behind_bg {
588 *behind = reader.read_bool()?;
589 }
590 for sprite in &mut self.scanline_sprites {
591 sprite.tile_id = reader.read_u8()?;
592 sprite.row = reader.read_u8()?;
593 sprite.x = reader.read_u8()?;
594 sprite.attributes = reader.read_u8()?;
595 sprite.pattern_lo = reader.read_u8()?;
596 sprite.pattern_hi = reader.read_u8()?;
597 sprite.sprite_zero = reader.read_bool()?;
598 }
599 self.scanline_sprite_count = reader.read_u8()?;
600 self.suppress_vblank = reader.read_bool()?;
601 self.even = reader.read_bool()?;
602 self.dot_clock = reader.read_u64()?;
603 self.update_rendering_flags();
604 Ok(())
605 }
606
607 fn read_status_timed(&mut self, cpu_cycle_offset: u8) -> u8 {
608 let ppu_cycle_offset = u16::from(cpu_cycle_offset) * 3;
609 let (future_scanline, future_cycles, future_status) =
610 self.predict_status_timing(ppu_cycle_offset);
611
612 let mut status_bits = future_status;
613 if cpu_cycle_offset <= 1
617 && (status_bits & STATUS_SPRITE_ZERO_HIT) == 0
618 && self.predict_sprite_zero_hit_within_offset(ppu_cycle_offset)
619 {
620 status_bits |= STATUS_SPRITE_ZERO_HIT;
621 }
622 if future_scanline == self.vblank_lines && future_cycles == 1 {
623 status_bits &= !STATUS_VBLANK;
624 self.suppress_vblank = true;
625 }
626
627 let status = (status_bits & 0xE0) | (self.open_bus & 0x1F);
628 self.status &= !STATUS_VBLANK;
629 self.write_latch = false;
630 self.open_bus = status;
631 status
632 }
633
634 fn predict_status_timing(&self, ppu_cycle_offset: u16) -> (i16, u16, u8) {
635 let mut scanline = self.scanline;
636 let mut cycles = self.cycles;
637 let mut odd_frame = self.odd_frame;
638 let mut status = self.status;
639 let mut suppress_vblank = self.suppress_vblank;
640
641 for _ in 0..ppu_cycle_offset {
642 if scanline == self.vblank_lines && cycles == 1 && !suppress_vblank {
643 status |= STATUS_VBLANK;
644 }
645
646 if scanline == self.num_scanlines - 1 && cycles == 1 {
647 status &= !(STATUS_SPRITE_OVERFLOW | STATUS_SPRITE_ZERO_HIT | STATUS_VBLANK);
648 suppress_vblank = false;
649 }
650
651 let pre_render_scanline = scanline == self.num_scanlines - 1;
652 let skip_odd_frame_cycle = self.num_scanlines == 262
653 && pre_render_scanline
654 && self.rendering_on()
655 && odd_frame
656 && cycles == DOTS_PER_SCANLINE - 2;
657
658 if skip_odd_frame_cycle {
659 scanline = 0;
660 cycles = 0;
661 odd_frame = !odd_frame;
662 continue;
663 }
664
665 cycles += 1;
666 if cycles >= DOTS_PER_SCANLINE {
667 cycles = 0;
668 scanline += 1;
669 if scanline >= self.num_scanlines {
670 scanline = 0;
671 odd_frame = !odd_frame;
672 }
673 }
674 }
675
676 (scanline, cycles, status)
677 }
678
679 fn predict_sprite_zero_hit_within_offset(&self, ppu_cycle_offset: u16) -> bool {
680 if ppu_cycle_offset == 0
681 || !self.bg_on()
682 || !self.sprites_on()
683 || self.scanline < 0
684 || self.scanline >= VISIBLE_SCANLINES
685 {
686 return false;
687 }
688
689 let Some(sprite) = self
690 .scanline_sprites
691 .iter()
692 .take(self.scanline_sprite_count as usize)
693 .find(|sprite| sprite.sprite_zero)
694 .copied()
695 else {
696 return false;
697 };
698
699 let mut cycles = self.cycles;
700 let mut scanline = self.scanline;
701 let mut odd_frame = self.odd_frame;
702
703 let mut bg_pattern_shift_lo = self.bg_pattern_shift_lo;
704 let mut bg_pattern_shift_hi = self.bg_pattern_shift_hi;
705 let mut bg_attr_shift_lo = self.bg_attr_shift_lo;
706 let mut bg_attr_shift_hi = self.bg_attr_shift_hi;
707
708 let show_leftmost_bg = (self.mask & MASK_SHOW_BG_LEFTMOST) != 0;
709 let show_leftmost_sprites = (self.mask & MASK_SHOW_SPRITES_LEFTMOST) != 0;
710
711 for _ in 0..ppu_cycle_offset {
712 let visible_scanline = scanline < VISIBLE_SCANLINES;
713 let pre_render_scanline = scanline == self.num_scanlines - 1;
714 let render_scanline = visible_scanline || pre_render_scanline;
715 let visible_cycle = cycles < 256;
716 let fetch_cycle = visible_cycle || (320..337).contains(&cycles);
717
718 if render_scanline && self.rendering_on() {
719 if visible_scanline && visible_cycle {
720 let x = cycles as usize;
721
722 let bg_pixel = if show_leftmost_bg || x >= 8 {
723 let bit = 0x8000 >> self.fine_x;
724 let lo = u8::from((bg_pattern_shift_lo & bit) != 0);
725 let hi = u8::from((bg_pattern_shift_hi & bit) != 0);
726 (hi << 1) | lo
727 } else {
728 0
729 };
730
731 if (show_leftmost_sprites || x >= 8) && x < 255 {
732 let sprite_x = usize::from(sprite.x);
733 if x >= sprite_x && x < sprite_x + 8 {
734 let sprite_pixel = self.sprite_pixel(&sprite, (x - sprite_x) as u8);
735 if sprite_pixel != 0 && bg_pixel != 0 {
736 return true;
737 }
738 }
739 }
740 }
741
742 if self.bg_on() && fetch_cycle {
743 bg_pattern_shift_lo <<= 1;
744 bg_pattern_shift_hi <<= 1;
745 bg_attr_shift_lo <<= 1;
746 bg_attr_shift_hi <<= 1;
747 }
748
749 if self.bg_on() && fetch_cycle && (cycles & 0x07) == 0 {
750 bg_pattern_shift_lo =
751 (bg_pattern_shift_lo & 0xFF00) | u16::from(self.next_tile_lsb);
752 bg_pattern_shift_hi =
753 (bg_pattern_shift_hi & 0xFF00) | u16::from(self.next_tile_msb);
754
755 let attr_lo = if (self.next_tile_attr & 0x01) != 0 {
756 0xFF
757 } else {
758 0x00
759 };
760 let attr_hi = if (self.next_tile_attr & 0x02) != 0 {
761 0xFF
762 } else {
763 0x00
764 };
765
766 bg_attr_shift_lo = (bg_attr_shift_lo & 0xFF00) | attr_lo;
767 bg_attr_shift_hi = (bg_attr_shift_hi & 0xFF00) | attr_hi;
768 }
769 }
770
771 let skip_odd_frame_cycle = self.num_scanlines == 262
772 && pre_render_scanline
773 && self.rendering_on()
774 && odd_frame
775 && cycles == DOTS_PER_SCANLINE - 2;
776
777 if skip_odd_frame_cycle {
778 scanline = 0;
779 cycles = 0;
780 odd_frame = !odd_frame;
781 continue;
782 }
783
784 cycles += 1;
785 if cycles >= DOTS_PER_SCANLINE {
786 cycles = 0;
787 scanline += 1;
788 if scanline >= self.num_scanlines {
789 scanline = 0;
790 odd_frame = !odd_frame;
791 }
792 }
793 }
794
795 false
796 }
797
798 fn read_data(&mut self, bus: &mut impl PPUBus) -> u8 {
799 let addr = self.loopy_v & 0x3FFF;
800 let data = if addr >= 0x3F00 {
801 self.read_buffer = self.ppu_read_bus(bus, addr.wrapping_sub(0x1000));
802 let palette_data = self.ppu_read_bus(bus, addr);
803 self.mask_palette_color(palette_data)
804 } else {
805 let buffered = self.read_buffer;
806 self.read_buffer = self.ppu_read_bus_exposed(bus, addr);
807 buffered
808 };
809
810 self.increment_data_access_vram_addr();
811 self.open_bus = data;
812 data
813 }
814
815 fn write_data_timed(&mut self, bus: &mut impl PPUBus, data: u8, effective_scanline: i16) {
816 let addr = self.loopy_v & 0x3FFF;
817 self.ppu_write_bus_exposed(bus, addr, data);
818 self.increment_data_access_vram_addr_on_scanline(effective_scanline);
819 }
820
821 fn write_scroll(&mut self, data: u8) {
822 if !self.write_latch {
823 self.fine_x = data & 0x07;
824 self.set_temp_vram_addr((self.temp_vram_addr & !0x001F) | ((data as u16) >> 3));
825 self.write_latch = true;
826 } else {
827 self.set_temp_vram_addr(
828 (self.temp_vram_addr & !0x73E0)
829 | ((((data as u16) >> 3) & 0x1F) << 5)
830 | (((data as u16) & 0x07) << 12),
831 );
832 self.write_latch = false;
833 }
834 }
835
836 fn write_addr(&mut self, data: u8) {
837 if !self.write_latch {
838 self.set_temp_vram_addr((self.temp_vram_addr & 0x00FF) | (((data as u16) & 0x3F) << 8));
839 self.write_latch = true;
840 } else {
841 self.set_temp_vram_addr((self.temp_vram_addr & 0x7F00) | data as u16);
842 self.set_current_vram_addr(self.temp_vram_addr);
843 self.write_latch = false;
844 }
845 }
846
847 fn increment_vram_addr(&mut self) {
848 let increment = if (self.ctrl & CTRL_VRAM_INCREMENT) != 0 {
849 32
850 } else {
851 1
852 };
853 self.set_current_vram_addr(self.loopy_v.wrapping_add(increment));
854 }
855
856 fn increment_data_access_vram_addr_on_scanline(&mut self, scanline: i16) {
857 if self.rendering_vram_access_active_on_scanline(scanline) {
858 self.increment_x();
859 self.increment_y();
860 } else {
861 self.increment_vram_addr();
862 }
863 }
864
865 fn increment_data_access_vram_addr(&mut self) {
866 self.increment_data_access_vram_addr_on_scanline(self.scanline);
867 }
868
869 fn write_oam_data(&mut self, data: u8) {
870 self.write_oam_data_timed(data, self.scanline);
871 }
872
873 fn write_oam_data_timed(&mut self, data: u8, effective_scanline: i16) {
874 if self.rendering_oam_access_active_on_scanline(effective_scanline) {
875 self.oam_addr = self.oam_addr.wrapping_add(4);
876 return;
877 }
878
879 self.oam[self.oam_addr as usize] = data;
880 self.oam_addr = self.oam_addr.wrapping_add(1);
881 }
882
883 fn read_oam_data(&self) -> u8 {
884 if self.rendering_oam_clear_phase() {
885 return 0xFF;
886 }
887
888 let data = self.oam[self.oam_addr as usize];
889 if (self.oam_addr & 0x03) == 0x02 {
890 data & 0xE3
891 } else {
892 data
893 }
894 }
895
896 fn fetch_bg(&mut self, bus: &mut impl PPUBus) {
897 match self.cycles & 0x07 {
898 0 => {
899 self.load_bg_shifters();
900 self.fetch_nt(bus);
901 }
902 2 => {
903 let addr = 0x23C0
904 | (self.loopy_v & 0x0C00)
905 | ((self.loopy_v >> 4) & 0x38)
906 | ((self.loopy_v >> 2) & 0x07);
907 let attr = self.ppu_read_bus(bus, addr);
908 let shift = ((self.loopy_v >> 4) & 0x04) | (self.loopy_v & 0x02);
909 self.next_tile_attr = (attr >> shift) as u8 & 0x03;
910 }
911 4 => {
912 let addr = self.bg_pattern_addr(self.next_tile_id);
913 self.next_tile_lsb = self.ppu_read_bus_exposed(bus, addr);
914 }
915 6 => {
916 let addr = self.bg_pattern_addr(self.next_tile_id).wrapping_add(8);
917 self.next_tile_msb = self.ppu_read_bus_exposed(bus, addr);
918 }
919 7 => self.increment_x(),
920 _ => {}
921 }
922 }
923
924 fn fetch_nt(&mut self, bus: &mut impl PPUBus) {
925 let addr = 0x2000 | (self.loopy_v & 0x0FFF);
926 self.next_tile_id = self.ppu_read_bus(bus, addr);
927 }
928
929 fn eval_sprites(&mut self, _bus: &mut impl PPUBus) {
930 let target_scanline = if self.scanline == self.num_scanlines - 1 {
931 0
932 } else {
933 (self.scanline as u8).wrapping_add(1)
934 };
935
936 self.scanline_sprites = [SpriteRenderData::default(); 8];
937 self.scanline_sprite_count = 0;
938
939 let sprite_height = self.sprite_height();
940 let mut overflow_start = None;
941 for index in 0..64 {
942 let row = match self.sprite_row_for_scanline(index, target_scanline, sprite_height) {
943 Some(row) => row,
944 None => continue,
945 };
946
947 if self.scanline_sprite_count >= 8 {
948 overflow_start = Some(index);
949 break;
950 }
951
952 let base = index * 4;
953 let tile = self.oam[base + 1];
954 let attributes = self.oam[base + 2];
955 let x = self.oam[base + 3];
956
957 self.scanline_sprites[self.scanline_sprite_count as usize] = SpriteRenderData {
958 tile_id: tile,
959 row,
960 x,
961 attributes,
962 pattern_lo: 0,
963 pattern_hi: 0,
964 sprite_zero: index == 0,
965 };
966 self.scanline_sprite_count += 1;
967
968 if self.scanline_sprite_count >= 8 {
969 overflow_start = Some(index + 1);
970 break;
971 }
972 }
973
974 if let Some(start_index) = overflow_start {
975 if self.sprite_overflow_bugged(start_index, target_scanline, sprite_height) {
976 self.status |= STATUS_SPRITE_OVERFLOW;
977 }
978 }
979 }
980
981 fn sprite_row_for_scanline(
982 &self,
983 sprite_index: usize,
984 target_scanline: u8,
985 sprite_height: u8,
986 ) -> Option<u8> {
987 let sprite_y = self.oam[sprite_index * 4];
988 let sprite_top = if sprite_y == 0xFF {
989 0
990 } else {
991 u16::from(sprite_y) + 1
992 };
993 let target = u16::from(target_scanline);
994 if target < sprite_top {
995 None
996 } else {
997 let row = target - sprite_top;
998 if row >= u16::from(sprite_height) {
999 None
1000 } else {
1001 Some(row as u8)
1002 }
1003 }
1004 }
1005
1006 fn sprite_overflow_bugged(
1007 &self,
1008 start_index: usize,
1009 target_scanline: u8,
1010 sprite_height: u8,
1011 ) -> bool {
1012 let mut n = start_index;
1013 let mut m = 0usize;
1014 while n < 64 {
1015 let value = self.oam[n * 4 + m];
1016 let sprite_top = if value == 0xFF {
1017 0
1018 } else {
1019 u16::from(value) + 1
1020 };
1021 let target = u16::from(target_scanline);
1022 if target >= sprite_top && (target - sprite_top) < u16::from(sprite_height) {
1023 return true;
1024 }
1025
1026 n += 1;
1027 m = (m + 1) & 0x03;
1028 }
1029
1030 false
1031 }
1032
1033 fn fetch_sprite_data(&mut self, bus: &mut impl PPUBus) {
1034 let slot = ((self.cycles - 257) / 8) as usize;
1035 if slot >= 8 {
1036 return;
1037 }
1038
1039 let subcycle = (self.cycles - 257) & 0x07;
1040 match subcycle {
1041 0 | 2 => {
1042 let _ = self.ppu_read_bus(bus, 0x2000 | (self.loopy_v & 0x0FFF));
1043 }
1044 4 => {
1045 let sprite = self.scanline_sprites[slot];
1046 let addr = self.sprite_pattern_addr(sprite.tile_id, sprite.attributes, sprite.row);
1047 self.scanline_sprites[slot].pattern_lo = self.ppu_read_bus_exposed(bus, addr);
1048 }
1049 6 => {
1050 let sprite = self.scanline_sprites[slot];
1051 let addr = self
1052 .sprite_pattern_addr(sprite.tile_id, sprite.attributes, sprite.row)
1053 .wrapping_add(8);
1054 self.scanline_sprites[slot].pattern_hi = self.ppu_read_bus_exposed(bus, addr);
1055 }
1056 _ => {}
1057 }
1058 }
1059
1060 fn draw_bg_pixel(&mut self, offset: i16, bus: &mut impl PPUBus) {
1061 let x = offset as usize;
1062 let y = self.scanline as usize;
1063 if x >= 256 || y >= VISIBLE_SCANLINES as usize {
1064 return;
1065 }
1066
1067 let show_leftmost = (self.mask & MASK_SHOW_BG_LEFTMOST) != 0;
1068 let bg_pixel = if self.bg_on() && (show_leftmost || x >= 8) {
1069 self.current_bg_pixel()
1070 } else {
1071 0
1072 };
1073 let bg_palette = if bg_pixel == 0 {
1074 0
1075 } else {
1076 self.current_bg_palette()
1077 };
1078
1079 let palette_addr = if bg_pixel == 0 {
1080 0x3F00
1081 } else {
1082 0x3F00 | (u16::from(bg_palette) << 2) | u16::from(bg_pixel)
1083 };
1084 let palette_data = self.ppu_read_bus(bus, palette_addr);
1085 let color = self.mask_palette_color(palette_data);
1086 let pixel_index = y * 256 + x;
1087
1088 let sprite_in_front = self.sprite_present[x] && !self.sprite_behind_bg[x];
1089 let sprite_visible_behind_bg =
1090 self.sprite_present[x] && self.sprite_behind_bg[x] && bg_pixel == 0;
1091 if !sprite_in_front && !sprite_visible_behind_bg {
1092 self.bit_map[pixel_index] = color;
1093 }
1094 self.bg_colors[x] = color;
1095 self.bg_pixels[x] = bg_pixel;
1096 }
1097
1098 fn draw_sprite_pixel(&mut self, offset: i16, bus: &mut impl PPUBus) {
1099 if !self.sprites_on() {
1100 return;
1101 }
1102
1103 let x = offset as usize;
1104 let y = self.scanline as usize;
1105 if x >= 256 || y >= VISIBLE_SCANLINES as usize {
1106 return;
1107 }
1108
1109 if x < 8 && (self.mask & MASK_SHOW_SPRITES_LEFTMOST) == 0 {
1110 return;
1111 }
1112
1113 for sprite in self
1114 .scanline_sprites
1115 .iter()
1116 .take(self.scanline_sprite_count as usize)
1117 .copied()
1118 {
1119 let sprite_x = usize::from(sprite.x);
1120 if x < sprite_x || x >= sprite_x + 8 {
1121 continue;
1122 }
1123
1124 let sprite_pixel = self.sprite_pixel(&sprite, (x - sprite_x) as u8);
1125 if sprite_pixel == 0 {
1126 continue;
1127 }
1128
1129 let bg_pixel_for_hit = self.bg_pixel_visible_for_sprite_zero_hit(x);
1130 if sprite.sprite_zero && bg_pixel_for_hit != 0 && x < 255 {
1131 self.status |= STATUS_SPRITE_ZERO_HIT;
1132 }
1133
1134 let bg_pixel_visible = self.bg_pixel_visible_to_sprite(x);
1135 let behind_background = (sprite.attributes & 0x20) != 0;
1136 if behind_background && bg_pixel_visible != 0 {
1137 break;
1138 }
1139
1140 let palette = sprite.attributes & 0x03;
1141 let palette_addr = 0x3F10 | (u16::from(palette) << 2) | u16::from(sprite_pixel);
1142 let palette_data = self.ppu_read_bus(bus, palette_addr);
1143 let color = self.mask_palette_color(palette_data);
1144 self.bit_map[y * 256 + x] = color;
1145 self.sprite_present[x] = true;
1146 self.sprite_behind_bg[x] = behind_background;
1147 break;
1148 }
1149 }
1150
1151 fn set_current_vram_addr(&mut self, addr: u16) {
1152 self.vram_addr = addr;
1153 self.loopy_v = addr;
1154 }
1155
1156 fn set_temp_vram_addr(&mut self, addr: u16) {
1157 self.temp_vram_addr = addr;
1158 self.loopy_t = addr;
1159 }
1160
1161 fn should_skip_odd_frame_cycle(&self, pre_render_scanline: bool) -> bool {
1162 self.num_scanlines == 262
1163 && pre_render_scanline
1164 && self.rendering_on()
1165 && self.odd_frame
1166 && self.cycles == DOTS_PER_SCANLINE - 2
1167 }
1168
1169 fn rendering_vram_access_active_on_scanline(&self, scanline: i16) -> bool {
1170 self.rendering_on() && (scanline < VISIBLE_SCANLINES || scanline == self.num_scanlines - 1)
1171 }
1172
1173 fn rendering_oam_access_active(&self) -> bool {
1174 self.rendering_oam_access_active_on_scanline(self.scanline)
1175 }
1176
1177 fn rendering_oam_access_active_on_scanline(&self, scanline: i16) -> bool {
1178 self.rendering_on() && scanline < VISIBLE_SCANLINES
1179 }
1180
1181 fn rendering_oam_clear_phase(&self) -> bool {
1182 self.rendering_oam_access_active() && (1..=64).contains(&self.cycles)
1183 }
1184
1185 fn start_next_frame(&mut self) {
1186 self.scanline = 0;
1187 self.cycles = 0;
1188 self.frame += 1;
1189 self.odd_frame = !self.odd_frame;
1190 self.even = !self.even;
1191 }
1192
1193 fn ppu_read_bus(&mut self, bus: &mut impl PPUBus, addr: u16) -> u8 {
1194 let addr = addr & 0x3FFF;
1195 self.observe_mapper_a12_line(bus, addr);
1196 bus.ppu_read(addr)
1197 }
1198
1199 fn ppu_read_bus_exposed(&mut self, bus: &mut impl PPUBus, addr: u16) -> u8 {
1200 let addr = addr & 0x3FFF;
1201 self.observe_mapper_a12_line(bus, addr);
1202 bus.ppu_read(addr)
1203 }
1204
1205 fn ppu_write_bus_exposed(&mut self, bus: &mut impl PPUBus, addr: u16, data: u8) {
1206 let addr = addr & 0x3FFF;
1207 self.observe_mapper_a12_line(bus, addr);
1208 bus.ppu_write(addr, data);
1209 }
1210
1211 fn observe_mapper_a12_line(&mut self, bus: &mut impl PPUBus, addr: u16) {
1212 match addr {
1213 0x0000..=0x1FFF => bus.check_a12(addr, self.dot_clock),
1214 0x2000..=0x2FFF => bus.check_a12(0x0000, self.dot_clock),
1217 _ => {}
1218 }
1219 }
1220
1221 fn bg_pattern_addr(&self, tile_id: u8) -> u16 {
1222 let table = if (self.ctrl & CTRL_BG_TABLE) != 0 {
1223 0x1000
1224 } else {
1225 0x0000
1226 };
1227 let fine_y = (self.loopy_v >> 12) & 0x0007;
1228 table | (u16::from(tile_id) << 4) | fine_y
1229 }
1230
1231 fn sprite_pattern_addr(&self, tile_id: u8, attributes: u8, row: u8) -> u16 {
1232 let mut fine_y = u16::from(row);
1233 let sprite_height = u16::from(self.sprite_height());
1234 if (attributes & 0x80) != 0 {
1235 fine_y = sprite_height - 1 - fine_y;
1236 }
1237
1238 if sprite_height == 16 {
1239 let table = u16::from(tile_id & 0x01) << 12;
1240 let tile = u16::from(tile_id & 0xFE) + (fine_y >> 3);
1241 table | (tile << 4) | (fine_y & 0x07)
1242 } else {
1243 let table = if (self.ctrl & CTRL_SPRITE_TABLE) != 0 {
1244 0x1000
1245 } else {
1246 0x0000
1247 };
1248 table | (u16::from(tile_id) << 4) | fine_y
1249 }
1250 }
1251
1252 fn sprite_height(&self) -> u8 {
1253 if (self.ctrl & CTRL_SPRITE_SIZE) != 0 {
1254 16
1255 } else {
1256 8
1257 }
1258 }
1259
1260 fn sprite_pixel(&self, sprite: &SpriteRenderData, offset: u8) -> u8 {
1261 let bit = if (sprite.attributes & 0x40) != 0 {
1262 offset
1263 } else {
1264 7 - offset
1265 };
1266 let lo = (sprite.pattern_lo >> bit) & 0x01;
1267 let hi = (sprite.pattern_hi >> bit) & 0x01;
1268 (hi << 1) | lo
1269 }
1270
1271 fn mask_palette_color(&self, color: u8) -> u8 {
1272 let color = color & 0x3F;
1273 if (self.mask & MASK_GRAYSCALE) != 0 {
1274 color & 0x30
1275 } else {
1276 color
1277 }
1278 }
1279
1280 fn update_bg_shifters(&mut self) {
1281 if !self.bg_on() {
1282 return;
1283 }
1284
1285 self.bg_pattern_shift_lo <<= 1;
1286 self.bg_pattern_shift_hi <<= 1;
1287 self.bg_attr_shift_lo <<= 1;
1288 self.bg_attr_shift_hi <<= 1;
1289 }
1290
1291 fn load_bg_shifters(&mut self) {
1292 self.bg_pattern_shift_lo =
1293 (self.bg_pattern_shift_lo & 0xFF00) | u16::from(self.next_tile_lsb);
1294 self.bg_pattern_shift_hi =
1295 (self.bg_pattern_shift_hi & 0xFF00) | u16::from(self.next_tile_msb);
1296
1297 let attr_lo = if (self.next_tile_attr & 0x01) != 0 {
1298 0xFF
1299 } else {
1300 0x00
1301 };
1302 let attr_hi = if (self.next_tile_attr & 0x02) != 0 {
1303 0xFF
1304 } else {
1305 0x00
1306 };
1307
1308 self.bg_attr_shift_lo = (self.bg_attr_shift_lo & 0xFF00) | attr_lo;
1309 self.bg_attr_shift_hi = (self.bg_attr_shift_hi & 0xFF00) | attr_hi;
1310 }
1311
1312 fn current_bg_pixel(&self) -> u8 {
1313 let bit = 0x8000 >> self.fine_x;
1314 let lo = u8::from((self.bg_pattern_shift_lo & bit) != 0);
1315 let hi = u8::from((self.bg_pattern_shift_hi & bit) != 0);
1316 (hi << 1) | lo
1317 }
1318
1319 fn current_bg_palette(&self) -> u8 {
1320 let bit = 0x8000 >> self.fine_x;
1321 let lo = u8::from((self.bg_attr_shift_lo & bit) != 0);
1322 let hi = u8::from((self.bg_attr_shift_hi & bit) != 0);
1323 (hi << 1) | lo
1324 }
1325
1326 fn bg_pixel_visible_to_sprite(&self, x: usize) -> u8 {
1327 self.bg_pixels[x]
1331 }
1332
1333 fn bg_pixel_visible_for_sprite_zero_hit(&self, x: usize) -> u8 {
1334 if !self.bg_on() {
1335 return 0;
1336 }
1337
1338 let show_leftmost = (self.mask & MASK_SHOW_BG_LEFTMOST) != 0;
1339 if !show_leftmost && x < 8 {
1340 return 0;
1341 }
1342
1343 let bit = 0x8000 >> self.fine_x;
1344 let lo = u8::from((self.bg_pattern_shift_lo & bit) != 0);
1345 let hi = u8::from((self.bg_pattern_shift_hi & bit) != 0);
1346 (hi << 1) | lo
1347 }
1348
1349 fn increment_x(&mut self) {
1350 if !self.rendering_on() {
1351 return;
1352 }
1353
1354 if (self.loopy_v & 0x001F) == 31 {
1355 self.loopy_v &= !0x001F;
1356 self.loopy_v ^= 0x0400;
1357 } else {
1358 self.loopy_v = self.loopy_v.wrapping_add(1);
1359 }
1360
1361 self.vram_addr = self.loopy_v;
1362 }
1363
1364 fn increment_y(&mut self) {
1365 if !self.rendering_on() {
1366 return;
1367 }
1368
1369 if (self.loopy_v & 0x7000) != 0x7000 {
1370 self.loopy_v = self.loopy_v.wrapping_add(0x1000);
1371 } else {
1372 self.loopy_v &= !0x7000;
1373 let mut coarse_y = (self.loopy_v & 0x03E0) >> 5;
1374 if coarse_y == 29 {
1375 coarse_y = 0;
1376 self.loopy_v ^= 0x0800;
1377 } else if coarse_y == 31 {
1378 coarse_y = 0;
1379 } else {
1380 coarse_y += 1;
1381 }
1382 self.loopy_v = (self.loopy_v & !0x03E0) | (coarse_y << 5);
1383 }
1384
1385 self.vram_addr = self.loopy_v;
1386 }
1387
1388 fn transfer_x(&mut self) {
1389 if !self.rendering_on() {
1390 return;
1391 }
1392
1393 self.loopy_v = (self.loopy_v & !0x041F) | (self.loopy_t & 0x041F);
1394 self.vram_addr = self.loopy_v;
1395 }
1396
1397 fn transfer_y(&mut self) {
1398 if !self.rendering_on() {
1399 return;
1400 }
1401
1402 self.loopy_v = (self.loopy_v & !0x7BE0) | (self.loopy_t & 0x7BE0);
1403 self.vram_addr = self.loopy_v;
1404 }
1405}
1406
1407#[cfg(test)]
1408pub(crate) fn palette_index_to_rgb(index: u8) -> [u8; 3] {
1409 NES_RGB_PALETTE[(index & 0x3F) as usize]
1410}
1411
1412impl Default for PPU {
1413 fn default() -> Self {
1414 Self::new()
1415 }
1416}
1417
1418#[cfg(test)]
1419mod tests;