ppu.rsannotatedppu.rssource2121 lines · 80.4 KB · raw

SNES PPU (picture processing unit)

3mod colortable;
4mod debug;
5mod registers;
6mod sprites;
8use crate::api::SnesEmulatorConfig;
9use crate::ppu::registers::{
10    AccessFlipflop, BgMode, BgScreenSize, BitsPerPixel, MidScanlineUpdate, Mode7OobBehavior,
11    Registers, TileSize, VramIncrementMode,
12};
13use crate::ppu::sprites::{SpriteProcessor, SpriteState};
14use bincode::{Decode, Encode};
15use jgenesis_common::boxedarray::BoxedColorArray;
16use jgenesis_common::frontend::{
17    Color, CompositeParams, FrameSize, SamplesPerColorCycle, TimingMode,
18};
19use jgenesis_common::num::{GetBit, U16Ext};
20use std::array;
21
22const MAX_BRIGHTNESS: u8 = 15;
23
24const NORMAL_SCREEN_WIDTH: usize = 256;
25const HIRES_SCREEN_WIDTH: usize = 512;
26const MAX_SCREEN_HEIGHT: usize = 478;
27const FRAME_BUFFER_LEN: usize = HIRES_SCREEN_WIDTH * MAX_SCREEN_HEIGHT;
28
29const MAX_SPRITES_PER_LINE: usize = 32;
30const MAX_SPRITE_TILES_PER_LINE: usize = 34;
31
32const VRAM_LEN_WORDS: usize = 64 * 1024 / 2;
33const OAM_LOW_LEN_WORDS: usize = 512 / 2;
34const OAM_HIGH_LEN_BYTES: usize = 32;
35const CGRAM_LEN_WORDS: usize = 256;
36
37const VRAM_ADDRESS_MASK: u16 = (1 << 15) - 1;
38const OAM_ADDRESS_MASK: u16 = (1 << 9) - 1;
39
40pub const MCLKS_PER_NORMAL_SCANLINE: u64 = 1364;
41const MCLKS_PER_SHORT_SCANLINE: u64 = 1360;
42const MCLKS_PER_LONG_SCANLINE: u64 = 1368;
43
44type Vram = [u16; VRAM_LEN_WORDS];
45type OamLow = [u16; OAM_LOW_LEN_WORDS];
46type OamHigh = [u8; OAM_HIGH_LEN_BYTES];
47type Cgram = [u16; CGRAM_LEN_WORDS];
48
49#[derive(Debug, Clone, Encode, Decode)]
50struct State {
51    scanline: u16,
52    scanline_master_cycles: u64,
53    current_line_length: ScanlineLength,
54    prev_line_length: ScanlineLength,
55    v_mosaic_counter: u8,
56    mosaic_size_latch: u8,
57    dot_event_idx: u8,
58    odd_frame: bool,
59    pending_sprite_pixel_overflow: bool,
60    ppu1_open_bus: u8,
61    ppu2_open_bus: u8,
62    last_rendered_scanline: Option<u16>,
63    // Tracks if Mode 5/6 or pseudo-hi-res was enabled at any point during active display
64    h_hi_res_frame: bool,
65    // Tracks if interlacing was enabled at the start of the frame
66    v_hi_res_frame: bool,
67    cycle_counter: u64,
68    frame_start_cycles: u64,
69}
70
71impl State {
72    fn new() -> Self {
73        Self {
74            scanline: 0,
75            scanline_master_cycles: 0,
76            current_line_length: ScanlineLength::Normal,
77            prev_line_length: ScanlineLength::Normal,
78            v_mosaic_counter: 0,
79            mosaic_size_latch: 0,
80            dot_event_idx: 0,
81            odd_frame: false,
82            pending_sprite_pixel_overflow: false,
83            ppu1_open_bus: 0,
84            ppu2_open_bus: 0,
85            last_rendered_scanline: None,
86            h_hi_res_frame: false,
87            v_hi_res_frame: false,
88            cycle_counter: 0,
89            frame_start_cycles: 0,
90        }
91    }
92
93    fn frame_screen_width(&self) -> u32 {
94        if self.h_hi_res_frame { HIRES_SCREEN_WIDTH as u32 } else { NORMAL_SCREEN_WIDTH as u32 }
95    }
96
97    fn update_v_mosaic(&mut self, mosaic_size: u8) {
98        if self.scanline == 1 || self.v_mosaic_counter == 0 {
99            self.v_mosaic_counter = mosaic_size;
100            self.mosaic_size_latch = mosaic_size;
101        } else {
102            self.v_mosaic_counter -= 1;
103        }
104    }
105
106    fn v_mosaic_filter(&self, scanline: u16, hi_res_mode: HiResMode) -> u16 {
107        let mut offset: u16 = (self.mosaic_size_latch - self.v_mosaic_counter).into();
108        if hi_res_mode == HiResMode::True && self.v_hi_res_frame {
109            offset *= 2;
110        }
111        scanline - offset
112    }
113}
114
115#[derive(Debug, Clone, Copy, Encode, Decode)]
116struct CachedBgMapEntry {
117    map_x: u16,
118    map_y: u16,
119    tile_number: u16,
120    palette: u8,
121    priority: bool,
122    x_flip: bool,
123    y_flip: bool,
124}
125
126impl Default for CachedBgMapEntry {
127    fn default() -> Self {
128        Self {
129            map_x: u16::MAX,
130            map_y: u16::MAX,
131            tile_number: 0,
132            palette: 0,
133            priority: false,
134            x_flip: false,
135            y_flip: false,
136        }
137    }
138}
139
140#[derive(Debug, Clone, Copy, Encode, Decode)]
141struct Pixel {
142    palette: u8,
143    color: u8,
144    priority: u8,
145}
146
147impl Pixel {
148    const TRANSPARENT: Self = Self { palette: 0, color: 0, priority: 0 };
149
150    fn is_transparent(self) -> bool {
151        self.color == 0
152    }
153}
154
155#[derive(Debug, Clone, Copy, Encode, Decode)]
156struct RenderedPixel {
157    color: u16,
158    palette: u8,
159    layer: Layer,
160}
161
162impl Default for RenderedPixel {
163    fn default() -> Self {
164        Self { color: 0, palette: 0, layer: Layer::Backdrop }
165    }
166}
167
168#[derive(Debug, Clone, Encode, Decode)]
169struct Buffers {
170    bg_pixels: [[Pixel; HIRES_SCREEN_WIDTH]; 4],
171    obj_pixels: [Pixel; NORMAL_SCREEN_WIDTH],
172    offset_per_tile_h_scroll: [[u16; NORMAL_SCREEN_WIDTH]; 2],
173    offset_per_tile_v_scroll: [[u16; NORMAL_SCREEN_WIDTH]; 2],
174    main_screen_pixels: [PriorityResolver; NORMAL_SCREEN_WIDTH],
175    main_screen_rendered_pixels: [RenderedPixel; NORMAL_SCREEN_WIDTH],
176    sub_screen_pixels: [PriorityResolver; NORMAL_SCREEN_WIDTH],
177    sub_screen_rendered_pixels: [RenderedPixel; NORMAL_SCREEN_WIDTH],
178}
179
180impl Buffers {
181    fn new() -> Self {
182        Self {
183            bg_pixels: array::from_fn(|_| array::from_fn(|_| Pixel::TRANSPARENT)),
184            obj_pixels: array::from_fn(|_| Pixel::TRANSPARENT),
185            offset_per_tile_h_scroll: array::from_fn(|_| array::from_fn(|_| 0)),
186            offset_per_tile_v_scroll: array::from_fn(|_| array::from_fn(|_| 0)),
187            main_screen_pixels: array::from_fn(|_| PriorityResolver::new()),
188            main_screen_rendered_pixels: array::from_fn(|_| RenderedPixel::default()),
189            sub_screen_pixels: array::from_fn(|_| PriorityResolver::new()),
190            sub_screen_rendered_pixels: array::from_fn(|_| RenderedPixel::default()),
191        }
192    }
193}
194
195#[derive(Debug, Clone, Copy, PartialEq, Eq)]
196pub enum PpuTickEffect {
197    None,
198    FrameComplete,
199}
200
201#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
202enum Layer {
203    Bg1,
204    Bg2,
205    Bg3,
206    Bg4,
207    Obj,
208    Backdrop,
209}
210
211#[derive(Debug, Clone, Copy, Encode, Decode)]
212struct PriorityResolver {
213    min_priority: u8,
214    min_pixel: Pixel,
215    min_layer: Layer,
216}
217
218impl PriorityResolver {
219    // Mode 0-1 priorities:
220    //   OBJ.3 > BG1.1 > BG2.1 > OBJ.2 > BG1.0 > BG2.0 > OBJ.1 > BG3.1 > BG4.1 > OBJ.0 > BG3.0 > BG4.0
221    //     0  <    1   <   2   <   3   <   4   <   5   <   6   <   7   <   8   <   9   <   10  <   11
222    //   (unless in Mode 1 and the BG3 high priority flag is set, which moves BG3.1 to highest priority)
223    // Mode 2-7 priorities:
224    //   OBJ.3 > BG1.1 > OBJ.2 > BG2.1 > OBJ.1 > BG1.0 > OBJ.0 > BG2.0
225    //     0   <   1   <   2   <   3   <   4   <   5   <   6   <   7
226    //   (BG3 and BG4 are never rendered in these modes)
227    const OBJ3: u8 = 0;
228    const BG1_HIGH: u8 = 1;
229    const MODE_01_BG2_HIGH: u8 = 2;
230    const MODE_01_OBJ2: u8 = 3;
231    const MODE_01_BG1_LOW: u8 = 4;
232    const MODE_01_BG2_LOW: u8 = 5;
233    const MODE_01_OBJ1: u8 = 6;
234    const BG3_HIGH: u8 = 7;
235    const BG4_HIGH: u8 = 8;
236    const MODE_01_OBJ0: u8 = 9;
237    const BG3_LOW: u8 = 10;
238    const BG4_LOW: u8 = 11;
239
240    // OBJ.3 and BG1.1 have the same priority in modes 2-7 as in modes 0-1
241    const MODE_27_OBJ2: u8 = 2;
242    const MODE_27_BG2_HIGH: u8 = 3;
243    const MODE_27_OBJ1: u8 = 4;
244    const MODE_27_BG1_LOW: u8 = 5;
245    const MODE_27_OBJ0: u8 = 6;
246    const MODE_27_BG2_LOW: u8 = 7;
247
248    fn new() -> Self {
249        Self { min_priority: u8::MAX, min_pixel: Pixel::TRANSPARENT, min_layer: Layer::Backdrop }
250    }
251
252    fn add_bg1(&mut self, pixel: Pixel, is_mode_0_or_1: bool) {
253        let priority = match (is_mode_0_or_1, pixel.priority) {
254            (true, 0) => Self::MODE_01_BG1_LOW,
255            (false, 0) => Self::MODE_27_BG1_LOW,
256            (_, 1) => Self::BG1_HIGH,
257            _ => panic!("Invalid BG1 pixel priority: {}", pixel.priority),
258        };
259        self.add_pixel(pixel, Layer::Bg1, priority);
260    }
261
262    fn add_bg2(&mut self, pixel: Pixel, is_mode_0_or_1: bool) {
263        let priority = match (is_mode_0_or_1, pixel.priority) {
264            (true, 0) => Self::MODE_01_BG2_LOW,
265            (true, 1) => Self::MODE_01_BG2_HIGH,
266            (false, 0) => Self::MODE_27_BG2_LOW,
267            (false, 1) => Self::MODE_27_BG2_HIGH,
268            _ => panic!("Invalid BG2 pixel priority: {}", pixel.priority),
269        };
270        self.add_pixel(pixel, Layer::Bg2, priority);
271    }
272
273    fn add_bg3(&mut self, pixel: Pixel, bg3_high_priority: bool) {
274        if bg3_high_priority && pixel.priority == 1 {
275            // In mode 1, non-transparent high-priority BG3 pixels display over all other layers
276            self.min_priority = 0;
277            self.min_pixel = pixel;
278            self.min_layer = Layer::Bg3;
279            return;
280        }
281
282        let priority = if pixel.priority == 1 { Self::BG3_HIGH } else { Self::BG3_LOW };
283        self.add_pixel(pixel, Layer::Bg3, priority);
284    }
285
286    fn add_bg4(&mut self, pixel: Pixel) {
287        let priority = if pixel.priority == 1 { Self::BG4_HIGH } else { Self::BG4_LOW };
288        self.add_pixel(pixel, Layer::Bg4, priority);
289    }
290
291    fn add_obj(&mut self, pixel: Pixel, is_mode_0_or_1: bool) {
292        let priority = match (is_mode_0_or_1, pixel.priority) {
293            (true, 0) => Self::MODE_01_OBJ0,
294            (true, 1) => Self::MODE_01_OBJ1,
295            (true, 2) => Self::MODE_01_OBJ2,
296            (_, 3) => Self::OBJ3,
297            (false, 0) => Self::MODE_27_OBJ0,
298            (false, 1) => Self::MODE_27_OBJ1,
299            (false, 2) => Self::MODE_27_OBJ2,
300            _ => panic!("Invalid OBJ pixel priority: {}", pixel.priority),
301        };
302        self.add_pixel(pixel, Layer::Obj, priority);
303    }
304
305    #[inline(always)]
306    fn add_pixel(&mut self, pixel: Pixel, layer: Layer, layer_priority: u8) {
307        if layer_priority < self.min_priority {
308            self.min_priority = layer_priority;
309            self.min_pixel = pixel;
310            self.min_layer = layer;
311        }
312    }
313
314    fn get(self) -> Option<(Pixel, Layer)> {
315        (self.min_priority != u8::MAX).then_some((self.min_pixel, self.min_layer))
316    }
317}
318
319#[derive(Debug, Clone, Copy, PartialEq, Eq)]
320enum Screen {
321    Main,
322    Sub,
323}
324
325#[derive(Debug, Clone, Copy, PartialEq, Eq)]
326enum HiResMode {
327    None,
328    Pseudo,
329    True,
330}
331
332impl HiResMode {
333    fn is_hi_res(self) -> bool {
334        matches!(self, Self::Pseudo | Self::True)
335    }
336}
337
338#[derive(Debug, Clone, Encode, Decode)]
339pub struct Ppu {
340    timing_mode: TimingMode,
341    registers: Registers,
342    state: State,
343    buffers: Box<Buffers>,
344    vram: Box<Vram>,
345    oam_low: Box<OamLow>,
346    oam_high: Box<OamHigh>,
347    cgram: Box<Cgram>,
348    frame_buffer: BoxedColorArray<FRAME_BUFFER_LEN>,
349    sprites: SpriteProcessor,
350    deinterlace: bool,
351}
352
353#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
354enum ScanlineLength {
355    Normal,
356    Short,
357    Long,
358}
359
360impl ScanlineLength {
361    fn scanline_mclks(self) -> u64 {
362        match self {
363            Self::Normal => MCLKS_PER_NORMAL_SCANLINE,
364            Self::Short => MCLKS_PER_SHORT_SCANLINE,
365            Self::Long => MCLKS_PER_LONG_SCANLINE,
366        }
367    }
368}

In actual hardware, PPU starts rendering pixels at H=22 / mclk=88 Some games depend on this 88-cycle delay to finish HDMA before rendering starts, e.g. Final Fantasy 6

Here, start rendering at H=23 / mclk=92 to account for CPU writes occurring at the end of the CPU cycle rather than in the middle of it; starting at mclk=88 causes flickering in Lemmings

375const RENDER_LINE_MCLK: u64 = 92;

Used as the threshold for applying mid-scanline register writes. Ignore writes that occur during the last few pixels of active display; this fixes minor flickering in Wild Guns

380const END_RENDER_LINE_MCLK: u64 = RENDER_LINE_MCLK + 256 * 4 - 3 * 4;

Latch Mode 7 registers a bit before rendering starts Battle Clash depends on this to avoid a glitchy line where the screen transitions from Mode 1 to Mode 7

384const MODE_7_LATCH_MCLK: u64 = 40;
386impl Ppu {
387    pub fn new(timing_mode: TimingMode, config: SnesEmulatorConfig) -> Self {
388        Self {
389            timing_mode,
390            registers: Registers::new(),
391            state: State::new(),
392            buffers: Box::new(Buffers::new()),
393            vram: vec![0; VRAM_LEN_WORDS].into_boxed_slice().try_into().unwrap(),
394            oam_low: vec![0; OAM_LOW_LEN_WORDS].into_boxed_slice().try_into().unwrap(),
395            oam_high: vec![0; OAM_HIGH_LEN_BYTES].into_boxed_slice().try_into().unwrap(),
396            cgram: vec![0; CGRAM_LEN_WORDS].into_boxed_slice().try_into().unwrap(),
397            frame_buffer: BoxedColorArray::new(),
398            sprites: SpriteProcessor::new(),
399            deinterlace: config.deinterlace,
400        }
401    }
402
403    #[must_use]
404    pub fn tick(&mut self, master_cycles: u64) -> PpuTickEffect {
405        debug_assert!(master_cycles <= MCLKS_PER_SHORT_SCANLINE);
406
407        self.state.cycle_counter += master_cycles;
408
409        let new_scanline_mclks = self.state.scanline_master_cycles + master_cycles;
410        self.state.scanline_master_cycles = new_scanline_mclks;
411
412        self.advance_to_dot((new_scanline_mclks / 4) as u16);
413
414        let v_display_size = self.registers.v_display_size.to_lines();
415
416        let mclks_per_scanline = self.state.current_line_length.scanline_mclks();
417
418        let mut tick_effect = PpuTickEffect::None;
419        if new_scanline_mclks >= mclks_per_scanline {
420            self.state.scanline += 1;
421            self.state.scanline_master_cycles = new_scanline_mclks - mclks_per_scanline;
422
423            if self.state.pending_sprite_pixel_overflow {
424                self.state.pending_sprite_pixel_overflow = false;
425                self.registers.sprite_pixel_overflow = true;
426            }
427
428            // Interlaced mode adds an extra scanline every other frame
429            let scanlines_per_frame = self.scanlines_per_frame();
430            if (self.state.scanline == scanlines_per_frame
431                && (!self.registers.interlaced || self.state.odd_frame))
432                || self.state.scanline == scanlines_per_frame + 1
433            {
434                // Start new frame
435                self.state.scanline = 0;
436
437                if !self.state.v_hi_res_frame && self.registers.interlaced && !self.deinterlace {
438                    self.fix_interlaced_frame_buffer();
439                }
440
441                // TODO wait until H=1?
442                self.state.odd_frame = !self.state.odd_frame;
443                self.state.last_rendered_scanline = None;
444                self.state.h_hi_res_frame = self.registers.in_hi_res_mode();
445                self.state.v_hi_res_frame = self.registers.interlaced;
446
447                if !self.registers.forced_blanking {
448                    self.registers.sprite_overflow = false;
449                    self.registers.sprite_pixel_overflow = false;
450                }
451
452                self.state.frame_start_cycles =
453                    self.state.cycle_counter - self.state.scanline_master_cycles;
454            }
455
456            self.state.prev_line_length = self.state.current_line_length;
457            self.state.current_line_length = self.scanline_length(self.state.scanline);
458
459            self.state.update_v_mosaic(self.registers.mosaic_size);
460
461            self.sprites_finish_line();
462
463            let sprites_interlaced_odd =
464                !self.deinterlace && self.state.v_hi_res_frame && self.state.odd_frame;
465            self.sprites_start_new_line(self.state.scanline, sprites_interlaced_odd);
466
467            self.state.dot_event_idx = 0;
468            self.advance_to_dot((self.state.scanline_master_cycles / 4) as u16);
469
470            if self.state.scanline == v_display_size + 1 {
471                // Reload OAM data port address at start of VBlank if not in forced blanking
472                if !self.registers.forced_blanking {
473                    self.registers.oam_address = self.registers.oam_address_reload_value << 1;
474                }
475
476                tick_effect = PpuTickEffect::FrameComplete;
477            }
478        }
479
480        if let Some(mid_line_update) = self.registers.mid_line_update
481            && (1..=v_display_size).contains(&self.state.scanline)
482            && (RENDER_LINE_MCLK..END_RENDER_LINE_MCLK).contains(&new_scanline_mclks)
483        {
484            // Between H=22 and H=276 and INIDISP or one of the scroll registers was just modified;
485            // partially render current line
486
487            // Scroll register writes don't seem to apply immediately - see the "Good Luck"
488            // animation in Air Strike Patrol
489            let pixel_offset = match mid_line_update {
490                MidScanlineUpdate::Inidisp => 0,
491                MidScanlineUpdate::Scroll => 15,
492            };
493
494            let from_pixel = (new_scanline_mclks - RENDER_LINE_MCLK) / 4 + pixel_offset;
495            if from_pixel < NORMAL_SCREEN_WIDTH as u64 {
496                self.render_current_line(from_pixel as u16);
497            }
498        }
499
500        self.registers.mid_line_update = None;
501
502        tick_effect
503    }
504
505    fn advance_to_dot(&mut self, dot: u16) {
506        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
507        enum PpuEvent {
508            LatchMode7Registers,
509            RenderLine,
510            FinishSpriteEvaluation,
511            StartSpriteTileFetch,
512            None,
513        }
514
515        const EVENT_DOTS: &[u16; 5] = &[
516            (MODE_7_LATCH_MCLK / 4) as u16,
517            (RENDER_LINE_MCLK / 4) as u16,
518            sprites::SPRITE_EVALUATION_END_DOT,
519            sprites::SPRITE_FETCH_START_DOT,
520            u16::MAX,
521        ];
522
523        const EVENTS: &[PpuEvent; 5] = &[
524            PpuEvent::LatchMode7Registers,
525            PpuEvent::RenderLine,
526            PpuEvent::FinishSpriteEvaluation,
527            PpuEvent::StartSpriteTileFetch,
528            PpuEvent::None,
529        ];
530
531        debug_assert!(EVENT_DOTS.is_sorted());
532
533        while dot >= EVENT_DOTS[self.state.dot_event_idx as usize] {
534            match EVENTS[self.state.dot_event_idx as usize] {
535                PpuEvent::LatchMode7Registers => self.registers.latch_mode_7(),
536                PpuEvent::RenderLine => {
537                    let v_display_size = self.registers.v_display_size.to_lines();
538                    let is_active_scanline = (1..=v_display_size).contains(&self.state.scanline);
539                    if is_active_scanline {
540                        self.render_current_line(0);
541                    }
542                }
543                PpuEvent::FinishSpriteEvaluation => {
544                    self.progress_sprite_evaluation(sprites::SPRITE_EVALUATION_END_DOT);
545                }
546                PpuEvent::StartSpriteTileFetch => self.begin_sprite_tile_fetch(),
547                PpuEvent::None => {}
548            }
549
550            self.state.dot_event_idx += 1;
551        }
552    }
553
554    fn render_current_line(&mut self, from_pixel: u16) {
555        let scanline = self.state.scanline;
556        self.state.last_rendered_scanline = Some(scanline);
557
558        if self.registers.forced_blanking {
559            // Forced blanking always draws black
560            let from_pixel = if self.state.h_hi_res_frame { 2 * from_pixel } else { from_pixel };
561            let screen_width = self.state.frame_screen_width();
562
563            if self.state.v_hi_res_frame {
564                for y in [2 * scanline - 1, 2 * scanline] {
565                    for pixel in from_pixel..screen_width as u16 {
566                        self.set_in_frame_buffer(y, pixel, Color::BLACK);
567                    }
568                }
569            } else {
570                for pixel in from_pixel..screen_width as u16 {
571                    self.set_in_frame_buffer(scanline, pixel, Color::BLACK);
572                }
573            }
574
575            return;
576        }
577
578        let hi_res_mode = if self.registers.bg_mode.is_hi_res() {
579            HiResMode::True
580        } else if self.registers.pseudo_h_hi_res {
581            HiResMode::Pseudo
582        } else {
583            HiResMode::None
584        };
585
586        let bg_from_pixel =
587            if hi_res_mode == HiResMode::True { 2 * from_pixel } else { from_pixel };
588        let screen_from_pixel = if hi_res_mode.is_hi_res() { 2 * from_pixel } else { from_pixel };
589        let v_hi_res = hi_res_mode == HiResMode::True && self.registers.interlaced;
590
591        if self.state.v_hi_res_frame && v_hi_res {
592            // Vertical hi-res, 448px
593            if self.deinterlace || !self.state.odd_frame {
594                // Render even line
595                self.render_bg_layers_to_buffer(2 * scanline - 1, hi_res_mode, bg_from_pixel);
596                self.render_scanline(2 * scanline - 1, hi_res_mode, screen_from_pixel);
597            }
598
599            if self.deinterlace || self.state.odd_frame {
600                // Render odd line
601                if self.deinterlace && self.registers.pseudo_obj_hi_res {
602                    self.render_sprite_tiles();
603                }
604
605                self.render_bg_layers_to_buffer(2 * scanline, hi_res_mode, bg_from_pixel);
606                self.render_scanline(2 * scanline, hi_res_mode, screen_from_pixel);
607            }
608        } else if !self.state.v_hi_res_frame && v_hi_res {
609            // Probably should never happen - PPU is in 448px mode but interlacing was disabled at
610            // start of frame
611            let y = if self.state.odd_frame { 2 * scanline } else { 2 * scanline - 1 };
612
613            self.render_bg_layers_to_buffer(y, hi_res_mode, bg_from_pixel);
614            self.render_scanline(scanline, hi_res_mode, screen_from_pixel);
615        } else if self.state.v_hi_res_frame {
616            // Interlacing was enabled at start of frame - duplicate lines
617            if !self.deinterlace {
618                let odd_frame: u16 = self.state.odd_frame.into();
619                self.render_bg_layers_to_buffer(scanline, hi_res_mode, screen_from_pixel);
620                self.render_scanline(2 * scanline - 1 + odd_frame, hi_res_mode, screen_from_pixel);
621            } else {
622                // Render even line
623                self.render_bg_layers_to_buffer(scanline, hi_res_mode, bg_from_pixel);
624                self.render_scanline(2 * scanline - 1, hi_res_mode, screen_from_pixel);
625
626                // Duplicate to odd line, re-rendering OBJs if necessary
627                if self.registers.pseudo_obj_hi_res {
628                    self.render_sprite_tiles();
629                    self.render_scanline(2 * scanline, hi_res_mode, screen_from_pixel);
630                } else {
631                    self.duplicate_line(
632                        (2 * scanline - 1).into(),
633                        (2 * scanline).into(),
634                        screen_from_pixel.into(),
635                    );
636                }
637            }
638        } else {
639            // Interlacing is disabled, render normally
640            self.render_bg_layers_to_buffer(scanline, hi_res_mode, bg_from_pixel);
641            self.render_scanline(scanline, hi_res_mode, screen_from_pixel);
642        }
643    }
644
645    fn render_bg_layers_to_buffer(
646        &mut self,
647        scanline: u16,
648        hi_res_mode: HiResMode,
649        from_pixel: u16,
650    ) {
651        let mode = self.registers.bg_mode;
652
653        if mode == BgMode::Seven {
654            self.render_mode_7_to_buffer(scanline, from_pixel);
655            return;
656        }
657
658        let bg1_enabled = self.registers.main_bg_enabled[0] || self.registers.sub_bg_enabled[0];
659        let bg2_enabled = mode.bg2_enabled(false)
660            && (self.registers.main_bg_enabled[1] || self.registers.sub_bg_enabled[1]);
661        let bg3_enabled = mode.bg3_enabled()
662            && (self.registers.main_bg_enabled[2] || self.registers.sub_bg_enabled[2]);
663        let bg4_enabled = mode.bg4_enabled()
664            && (self.registers.main_bg_enabled[3] || self.registers.sub_bg_enabled[3]);
665
666        if mode.is_offset_per_tile() && (bg1_enabled || bg2_enabled) {
667            // Populate offset-per-tile buffers before rendering BG1 or BG2
668            self.populate_offset_per_tile_buffers();
669        }
670
671        if bg1_enabled {
672            self.render_bg_to_buffer(0, scanline, hi_res_mode, from_pixel);
673        }
674
675        if bg2_enabled {
676            self.render_bg_to_buffer(1, scanline, hi_res_mode, from_pixel);
677        }
678
679        if bg3_enabled {
680            self.render_bg_to_buffer(2, scanline, hi_res_mode, from_pixel);
681        }
682
683        if bg4_enabled {
684            self.render_bg_to_buffer(3, scanline, hi_res_mode, from_pixel);
685        }
686    }
687
688    fn render_bg_to_buffer(
689        &mut self,
690        bg: usize,
691        scanline: u16,
692        hi_res_mode: HiResMode,
693        from_pixel: u16,
694    ) {
695        let screen_width =
696            if hi_res_mode == HiResMode::True { HIRES_SCREEN_WIDTH } else { NORMAL_SCREEN_WIDTH };
697
698        let mode = self.registers.bg_mode;
699        let bpp = match bg {
700            0 => mode.bg1_bpp(),
701            1 => mode.bg2_bpp(),
702            2 => BitsPerPixel::BG3,
703            3 => BitsPerPixel::BG4,
704            _ => panic!("invalid BG layer: {bg}"),
705        };
706
707        let bg_h_scroll = self.registers.bg_h_scroll[bg];
708        let bg_v_scroll = self.registers.bg_v_scroll[bg];
709
710        let mut bg_map_entry = CachedBgMapEntry::default();
711
712        for pixel_idx in from_pixel..screen_width as u16 {
713            // Apply mosaic if enabled
714            let (base_y, mosaic_x) = self.apply_mosaic(bg, scanline, pixel_idx, hi_res_mode);
715            if mosaic_x != pixel_idx {
716                // Mosaic is enabled and this is not the far left pixel; copy last pixel and move on
717                self.buffers.bg_pixels[bg][pixel_idx as usize] =
718                    self.buffers.bg_pixels[bg][(pixel_idx - 1) as usize];
719                continue;
720            }
721
722            // Account for offset-per-tile if in mode 2/4/6
723            let (mut h_scroll, v_scroll) = if mode.is_offset_per_tile() {
724                let buffer_idx = match hi_res_mode {
725                    HiResMode::True => (pixel_idx / 2) as usize,
726                    HiResMode::None | HiResMode::Pseudo => pixel_idx as usize,
727                };
728                (
729                    self.buffers.offset_per_tile_h_scroll[bg][buffer_idx],
730                    self.buffers.offset_per_tile_v_scroll[bg][buffer_idx],
731                )
732            } else {
733                (bg_h_scroll, bg_v_scroll)
734            };
735
736            if hi_res_mode == HiResMode::True {
737                // H scroll values are effectively doubled in mode 5/6
738                h_scroll <<= 1;
739            }
740
741            // Apply scroll values
742            let x = pixel_idx.wrapping_add(h_scroll);
743            let y = base_y.wrapping_add(v_scroll);
744
745            // Retrieve background map entry (if different from the last pixel's)
746            // BG tiles can be larger than 8x8 but that doesn't matter here since this is just to
747            // avoid doing a BG map lookup on every pixel
748            if x / 8 != bg_map_entry.map_x || y / 8 != bg_map_entry.map_y {
749                let raw_entry = get_bg_map_entry(&self.vram, &self.registers, bg, x, y);
750                bg_map_entry = CachedBgMapEntry {
751                    map_x: x / 8,
752                    map_y: y / 8,
753                    tile_number: raw_entry & 0x3FF,
754                    palette: ((raw_entry >> 10) & 0x07) as u8,
755                    priority: raw_entry.bit(13),
756                    x_flip: raw_entry.bit(14),
757                    y_flip: raw_entry.bit(15),
758                };
759            }
760
761            // Retrieve tile bytes from VRAM
762            let tile_data = get_bg_tile(
763                &self.vram,
764                &self.registers,
765                bg,
766                x,
767                y,
768                bpp,
769                bg_map_entry.tile_number,
770                bg_map_entry.x_flip,
771                bg_map_entry.y_flip,
772            );
773
774            let tile_row = if bg_map_entry.y_flip { 7 - (y % 8) } else { y % 8 };
775            let tile_col = if bg_map_entry.x_flip { 7 - (x % 8) } else { x % 8 };
776            let bit_index = (7 - tile_col) as u8;
777
778            // Parse color bits out of bitplane tile data
779            let mut color = 0_u8;
780            for plane in (0..bpp.bitplanes()).step_by(2) {
781                let word_index = tile_row as usize + 4 * plane;
782                let word = tile_data[word_index];
783
784                color |= u8::from(word.bit(bit_index)) << plane;
785                color |= u8::from(word.bit(bit_index + 8)) << (plane + 1);
786            }
787
788            let pixel = Pixel {
789                palette: bg_map_entry.palette,
790                color,
791                priority: bg_map_entry.priority.into(),
792            };
793            self.buffers.bg_pixels[bg][pixel_idx as usize] = pixel;
794        }
795    }
796
797    fn render_mode_7_to_buffer(&mut self, scanline: u16, from_pixel: u16) {
798        // Mode 7 tile map is always 128x128
799        const TILE_MAP_SIZE_PIXELS: i32 = 128 * 8;
800
801        fn clip_i11(value: i32) -> i32 {
802            // This is intentionally not equivalent to `(value << (32 - 11)) >> (32 - 11)`
803            //
804            // Some games depend on using the magnitude from the lowest 10 bits but using the sign
805            // of the full i14 value instead of clipping to i11.
806            // e.g. Kaite Tsukutte Asoberu Dezaemon, title screen animation
807            // e.g. Tiny Toon Adventures: Wacky Sports Challenge, the birdman event
808            let magnitude = value & 0x03FF;
809            let sign = (value >> 31) & !0x03FF;
810            magnitude | sign
811        }
812
813        fn sign_extend_i13(value: u16) -> i32 {
814            (((value as i16) << 3) >> 3).into()
815        }
816
817        // Several Mode 7 intermediate results truncate the lowest 6 bits
818        fn truncate_intermediate(value: i32) -> i32 {
819            value & !0x3F
820        }
821
822        // Affine transformation parameters (fixed point, 1/256 pixel units)
823        let m7a: i32 = (self.registers.mode_7_latched.parameter_a as i16).into();
824        let m7b: i32 = (self.registers.mode_7_latched.parameter_b as i16).into();
825        let m7c: i32 = (self.registers.mode_7_latched.parameter_c as i16).into();
826        let m7d: i32 = (self.registers.mode_7_latched.parameter_d as i16).into();
827
828        // Center of rotation
829        let m7x = sign_extend_i13(self.registers.mode_7_latched.center_x);
830        let m7y = sign_extend_i13(self.registers.mode_7_latched.center_y);
831
832        let h_scroll = sign_extend_i13(self.registers.mode_7_latched.h_scroll);
833        let v_scroll = sign_extend_i13(self.registers.mode_7_latched.v_scroll);
834
835        let h_flip = self.registers.mode_7_latched.h_flip;
836        let v_flip = self.registers.mode_7_latched.v_flip;
837
838        let oob_behavior = self.registers.mode_7_latched.oob_behavior;
839
840        for pixel in from_pixel..NORMAL_SCREEN_WIDTH as u16 {
841            let (base_y, mosaic_x) = self.apply_mosaic(0, scanline, pixel, HiResMode::None);
842            if mosaic_x != pixel {
843                // Copy last pixel and move on
844                self.buffers.bg_pixels[0][pixel as usize] =
845                    self.buffers.bg_pixels[0][(pixel - 1) as usize];
846                continue;
847            }
848
849            let screen_x: i32 = (if h_flip { 255 - pixel } else { pixel }).into();
850            let screen_y: i32 = (if v_flip { 255 - base_y } else { base_y }).into();
851
852            // Perform the following matrix transformation (logically):
853            //   [ vram_x ] = [ m7a  m7b ] * [ screen_x + m7hofs - m7x ] + [ m7x ]
854            //   [ vram_y ]   [ m7c  m7d ]   [ screen_y + m7vofs - m7y ]   [ m7y ]
855            // m7a/m7b/m7c/m7d are in 1/256 pixel units, so the multiplication result is also in
856            // 1/256 pixel units, and m7x/m7y need to be converted for the addition
857            let scrolled_center_x = clip_i11(h_scroll - m7x);
858            let scrolled_center_y = clip_i11(v_scroll - m7y);
859
860            let mut tile_map_x = truncate_intermediate(m7a * scrolled_center_x)
861                + m7a * screen_x
862                + truncate_intermediate(m7b * scrolled_center_y)
863                + truncate_intermediate(m7b * screen_y)
864                + (m7x << 8);
865            let mut tile_map_y = truncate_intermediate(m7c * scrolled_center_x)
866                + m7c * screen_x
867                + truncate_intermediate(m7d * scrolled_center_y)
868                + truncate_intermediate(m7d * screen_y)
869                + (m7y << 8);
870
871            // Convert back from 1/256 pixel units to pixel units
872            tile_map_x >>= 8;
873            tile_map_y >>= 8;
874
875            let mut force_tile_0 = false;
876            if tile_map_x < 0
877                || tile_map_y < 0
878                || tile_map_x >= TILE_MAP_SIZE_PIXELS
879                || tile_map_y >= TILE_MAP_SIZE_PIXELS
880            {
881                match oob_behavior {
882                    Mode7OobBehavior::Wrap => {
883                        tile_map_x &= TILE_MAP_SIZE_PIXELS - 1;
884                        tile_map_y &= TILE_MAP_SIZE_PIXELS - 1;
885                    }
886                    Mode7OobBehavior::Transparent => {
887                        self.buffers.bg_pixels[0][pixel as usize] = Pixel::TRANSPARENT;
888                        continue;
889                    }
890                    Mode7OobBehavior::Tile0 => {
891                        tile_map_x &= 0x07;
892                        tile_map_y &= 0x07;
893                        force_tile_0 = true;
894                    }
895                }
896            }
897
898            let tile_number = if force_tile_0 {
899                0
900            } else {
901                // Mode 7 tile map is always located at $0000
902                let tile_map_row = tile_map_y / 8;
903                let tile_map_col = tile_map_x / 8;
904                let tile_map_addr = tile_map_row * TILE_MAP_SIZE_PIXELS / 8 + tile_map_col;
905                self.vram[tile_map_addr as usize] & 0x00FF
906            };
907
908            let tile_row = (tile_map_y % 8) as u16;
909            let tile_col = (tile_map_x % 8) as u16;
910            let pixel_addr = 64 * tile_number + 8 * tile_row + tile_col;
911            let color = self.vram[pixel_addr as usize].msb();
912
913            self.buffers.bg_pixels[0][pixel as usize] = Pixel { palette: 0, color, priority: 0 };
914        }
915    }
916
917    fn populate_offset_per_tile_buffers(&mut self) {
918        // Offset-per-tile does not apply to first visible BG1/BG2 tile (1-8 pixels)
919        for bg in 0..2 {
920            let bg_h_scroll = self.registers.bg_h_scroll[bg];
921            let bg_v_scroll = self.registers.bg_v_scroll[bg];
922
923            for pixel in 0..(8 - (bg_h_scroll & 7)) {
924                self.buffers.offset_per_tile_h_scroll[bg][pixel as usize] = bg_h_scroll;
925                self.buffers.offset_per_tile_v_scroll[bg][pixel as usize] = bg_v_scroll;
926            }
927        }
928
929        let mode = self.registers.bg_mode;
930        let bg3_h_scroll = self.registers.bg_h_scroll[2];
931        let bg3_v_scroll = self.registers.bg_v_scroll[2];
932
933        // Up to 33 8x8 tiles can be visible when fine H scrolling is used
934        for tile_idx in 0..33_u16 {
935            // Lowest 3 bits of BG3 H scroll do not apply in offset-per-tile
936            let bg3_x = (8 * tile_idx).wrapping_add(bg3_h_scroll & !7);
937
938            let (h_offset_entry, v_offset_entry) = match mode {
939                BgMode::Four => {
940                    // In Mode 4, instead of loading both map entries, the PPU uses the highest bit
941                    // of the first entry to determine whether to apply offset to H or V
942                    let bg3_entry =
943                        get_bg_map_entry(&self.vram, &self.registers, 2, bg3_x, bg3_v_scroll);
944                    if bg3_entry.bit(15) {
945                        // Apply to V scroll
946                        (0, bg3_entry)
947                    } else {
948                        // Apply to H scroll
949                        (bg3_entry, 0)
950                    }
951                }
952                _ => {
953                    let h_offset_entry =
954                        get_bg_map_entry(&self.vram, &self.registers, 2, bg3_x, bg3_v_scroll);
955                    let v_offset_entry = get_bg_map_entry(
956                        &self.vram,
957                        &self.registers,
958                        2,
959                        bg3_x,
960                        bg3_v_scroll.wrapping_add(8),
961                    );
962                    (h_offset_entry, v_offset_entry)
963                }
964            };
965
966            for bg in 0..2 {
967                let bg_h_scroll = self.registers.bg_h_scroll[bg];
968                let bg_v_scroll = self.registers.bg_v_scroll[bg];
969
970                let use_offset_bit = [13, 14][bg];
971                let h_scroll = if h_offset_entry.bit(use_offset_bit) {
972                    // TODO is this right for fine H scroll?
973                    (h_offset_entry & 0x03FF & !7) | (bg_h_scroll & 7)
974                } else {
975                    bg_h_scroll
976                };
977                let v_scroll = if v_offset_entry.bit(use_offset_bit) {
978                    v_offset_entry & 0x03FF
979                } else {
980                    bg_v_scroll
981                };
982
983                // BG3 tile N is used as offsets for BG1/BG2 visible tile N+1
984                let base_x = (8 * (tile_idx + 1)).wrapping_sub(bg_h_scroll & 7);
985                for dx in 0..8 {
986                    let pixel = base_x + dx;
987                    if pixel >= NORMAL_SCREEN_WIDTH as u16 {
988                        break;
989                    }
990
991                    self.buffers.offset_per_tile_h_scroll[bg][pixel as usize] = h_scroll;
992                    self.buffers.offset_per_tile_v_scroll[bg][pixel as usize] = v_scroll;
993                }
994            }
995        }
996    }
997
998    fn render_scanline(&mut self, scanline: u16, hi_res_mode: HiResMode, screen_from_pixel: u16) {
999        // Main screen is always rendered
1000        self.render_screen_pixels(Screen::Main, hi_res_mode);
1001
1002        // Sub screen is rendered if in a hi-res mode (which causes even pixels to display the sub screen) OR color math
1003        // is enabled for at least one layer and the sub screen is not forced to the subbackdrop color.
1004        if hi_res_mode.is_hi_res()
1005            || (self.registers.sub_bg_obj_enabled
1006                && self.registers.color_math_enabled_for_any_layer())
1007        {
1008            self.render_screen_pixels(Screen::Sub, hi_res_mode);
1009        }
1010
1011        let screen_width =
1012            if hi_res_mode.is_hi_res() { HIRES_SCREEN_WIDTH } else { NORMAL_SCREEN_WIDTH };
1013
1014        let brightness = self.registers.brightness;
1015        let main_backdrop_pixel =
1016            RenderedPixel { palette: 0, color: self.cgram[0], layer: Layer::Backdrop };
1017        let sub_backdrop_color = self.registers.sub_backdrop_color;
1018
1019        for pixel in screen_from_pixel..screen_width as u16 {
1020            let screen_x = match hi_res_mode {
1021                HiResMode::None => pixel,
1022                HiResMode::Pseudo | HiResMode::True => pixel / 2,
1023            };
1024
1025            let mut main_screen_pixel = if hi_res_mode.is_hi_res() && !pixel.bit(0) {
1026                // Even pixels draw the sub screen in hi-res mode
1027                // If all sub screen pixels are transparent, draw the main backdrop color
1028                let sub_pixel = self.buffers.sub_screen_rendered_pixels[screen_x as usize];
1029                if sub_pixel.layer == Layer::Backdrop { main_backdrop_pixel } else { sub_pixel }
1030            } else {
1031                self.buffers.main_screen_rendered_pixels[screen_x as usize]
1032            };
1033
1034            // Check if inside the color window (used for clipping and color math)
1035            let in_color_window = self.registers.in_math_window(screen_x);
1036
1037            let force_main_screen_black =
1038                self.registers.force_main_screen_black.enabled(in_color_window);
1039            if force_main_screen_black {
1040                // Pixel is clipped; force color to 0 (black)
1041                main_screen_pixel.color = 0;
1042            }
1043
1044            // Check if color math is enabled globally and for this layer
1045            let color_math_enabled_global =
1046                self.registers.color_math_enabled.enabled(in_color_window);
1047
1048            let color_math_enabled_layer = match main_screen_pixel.layer {
1049                Layer::Bg1 => self.registers.bg_color_math_enabled[0],
1050                Layer::Bg2 => self.registers.bg_color_math_enabled[1],
1051                Layer::Bg3 => self.registers.bg_color_math_enabled[2],
1052                Layer::Bg4 => self.registers.bg_color_math_enabled[3],
1053                Layer::Obj => {
1054                    self.registers.obj_color_math_enabled && main_screen_pixel.palette >= 4
1055                }
1056                Layer::Backdrop => self.registers.backdrop_color_math_enabled,
1057            };
1058
1059            let snes_color = if color_math_enabled_global && color_math_enabled_layer {
1060                // Find the frontmost sub screen pixel
1061                let (sub_screen_color, sub_transparent) = if self.registers.sub_bg_obj_enabled {
1062                    let pixel = self.buffers.sub_screen_rendered_pixels[screen_x as usize];
1063                    (pixel.color, pixel.layer == Layer::Backdrop)
1064                } else {
1065                    (sub_backdrop_color, false)
1066                };
1067
1068                // Apply color math to the main and sub screen pixels
1069                // Division only applies if the main pixel was not clipped and the sub pixel is not
1070                // transparent
1071                let divide = self.registers.color_math_divide_enabled
1072                    && !force_main_screen_black
1073                    && !sub_transparent;
1074                self.registers.color_math_operation.apply(
1075                    main_screen_pixel.color,
1076                    sub_screen_color,
1077                    divide,
1078                )
1079            } else {
1080                main_screen_pixel.color
1081            };
1082
1083            let final_color = convert_snes_color(snes_color, brightness);
1084
1085            if self.state.h_hi_res_frame && !hi_res_mode.is_hi_res() {
1086                // Hi-res mode is not currently enabled, but it was enabled earlier in the frame;
1087                // draw in 512px
1088                self.set_in_frame_buffer(scanline, 2 * pixel, final_color);
1089                self.set_in_frame_buffer(scanline, 2 * pixel + 1, final_color);
1090            } else {
1091                self.set_in_frame_buffer(scanline, pixel, final_color);
1092            }
1093        }
1094    }
1095
1096    fn render_screen_pixels(&mut self, screen: Screen, hi_res_mode: HiResMode) {
1097        #[inline(always)]
1098        fn apply_screen_shift(x: usize, shift: i32, offset: usize) -> u16 {
1099            ((x << shift) | offset) as u16
1100        }
1101
1102        let (
1103            screen_pixels,
1104            screen_rendered_pixels,
1105            bg_enabled,
1106            bg_disabled_in_window,
1107            obj_enabled,
1108            obj_disabled_in_window,
1109            backdrop_color,
1110        ) = match screen {
1111            Screen::Main => (
1112                &mut self.buffers.main_screen_pixels,
1113                &mut self.buffers.main_screen_rendered_pixels,
1114                self.registers.main_bg_enabled,
1115                self.registers.main_bg_disabled_in_window,
1116                self.registers.main_obj_enabled,
1117                self.registers.main_obj_disabled_in_window,
1118                self.cgram[0],
1119            ),
1120            Screen::Sub => (
1121                &mut self.buffers.sub_screen_pixels,
1122                &mut self.buffers.sub_screen_rendered_pixels,
1123                self.registers.sub_bg_enabled,
1124                self.registers.sub_bg_disabled_in_window,
1125                self.registers.sub_obj_enabled,
1126                self.registers.sub_obj_disabled_in_window,
1127                self.registers.sub_backdrop_color,
1128            ),
1129        };
1130
1131        screen_pixels.fill(PriorityResolver::new());
1132
1133        let screen_x_shift = match hi_res_mode {
1134            HiResMode::True => 1,
1135            HiResMode::None | HiResMode::Pseudo => 0,
1136        };
1137        let screen_x_offset = match (hi_res_mode, screen) {
1138            (HiResMode::None | HiResMode::Pseudo, _) | (HiResMode::True, Screen::Sub) => 0,
1139            (HiResMode::True, Screen::Main) => 1,
1140        };
1141
1142        let mode = self.registers.bg_mode;
1143        let is_mode_0_or_1 = matches!(mode, BgMode::Zero | BgMode::One);
1144
1145        // OBJ layer (enabled in all modes)
1146        if obj_enabled {
1147            for (x, priority_resolver) in screen_pixels.iter_mut().enumerate() {
1148                if obj_disabled_in_window {
1149                    let screen_x = apply_screen_shift(x, screen_x_shift, screen_x_offset);
1150                    let obj_in_window = self.registers.obj_in_window(screen_x);
1151                    if obj_in_window {
1152                        continue;
1153                    }
1154                }
1155
1156                let obj_pixel = self.buffers.obj_pixels[x];
1157                if !obj_pixel.is_transparent() {
1158                    priority_resolver.add_obj(obj_pixel, is_mode_0_or_1);
1159                }
1160            }
1161        }
1162
1163        // BG1 layer (enabled in all modes)
1164        if bg_enabled[0] {
1165            for (x, priority_resolver) in screen_pixels.iter_mut().enumerate() {
1166                let screen_x = apply_screen_shift(x, screen_x_shift, screen_x_offset);
1167
1168                if bg_disabled_in_window[0] {
1169                    let bg1_in_window = self.registers.bg_in_window(0, screen_x);
1170                    if bg1_in_window {
1171                        continue;
1172                    }
1173                }
1174
1175                let bg1_pixel = self.buffers.bg_pixels[0][screen_x as usize];
1176                if !bg1_pixel.is_transparent() {
1177                    priority_resolver.add_bg1(bg1_pixel, is_mode_0_or_1);
1178                }
1179            }
1180        }
1181
1182        // BG2 layer (enabled in all modes except 6 and 7)
1183        if mode.bg2_enabled(self.registers.extbg_enabled) && bg_enabled[1] {
1184            for (x, priority_resolver) in screen_pixels.iter_mut().enumerate() {
1185                let screen_x = apply_screen_shift(x, screen_x_shift, screen_x_offset);
1186
1187                if bg_disabled_in_window[1] {
1188                    let bg2_in_window = self.registers.bg_in_window(1, screen_x);
1189                    if bg2_in_window {
1190                        continue;
1191                    }
1192                }
1193
1194                match mode {
1195                    BgMode::Seven => {
1196                        // When EXTBG is enabled in Mode 7, BG1 pixels are duplicated into BG2 but
1197                        // use the highest color bit as priority
1198                        let bg1_pixel = self.buffers.bg_pixels[0][screen_x as usize];
1199                        let bg2_pixel_color = bg1_pixel.color & 0x7F;
1200                        if bg2_pixel_color == 0 {
1201                            // Transparent
1202                            continue;
1203                        }
1204
1205                        let bg2_priority = bg1_pixel.color >> 7;
1206                        priority_resolver.add_bg2(
1207                            Pixel { priority: bg2_priority, color: bg2_pixel_color, palette: 0 },
1208                            false,
1209                        );
1210                    }
1211                    _ => {
1212                        let bg2_pixel = self.buffers.bg_pixels[1][screen_x as usize];
1213                        if !bg2_pixel.is_transparent() {
1214                            priority_resolver.add_bg2(bg2_pixel, is_mode_0_or_1);
1215                        }
1216                    }
1217                }
1218            }
1219        }
1220
1221        // BG3 layer (enabled in modes 0 and 1)
1222        if mode.bg3_enabled() && bg_enabled[2] {
1223            let bg3_high_priority = mode == BgMode::One && self.registers.mode_1_bg3_priority;
1224
1225            for (x, priority_resolver) in screen_pixels.iter_mut().enumerate() {
1226                let screen_x = apply_screen_shift(x, screen_x_shift, screen_x_offset);
1227
1228                if bg_disabled_in_window[2] {
1229                    let bg3_in_window = self.registers.bg_in_window(2, screen_x);
1230                    if bg3_in_window {
1231                        continue;
1232                    }
1233                }
1234
1235                let bg3_pixel = self.buffers.bg_pixels[2][screen_x as usize];
1236                if !bg3_pixel.is_transparent() {
1237                    priority_resolver.add_bg3(bg3_pixel, bg3_high_priority);
1238                }
1239            }
1240        }
1241
1242        // BG4 layer (enabled in mode 0 only)
1243        if mode.bg4_enabled() && bg_enabled[3] {
1244            for (x, priority_resolver) in screen_pixels.iter_mut().enumerate() {
1245                let screen_x = apply_screen_shift(x, screen_x_shift, screen_x_offset);
1246
1247                if bg_disabled_in_window[3] {
1248                    let bg4_in_window = self.registers.bg_in_window(3, screen_x);
1249                    if bg4_in_window {
1250                        continue;
1251                    }
1252                }
1253
1254                let bg4_pixel = self.buffers.bg_pixels[3][screen_x as usize];
1255                if !bg4_pixel.is_transparent() {
1256                    priority_resolver.add_bg4(bg4_pixel);
1257                }
1258            }
1259        }
1260
1261        let backdrop_pixel =
1262            RenderedPixel { color: backdrop_color, palette: 0, layer: Layer::Backdrop };
1263        for (priority_resolver, rendered_pixel) in
1264            screen_pixels.iter_mut().zip(screen_rendered_pixels)
1265        {
1266            *rendered_pixel = priority_resolver.get().map_or(backdrop_pixel, |(pixel, layer)| {
1267                let color = resolve_pixel_color(
1268                    &self.cgram,
1269                    layer,
1270                    mode,
1271                    self.registers.direct_color_mode_enabled,
1272                    pixel.palette,
1273                    pixel.color,
1274                );
1275                RenderedPixel { color, palette: pixel.palette, layer }
1276            });
1277        }
1278    }
1279
1280    fn apply_mosaic(
1281        &self,
1282        bg: usize,
1283        scanline: u16,
1284        pixel: u16,
1285        hi_res_mode: HiResMode,
1286    ) -> (u16, u16) {
1287        let mosaic_size = self.registers.mosaic_size;
1288        let mosaic_enabled = self.registers.bg_mosaic_enabled[bg];
1289        if !mosaic_enabled {
1290            return (scanline, pixel);
1291        }
1292
1293        let mosaic_line = self.state.v_mosaic_filter(scanline, hi_res_mode);
1294
1295        // Mosaic size of N fills each (N+1)x(N+1) square with the pixel in the top-left corner
1296        // Mosaic sizes are doubled in true hi-res mode
1297        let mosaic_size: u16 = (mosaic_size + 1).into();
1298        let mosaic_width = match hi_res_mode {
1299            HiResMode::True => 2 * mosaic_size,
1300            _ => mosaic_size,
1301        };
1302
1303        (mosaic_line, pixel / mosaic_width * mosaic_width)
1304    }
1305
1306    fn enter_hi_res_mode(&mut self) {
1307        if !self.vblank_flag() && !self.state.h_hi_res_frame {
1308            // Hi-res mode enabled mid-frame; redraw previously rendered scanlines to 512x224 in-place
1309            if let Some(last_rendered_scanline) = self.state.last_rendered_scanline {
1310                let last_copy_line = if self.state.v_hi_res_frame {
1311                    2 * last_rendered_scanline
1312                } else {
1313                    last_rendered_scanline
1314                };
1315                self.frame_buffer_h256_to_h512(last_copy_line);
1316            }
1317        }
1318
1319        self.state.h_hi_res_frame = true;
1320    }
1321
1322    fn frame_buffer_h256_to_h512(&mut self, to_scanline: u16) {
1323        for scanline in (1..=to_scanline).rev() {
1324            let src_line_addr = 256 * u32::from(scanline - 1);
1325            let dest_line_addr = 512 * u32::from(scanline - 1);
1326            for pixel in (0..256).rev() {
1327                let color = self.frame_buffer[(src_line_addr + pixel) as usize];
1328                self.frame_buffer[(dest_line_addr + 2 * pixel) as usize] = color;
1329                self.frame_buffer[(dest_line_addr + 2 * pixel + 1) as usize] = color;
1330            }
1331        }
1332    }
1333
1334    fn set_in_frame_buffer(&mut self, scanline: u16, pixel: u16, color: Color) {
1335        let screen_width = self.state.frame_screen_width();
1336        let index = u32::from(scanline - 1) * screen_width + u32::from(pixel);
1337        self.frame_buffer[index as usize] = color;
1338    }
1339
1340    fn duplicate_line(&mut self, from_line: u32, to_line: u32, from_pixel: u32) {
1341        let screen_width = self.state.frame_screen_width();
1342        let from_row_addr = screen_width * (from_line - 1);
1343        let to_row_addr = screen_width * (to_line - 1);
1344        for pixel in from_pixel..screen_width {
1345            self.frame_buffer[(to_row_addr + pixel) as usize] =
1346                self.frame_buffer[(from_row_addr + pixel) as usize];
1347        }
1348    }
1349
1350    fn fix_interlaced_frame_buffer(&mut self) {
1351        log::debug!("Just entered interlaced mode; rewriting frame buffer");
1352
1353        let v_display_size = self.registers.v_display_size.to_lines();
1354
1355        // Check if changed from H256px to H512px
1356        let next_frame_h_hi_res =
1357            self.registers.bg_mode.is_hi_res() || self.registers.pseudo_h_hi_res;
1358        if !self.state.h_hi_res_frame && next_frame_h_hi_res {
1359            log::debug!("Expanding previous frame from H256px to H512px");
1360            self.frame_buffer_h256_to_h512(v_display_size);
1361        }
1362
1363        log::debug!(
1364            "Expanding previous frame from V{}px to V{}px; screen width H{}px",
1365            v_display_size,
1366            2 * v_display_size,
1367            if next_frame_h_hi_res { HIRES_SCREEN_WIDTH } else { NORMAL_SCREEN_WIDTH }
1368        );
1369
1370        // Duplicate lines to expand frame buffer from V224px to V448px (or V239px to V478px)
1371        let screen_width = if next_frame_h_hi_res {
1372            HIRES_SCREEN_WIDTH as u32
1373        } else {
1374            NORMAL_SCREEN_WIDTH as u32
1375        };
1376        for scanline in (1..=u32::from(v_display_size)).rev() {
1377            let even_line = 2 * scanline - 1;
1378            let odd_line = 2 * scanline;
1379            for pixel in 0..screen_width {
1380                let color = self.frame_buffer[(scanline * screen_width + pixel) as usize];
1381                self.frame_buffer[(even_line * screen_width + pixel) as usize] = color;
1382                self.frame_buffer[(odd_line * screen_width + pixel) as usize] = color;
1383            }
1384        }
1385    }
1386
1387    pub fn scanlines_per_frame(&self) -> u16 {
1388        match self.timing_mode {
1389            TimingMode::Ntsc => 262,
1390            TimingMode::Pal => 312,
1391        }
1392    }
1393
1394    fn scanline_length(&self, scanline: u16) -> ScanlineLength {
1395        // In NTSC progressive mode, line 240 is short every other frame (1360 mclks)
1396        // In PAL interlaced mode, line 311 is long every other frame (1368 mclks)
1397        // All other lines are 1364 mclks
1398        if !self.state.odd_frame {
1399            return ScanlineLength::Normal;
1400        }
1401
1402        match (scanline, self.timing_mode, self.registers.interlaced) {
1403            (240, TimingMode::Ntsc, false) => ScanlineLength::Short,
1404            (311, TimingMode::Pal, true) => ScanlineLength::Long,
1405            _ => ScanlineLength::Normal,
1406        }
1407    }
1408
1409    fn in_active_display(&self, scanline: u16) -> bool {
1410        !self.registers.forced_blanking && scanline <= self.registers.v_display_size.to_lines()
1411    }
1412
1413    pub fn vblank_flag(&self) -> bool {
1414        self.state.scanline > self.registers.v_display_size.to_lines()
1415    }
1416
1417    pub fn hblank_flag(&self) -> bool {
1418        self.state.scanline_master_cycles < 4 || self.state.scanline_master_cycles >= 1096
1419    }
1420
1421    pub fn scanline(&self) -> u16 {
1422        self.state.scanline
1423    }
1424
1425    pub fn frame_start_cycles(&self) -> u64 {
1426        self.state.frame_start_cycles
1427    }
1428
1429    #[cfg(test)]
1430    pub fn set_scanline(&mut self, scanline: u16) {
1431        assert!(
1432            scanline < self.scanlines_per_frame(),
1433            "{scanline} must be less than {}",
1434            self.scanlines_per_frame()
1435        );
1436        self.state.scanline = scanline;
1437    }
1438
1439    pub fn is_first_vblank_scanline(&self) -> bool {
1440        self.state.scanline == self.registers.v_display_size.to_lines() + 1
1441    }
1442
1443    pub fn scanline_master_cycles(&self) -> u64 {
1444        self.state.scanline_master_cycles
1445    }
1446
1447    #[cfg(test)]
1448    pub fn set_scanline_master_cycles(&mut self, scanline_master_cycles: u64) {
1449        assert!(
1450            scanline_master_cycles < self.state.current_line_length.scanline_mclks(),
1451            "{scanline_master_cycles} must be less than {}",
1452            self.state.current_line_length.scanline_mclks()
1453        );
1454        self.state.scanline_master_cycles = scanline_master_cycles;
1455    }
1456
1457    // There is a 10 mclk / 2.5 dot delay on raising IRQ in response to VTIME/HTIME matches
1458    const IRQ_OFFSET_MCLKS: u64 = 10;
1459
1460    pub fn vtime_for_irq(&self) -> u16 {
1461        if self.state.scanline_master_cycles >= Self::IRQ_OFFSET_MCLKS {
1462            self.state.scanline
1463        } else if self.state.scanline == 0 {
1464            self.scanlines_per_frame() - 1
1465        } else {
1466            self.state.scanline - 1
1467        }
1468    }
1469
1470    pub fn htime_for_irq(&self) -> u16 {
1471        let scanline_mclks = if self.state.scanline_master_cycles >= Self::IRQ_OFFSET_MCLKS {
1472            self.state.scanline_master_cycles - Self::IRQ_OFFSET_MCLKS
1473        } else {
1474            self.state.prev_line_length.scanline_mclks()
1475                - (Self::IRQ_OFFSET_MCLKS - self.state.scanline_master_cycles)
1476        };
1477
1478        // CPU HTIME counter does not account for some dots being shorter/longer; assumes all dots
1479        // are 4 mclks
1480        (scanline_mclks / 4) as u16
1481    }
1482
1483    pub fn previous_line_max_htime(&self) -> u16 {
1484        (self.state.prev_line_length.scanline_mclks() / 4) as u16
1485    }
1486
1487    pub fn frame_buffer(&self) -> &[Color] {
1488        self.frame_buffer.as_ref()
1489    }
1490
1491    pub fn frame_size(&self) -> FrameSize {
1492        let screen_width = self.state.frame_screen_width();
1493
1494        let mut screen_height = self.registers.v_display_size.to_lines();
1495        if self.state.v_hi_res_frame {
1496            screen_height *= 2;
1497        }
1498
1499        FrameSize { width: screen_width, height: screen_height.into() }
1500    }
1501
1502    pub fn composite_params(&self) -> CompositeParams {
1503        let upscale_factor = if self.state.h_hi_res_frame { 4 } else { 8 };
1504
1505        CompositeParams { upscale_factor, samples_per_color_cycle: SamplesPerColorCycle::Twelve }
1506    }
1507
1508    pub fn read_port(&mut self, address: u32) -> Option<u8> {
1509        log::trace!("Read PPU register: {address:06X}");
1510
1511        let address_lsb = address & 0xFF;
1512        let value = match address_lsb {
1513            0x34 => self.registers.read_mpyl(),
1514            0x35 => self.registers.read_mpym(),
1515            0x36 => self.registers.read_mpyh(),
1516            0x37 => {
1517                // SLHV: Latch H/V counter
1518                let h_counter = (self.state.scanline_master_cycles >> 2) as u16;
1519                let v_counter = self.state.scanline;
1520                self.registers.read_slhv(h_counter, v_counter);
1521
1522                // Reading from this address returns CPU open bus
1523                return None;
1524            }
1525            0x38 => {
1526                // RDOAM: OAM data port, read
1527                self.read_oam_data_port()
1528            }
1529            0x39 => {
1530                // RDVRAML: VRAM data port, read, low byte
1531                self.read_vram_data_port_low()
1532            }
1533            0x3A => {
1534                // RDVRAMH: VRAM data port, read, high byte
1535                self.read_vram_data_port_high()
1536            }
1537            0x3B => {
1538                // RDCGRAM: CGRAM data port, read
1539                self.read_cgram_data_port()
1540            }
1541            0x3C => self.registers.read_ophct(self.state.ppu2_open_bus),
1542            0x3D => self.registers.read_opvct(self.state.ppu2_open_bus),
1543            0x3E => {
1544                // STAT77: PPU1 status and version number
1545                // Version number hardcoded to 1
1546                // Bit 4 is PPU1 open bus
1547                (u8::from(self.registers.sprite_pixel_overflow) << 7)
1548                    | (u8::from(self.registers.sprite_overflow) << 6)
1549                    | (self.state.ppu1_open_bus & 0x10)
1550                    | 0x01
1551            }
1552            0x3F => {
1553                // STAT78: PPU2 status and version number
1554                // Version number hardcoded to 1
1555                // Bit 5 is PPU2 open bus
1556                let value = (u8::from(self.state.odd_frame) << 7)
1557                    | (u8::from(self.registers.new_hv_latched) << 6)
1558                    | (self.state.ppu2_open_bus & 0x20)
1559                    | (u8::from(self.timing_mode == TimingMode::Pal) << 4)
1560                    | 0x01;
1561
1562                self.registers.new_hv_latched = false;
1563                self.registers.reset_hv_counter_flipflops();
1564
1565                value
1566            }
1567            0x04 | 0x05 | 0x06 | 0x08 | 0x09 | 0x0A | 0x14 | 0x15 | 0x16 | 0x18 | 0x19 | 0x1A
1568            | 0x24 | 0x25 | 0x26 | 0x28 | 0x29 | 0x2A => {
1569                // PPU1 open bus (all 8 bits)
1570                self.state.ppu1_open_bus
1571            }
1572            _ => {
1573                // CPU open bus
1574                return None;
1575            }
1576        };
1577
1578        if (0x34..0x37).contains(&address_lsb)
1579            || (0x38..0x3B).contains(&address_lsb)
1580            || address_lsb == 0x3E
1581        {
1582            // Reading $2134-$2136, $2138-$213A, or $213E sets PPU1 open bus
1583            self.state.ppu1_open_bus = value;
1584        } else if (0x3B..0x3E).contains(&address_lsb) || address_lsb == 0x3F {
1585            // Reading $213B-$213D or $213F sets PPU2 open bus
1586            self.state.ppu2_open_bus = value;
1587        }
1588
1589        Some(value)
1590    }
1591
1592    pub fn write_port(&mut self, address: u32, value: u8) {
1593        if log::log_enabled!(log::Level::Trace) {
1594            // Don't log data port writes
1595            let address = address & 0xFF;
1596            if address != 0x04 && address != 0x18 && address != 0x19 && address != 0x22 {
1597                log::trace!(
1598                    "PPU register write: 21{address:02X} {value:02X} (scanline {} mclk {})",
1599                    self.state.scanline,
1600                    self.state.scanline_master_cycles,
1601                );
1602            }
1603        }
1604
1605        let scanline = self.state.scanline;
1606        match address & 0xFF {
1607            0x00 => {
1608                let new_forced_blanking = value.bit(7);
1609                if self.registers.forced_blanking != new_forced_blanking {
1610                    self.sprites_forced_blanking_change(new_forced_blanking);
1611                }
1612
1613                self.registers.write_inidisp(value, self.is_first_vblank_scanline());
1614            }
1615            0x01 => self.registers.write_obsel(value),
1616            0x02 => self.registers.write_oamaddl(value, self.in_active_display(scanline)),
1617            0x03 => self.registers.write_oamaddh(value, self.in_active_display(scanline)),
1618            0x04 => {
1619                // OAMDATA: OAM data port (write)
1620                self.write_oam_data_port(value);
1621            }
1622            0x05 => {
1623                self.registers.write_bgmode(value);
1624                if self.registers.bg_mode.is_hi_res() {
1625                    self.enter_hi_res_mode();
1626                }
1627            }
1628            0x06 => self.registers.write_mosaic(value),
1629            0x07..=0x0A => {
1630                let bg = ((address + 1) & 0x3) as usize;
1631                self.registers.write_bg1234sc(bg, value);
1632            }
1633            0x0B => self.registers.write_bg1234nba(0, value),
1634            0x0C => self.registers.write_bg1234nba(2, value),
1635            0x0D => self.registers.write_bg1hofs(value),
1636            0x0E => self.registers.write_bg1vofs(value),
1637            address @ (0x0F | 0x11 | 0x13) => {
1638                // BG2HOFS/BG3HOFS/BG4HOFS: BG2-4 horizontal scroll
1639                let bg = (((address - 0x0F) >> 1) + 1) as usize;
1640                self.registers.write_bg_h_scroll(bg, value);
1641            }
1642            address @ (0x10 | 0x12 | 0x14) => {
1643                // BG2VOFS/BG3VOFS/BG4VOFS: BG2-4 vertical scroll
1644                let bg = (((address & 0x0F) >> 1) + 1) as usize;
1645                self.registers.write_bg_v_scroll(bg, value);
1646            }
1647            0x15 => self.registers.write_vmain(value),
1648            0x16 => self.registers.write_vmaddl(value, &self.vram),
1649            0x17 => self.registers.write_vmaddh(value, &self.vram),
1650            0x18 => {
1651                // VMDATAL: VRAM data port (write), low byte
1652                self.write_vram_data_port_low(value);
1653            }
1654            0x19 => {
1655                // VMDATAH: VRAM data port (write), high byte
1656                self.write_vram_data_port_high(value);
1657            }
1658            0x1A => self.registers.write_m7sel(value),
1659            0x1B => self.registers.write_m7a(value),
1660            0x1C => self.registers.write_m7b(value),
1661            0x1D => self.registers.write_m7c(value),
1662            0x1E => self.registers.write_m7d(value),
1663            0x1F => self.registers.write_m7x(value),
1664            0x20 => self.registers.write_m7y(value),
1665            0x21 => self.registers.write_cgadd(value),
1666            0x22 => {
1667                // CGDATA: CGRAM data port (write)
1668                self.write_cgram_data_port(value);
1669            }
1670            0x23 => self.registers.write_w1234sel(0, value),
1671            0x24 => self.registers.write_w1234sel(2, value),
1672            0x25 => self.registers.write_wobjsel(value),
1673            0x26 => self.registers.write_wh0(value),
1674            0x27 => self.registers.write_wh1(value),
1675            0x28 => self.registers.write_wh2(value),
1676            0x29 => self.registers.write_wh3(value),
1677            0x2A => self.registers.write_wbglog(value),
1678            0x2B => self.registers.write_wobjlog(value),
1679            0x2C => self.registers.write_tm(value),
1680            0x2D => self.registers.write_ts(value),
1681            0x2E => self.registers.write_tmw(value),
1682            0x2F => self.registers.write_tsw(value),
1683            0x30 => self.registers.write_cgwsel(value),
1684            0x31 => self.registers.write_cgadsub(value),
1685            0x32 => self.registers.write_coldata(value),
1686            0x33 => {
1687                self.registers.write_setini(value);
1688                if self.registers.pseudo_h_hi_res {
1689                    self.enter_hi_res_mode();
1690                }
1691            }
1692            _ => {
1693                // No other mappings are valid; do nothing
1694            }
1695        }
1696    }
1697
1698    fn write_vram_data_port_low(&mut self, value: u8) {
1699        if self.vblank_flag() || self.registers.forced_blanking {
1700            // VRAM writes only allowed during VBlank and forced blanking
1701            let vram_addr =
1702                (self.registers.vram_address_translation.apply(self.registers.vram_address)
1703                    & VRAM_ADDRESS_MASK) as usize;
1704            self.vram[vram_addr].set_lsb(value);
1705        }
1706
1707        if self.registers.vram_address_increment_mode == VramIncrementMode::Low {
1708            self.increment_vram_address();
1709        }
1710    }
1711
1712    fn write_vram_data_port_high(&mut self, value: u8) {
1713        if self.vblank_flag() || self.registers.forced_blanking {
1714            // VRAM writes only allowed during VBlank and forced blanking
1715            let vram_addr =
1716                (self.registers.vram_address_translation.apply(self.registers.vram_address)
1717                    & VRAM_ADDRESS_MASK) as usize;
1718            self.vram[vram_addr].set_msb(value);
1719        }
1720
1721        if self.registers.vram_address_increment_mode == VramIncrementMode::High {
1722            self.increment_vram_address();
1723        }
1724    }
1725
1726    fn read_vram_data_port_low(&mut self) -> u8 {
1727        let vram_byte = self.registers.vram_prefetch_buffer.lsb();
1728
1729        if self.registers.vram_address_increment_mode == VramIncrementMode::Low {
1730            // Fill prefetch buffer *before* address increment
1731            self.fill_vram_prefetch_buffer();
1732            self.increment_vram_address();
1733        }
1734
1735        vram_byte
1736    }
1737
1738    fn read_vram_data_port_high(&mut self) -> u8 {
1739        let vram_byte = self.registers.vram_prefetch_buffer.msb();
1740
1741        if self.registers.vram_address_increment_mode == VramIncrementMode::High {
1742            // Fill prefetch buffer *before* address increment
1743            self.fill_vram_prefetch_buffer();
1744            self.increment_vram_address();
1745        }
1746
1747        vram_byte
1748    }
1749
1750    fn increment_vram_address(&mut self) {
1751        self.registers.vram_address =
1752            self.registers.vram_address.wrapping_add(self.registers.vram_address_increment_step);
1753    }
1754
1755    fn fill_vram_prefetch_buffer(&mut self) {
1756        let vram_addr = self.registers.vram_address_translation.apply(self.registers.vram_address)
1757            & VRAM_ADDRESS_MASK;
1758        self.registers.vram_prefetch_buffer = self.vram[vram_addr as usize];
1759    }
1760
1761    fn write_oam_data_port(&mut self, value: u8) {
1762        if self.in_active_display(self.state.scanline) {
1763            self.handle_active_display_oam_write(value);
1764            return;
1765        }
1766
1767        if self.registers.oam_address >= 0x100 {
1768            // Writes to $100 or higher immediately go through to high OAM at (address << 1) & 0x1F
1769            // $220-$3FF are mirrors of $200-$21F
1770            let second_write = self.registers.oam_data_flipflop == AccessFlipflop::Second;
1771            let oam_high_addr = (self.registers.oam_address << 1) | u16::from(second_write);
1772            self.oam_high[(oam_high_addr & 0x1F) as usize] = value;
1773
1774            self.registers.oam_data_flipflop = self.registers.oam_data_flipflop.toggle();
1775            if second_write {
1776                self.registers.oam_address = (self.registers.oam_address + 1) & OAM_ADDRESS_MASK;
1777            }
1778        } else {
1779            // Writes to $000-$1FF go to low OAM; requires two writes to persist a word
1780            match self.registers.oam_data_flipflop {
1781                AccessFlipflop::First => {
1782                    // First write: Latch LSB
1783                    self.registers.oam_write_buffer = value;
1784                }
1785                AccessFlipflop::Second => {
1786                    // Second write: Write word to OAM
1787                    self.oam_low[self.registers.oam_address as usize] =
1788                        u16::from_le_bytes([self.registers.oam_write_buffer, value]);
1789                    self.registers.oam_address =
1790                        (self.registers.oam_address + 1) & OAM_ADDRESS_MASK;
1791                }
1792            }
1793            self.registers.oam_data_flipflop = self.registers.oam_data_flipflop.toggle();
1794        }
1795    }
1796
1797    fn handle_active_display_oam_write(&mut self, value: u8) {
1798        self.progress_for_mid_scanline_write((self.state.scanline_master_cycles / 4) as u16);
1799
1800        if log::log_enabled!(log::Level::Debug) {
1801            log::debug!(
1802                "Active display OAM write at line {} dot {}, state is {:?}",
1803                self.state.scanline,
1804                self.state.scanline_master_cycles / 4,
1805                self.sprites.state
1806            );
1807
1808            if let SpriteState::TileFetch { oam_buffer_idx, .. } = self.sprites.state {
1809                log::debug!(
1810                    "  Current OAM idx = {}",
1811                    self.sprites.scanned_oam_idxs[oam_buffer_idx as usize]
1812                );
1813            }
1814        }
1815
1816        let oam_idx = self.sprites.state.oam_idx(&self.sprites.scanned_oam_idxs);
1817
1818        let oam_high_addr = (oam_idx >> 2) as usize;
1819        self.oam_high[oam_high_addr] = value;
1820
1821        log::debug!("Set OAMHigh[${oam_high_addr:02X}] = 0x{value:02X}");
1822
1823        match self.registers.oam_data_flipflop {
1824            AccessFlipflop::First => {
1825                self.registers.oam_write_buffer = value;
1826            }
1827            AccessFlipflop::Second => {
1828                let word = u16::from_le_bytes([self.registers.oam_write_buffer, value]);
1829                let oam_low_addr = usize::from(oam_idx << 1);
1830                self.oam_low[oam_low_addr] = word;
1831
1832                log::debug!("Set OAMLow[${oam_low_addr:02X}] = 0x{value:04X}");
1833            }
1834        }
1835
1836        self.registers.oam_data_flipflop = self.registers.oam_data_flipflop.toggle();
1837    }
1838
1839    fn read_oam_data_port(&mut self) -> u8 {
1840        if self.in_active_display(self.state.scanline) {
1841            return self.handle_active_display_oam_read();
1842        }
1843
1844        let second_read = self.registers.oam_data_flipflop == AccessFlipflop::Second;
1845
1846        let oam_byte = if self.registers.oam_address >= 0x100 {
1847            // High OAM; $220-$3FF mirrors $200-$21F
1848            let oam_high_addr = (self.registers.oam_address << 1) | u16::from(second_read);
1849            self.oam_high[(oam_high_addr & 0x1F) as usize]
1850        } else {
1851            // Low OAM
1852            let word_bytes = self.oam_low[self.registers.oam_address as usize].to_le_bytes();
1853            word_bytes[usize::from(second_read)]
1854        };
1855
1856        self.registers.oam_data_flipflop = self.registers.oam_data_flipflop.toggle();
1857        if second_read {
1858            self.registers.oam_address = (self.registers.oam_address + 1) & OAM_ADDRESS_MASK;
1859        }
1860
1861        oam_byte
1862    }
1863
1864    fn handle_active_display_oam_read(&self) -> u8 {
1865        // TODO is this right? should active display reads always return from high OAM instead of low OAM?
1866        let oam_idx = self.sprites.state.oam_idx(&self.sprites.scanned_oam_idxs);
1867        let oam_high_addr = (oam_idx >> 2) & 0x1F;
1868        self.oam_high[oam_high_addr as usize]
1869    }
1870
1871    fn write_cgram_data_port(&mut self, value: u8) {
1872        match self.registers.cgram_flipflop {
1873            AccessFlipflop::First => {
1874                self.registers.cgram_write_buffer = value;
1875                self.registers.cgram_flipflop = AccessFlipflop::Second;
1876            }
1877            AccessFlipflop::Second => {
1878                // Only bits 6-0 of high byte are persisted
1879                self.cgram[self.registers.cgram_address as usize] =
1880                    u16::from_le_bytes([self.registers.cgram_write_buffer, value & 0x7F]);
1881                self.registers.cgram_flipflop = AccessFlipflop::First;
1882
1883                self.registers.cgram_address = self.registers.cgram_address.wrapping_add(1);
1884            }
1885        }
1886    }
1887
1888    fn read_cgram_data_port(&mut self) -> u8 {
1889        let word = self.cgram[self.registers.cgram_address as usize];
1890
1891        match self.registers.cgram_flipflop {
1892            AccessFlipflop::First => {
1893                // Low byte
1894                self.registers.cgram_flipflop = AccessFlipflop::Second;
1895
1896                word.lsb()
1897            }
1898            AccessFlipflop::Second => {
1899                // High byte; bit 7 is PPU2 open bus
1900                self.registers.cgram_flipflop = AccessFlipflop::First;
1901                self.registers.cgram_address = self.registers.cgram_address.wrapping_add(1);
1902
1903                (self.state.ppu2_open_bus & 0x80) | word.msb()
1904            }
1905        }
1906    }
1907
1908    pub fn update_wrio(&mut self, wrio: u8) {
1909        if wrio != self.registers.programmable_joypad_port {
1910            let h_counter = (self.state.scanline_master_cycles >> 2) as u16;
1911            let v_counter = self.state.scanline;
1912            self.registers.update_wrio(wrio, h_counter, v_counter);
1913        }
1914    }
1915
1916    pub fn update_controller_hv_latch(&mut self, h: u16, v: u16, master_cycles_elapsed: u64) {
1917        if v == self.state.scanline
1918            && h > (self.state.scanline_master_cycles / 4) as u16
1919            && h <= ((self.state.scanline_master_cycles + master_cycles_elapsed) / 4) as u16
1920        {
1921            self.registers.latched_h_counter = h;
1922            self.registers.latched_v_counter = v;
1923            self.registers.new_hv_latched = true;
1924        }
1925    }
1926
1927    pub fn update_config(&mut self, config: SnesEmulatorConfig) {
1928        self.deinterlace = config.deinterlace;
1929    }
1930
1931    pub fn reset(&mut self) {
1932        // Enable forced blanking
1933        self.registers.write_inidisp(0x80, self.is_first_vblank_scanline());
1934
1935        // Return to default rendering mode (224-line, non-interlaced, no pseudo-hi-res or smaller OBJs)
1936        self.registers.write_setini(0x00);
1937    }
1938}
1939
1940#[allow(clippy::too_many_arguments)]
1941fn get_bg_tile<'vram>(
1942    vram: &'vram Vram,
1943    registers: &Registers,
1944    bg: usize,
1945    x: u16,
1946    y: u16,
1947    bpp: BitsPerPixel,
1948    raw_tile_number: u16,
1949    x_flip: bool,
1950    y_flip: bool,
1951) -> &'vram [u16] {
1952    let bg_mode = registers.bg_mode;
1953    let bg_tile_size = registers.bg_tile_size[bg];
1954    let (bg_tile_width_pixels, bg_tile_height_pixels) = get_bg_tile_size(bg_mode, bg_tile_size);
1955
1956    let tile_number = {
1957        let x_shift = bg_tile_width_pixels == 16 && (if x_flip { x % 16 < 8 } else { x % 16 >= 8 });
1958        let y_shift =
1959            bg_tile_height_pixels == 16 && (if y_flip { y % 16 < 8 } else { y % 16 >= 8 });
1960        match (x_shift, y_shift) {
1961            (false, false) => raw_tile_number,
1962            (true, false) => raw_tile_number + 1,
1963            (false, true) => raw_tile_number + 16,
1964            (true, true) => raw_tile_number + 17,
1965        }
1966    };
1967
1968    let bg_data_base_addr = registers.bg_tile_base_address[bg];
1969    let tile_size_words = bpp.tile_size_words();
1970    let tile_addr = (bg_data_base_addr.wrapping_add(tile_number * tile_size_words)
1971        & VRAM_ADDRESS_MASK) as usize;
1972    &vram[tile_addr..tile_addr + tile_size_words as usize]
1973}
1974
1975fn get_bg_map_entry(vram: &Vram, registers: &Registers, bg: usize, x: u16, y: u16) -> u16 {
1976    let bg_mode = registers.bg_mode;
1977    let bg_tile_size = registers.bg_tile_size[bg];
1978    let (bg_tile_width_pixels, bg_tile_height_pixels) = get_bg_tile_size(bg_mode, bg_tile_size);
1979
1980    let bg_screen_size = registers.bg_screen_size[bg];
1981    let screen_width_pixels = bg_screen_size.width_tiles() * bg_tile_width_pixels;
1982    let screen_height_pixels = bg_screen_size.height_tiles() * bg_tile_height_pixels;
1983
1984    let mut bg_map_base_addr = registers.bg_base_address[bg];
1985    let mut x = x & (screen_width_pixels - 1);
1986    let mut y = y & (screen_height_pixels - 1);
1987
1988    // The larger BG screen is made up of 1-4 smaller 32x32 tile screens
1989    let single_screen_width_pixels = 32 * bg_tile_width_pixels;
1990    let single_screen_height_pixels = 32 * bg_tile_height_pixels;
1991
1992    if x >= single_screen_width_pixels {
1993        bg_map_base_addr += 32 * 32;
1994        x &= single_screen_width_pixels - 1;
1995    }
1996
1997    if y >= single_screen_height_pixels {
1998        bg_map_base_addr += match bg_screen_size {
1999            BgScreenSize::HorizontalMirror => 32 * 32,
2000            BgScreenSize::FourScreen => 2 * 32 * 32,
2001            _ => panic!(
2002                "y should always be <= 256/512 in OneScreen and VerticalMirror sizes; was {y}"
2003            ),
2004        };
2005        y &= single_screen_height_pixels - 1;
2006    }
2007
2008    let tile_row = y / bg_tile_height_pixels;
2009    let tile_col = x / bg_tile_width_pixels;
2010    let tile_map_addr = 32 * tile_row + tile_col;
2011
2012    vram[(bg_map_base_addr.wrapping_add(tile_map_addr) & VRAM_ADDRESS_MASK) as usize]
2013}
2014
2015fn get_bg_tile_size(bg_mode: BgMode, tile_size: TileSize) -> (u16, u16) {
2016    match (bg_mode, tile_size) {
2017        (BgMode::Six, _) | (BgMode::Five, TileSize::Small) => (16, 8),
2018        (_, TileSize::Small) => (8, 8),
2019        (_, TileSize::Large) => (16, 16),
2020    }
2021}
2022
2023fn line_overlaps_sprite(sprite_y: u8, sprite_height: u16, scanline: u16) -> bool {
2024    let scanline = scanline as u8;
2025    let sprite_bottom = sprite_y.wrapping_add(sprite_height as u8);
2026    if sprite_bottom > sprite_y {
2027        (sprite_y..sprite_bottom).contains(&scanline)
2028    } else {
2029        scanline >= sprite_y || scanline < sprite_bottom
2030    }
2031}
2032
2033fn resolve_pixel_color(
2034    cgram: &Cgram,
2035    layer: Layer,
2036    bg_mode: BgMode,
2037    direct_color_mode: bool,
2038    palette: u8,
2039    color: u8,
2040) -> u16 {
2041    let bpp = match (layer, bg_mode) {
2042        (Layer::Bg1 | Layer::Bg2, BgMode::Seven) => BitsPerPixel::Eight,
2043        (Layer::Bg1, _) => bg_mode.bg1_bpp(),
2044        (Layer::Bg2, _) => bg_mode.bg2_bpp(),
2045        (Layer::Bg3, _) => BitsPerPixel::BG3,
2046        (Layer::Bg4, _) => BitsPerPixel::BG4,
2047        (Layer::Obj, _) => BitsPerPixel::OBJ,
2048        (Layer::Backdrop, _) => {
2049            panic!("invalid input to resolve_pixel_color: mode={bg_mode:?}, layer={layer:?}")
2050        }
2051    };
2052
2053    let two_bpp_offset = if bg_mode == BgMode::Zero {
2054        // Mode 0 gives each BG layer its own set of 8 palettes
2055        match layer {
2056            Layer::Bg1 | Layer::Obj => 0x00,
2057            Layer::Bg2 => 0x20,
2058            Layer::Bg3 => 0x40,
2059            Layer::Bg4 => 0x60,
2060            Layer::Backdrop => unreachable!("above match checks layer is not backdrop"),
2061        }
2062    } else {
2063        0
2064    };
2065
2066    // Sprites only use palettes in the second half of CGRAM
2067    let four_bpp_offset = if layer == Layer::Obj { 0x80 } else { 0 };
2068
2069    match bpp {
2070        BitsPerPixel::Two => cgram[(two_bpp_offset | (palette << 2) | color) as usize],
2071        BitsPerPixel::Four => cgram[(four_bpp_offset | (palette << 4) | color) as usize],
2072        BitsPerPixel::Eight => {
2073            if direct_color_mode {
2074                resolve_direct_color(palette, color)
2075            } else {
2076                cgram[color as usize]
2077            }
2078        }
2079    }
2080}
2081
2082fn resolve_direct_color(palette: u8, color: u8) -> u16 {
2083    let color: u16 = color.into();
2084    let palette: u16 = palette.into();
2085
2086    // Color (8-bit) interpreted as BBGGGRRR
2087    // Palette (3-bit) interpreted as bgr
2088    // Result (16-bit): 0 BBb00 GGGg0 RRRr0
2089    let r_component = ((color & 0b00_000_111) << 2) | ((palette & 0b001) << 1);
2090    let g_component = ((color & 0b00_111_000) << 4) | ((palette & 0b010) << 5);
2091    let b_component = ((color & 0b11_000_000) << 7) | ((palette & 0b100) << 10);
2092    r_component | g_component | b_component
2093}
2094
2095fn convert_snes_color(snes_color: u16, brightness: u8) -> Color {
2096    let color_table = &colortable::TABLE[brightness as usize];
2097
2098    let r = color_table[(snes_color & 0x1F) as usize];
2099    let g = color_table[((snes_color >> 5) & 0x1F) as usize];
2100    let b = color_table[((snes_color >> 10) & 0x1F) as usize];
2101    Color::rgb(r, g, b)
2102}
2103
2104#[cfg(test)]
2105mod tests {
2106    use super::*;
2107
2108    #[test]
2109    fn direct_color() {
2110        assert_eq!(0b00000_00000_11100, resolve_direct_color(0b000, 0b00_000_111));
2111        assert_eq!(0b00000_00000_11110, resolve_direct_color(0b001, 0b00_000_111));
2112
2113        assert_eq!(0b00000_11100_00000, resolve_direct_color(0b000, 0b00_111_000));
2114        assert_eq!(0b00000_11110_00000, resolve_direct_color(0b010, 0b00_111_000));
2115
2116        assert_eq!(0b11000_00000_00000, resolve_direct_color(0b000, 0b11_000_000));
2117        assert_eq!(0b11100_00000_00000, resolve_direct_color(0b100, 0b11_000_000));
2118
2119        assert_eq!(0b11100_11110_11110, resolve_direct_color(0b111, 0b11_111_111));
2120    }
2121}