1mod debug;
2
3pub use debug::{PatternTable, copy_nametables, copy_oam, copy_palette_ram};
4
5use crate::ppu;
6use crate::ppu::{ColorEmphasis, FrameBuffer};
7use jgenesis_common::frontend::{Color, TimingMode};
8use nes_config::{NesPalette, Overscan};
9
10pub trait TimingModeGraphicsExt {
11    fn visible_screen_height(self) -> u16;
12
13    fn starting_row(self) -> u16;
14}
15
16impl TimingModeGraphicsExt for TimingMode {
17    fn visible_screen_height(self) -> u16 {
18        match self {
19            Self::Ntsc => 224,
20            Self::Pal => 240,
21        }
22    }
23
24    fn starting_row(self) -> u16 {
25        match self {
26            Self::Ntsc => 8,
27            Self::Pal => 0,
28        }
29    }
30}
31
32pub fn ppu_frame_buffer_to_rgba(
33    ppu_frame_buffer: &FrameBuffer,
34    rgba_frame_buffer: &mut [Color],
35    overscan: Overscan,
36    display_mode: TimingMode,
37    palette: &NesPalette,
38    emulate_ntsc_output: bool,
39) {
40    if emulate_ntsc_output {
41        convert_to_rgba::<true>(
42            ppu_frame_buffer,
43            rgba_frame_buffer,
44            overscan,
45            display_mode,
46            palette,
47        );
48    } else {
49        convert_to_rgba::<false>(
50            ppu_frame_buffer,
51            rgba_frame_buffer,
52            overscan,
53            display_mode,
54            palette,
55        );
56    }
57}
58
59fn convert_to_rgba<const NTSC_OUTPUT: bool>(
60    ppu_frame_buffer: &FrameBuffer,
61    rgba_frame_buffer: &mut [Color],
62    overscan: Overscan,
63    display_mode: TimingMode,
64    palette: &NesPalette,
65) {
66    rgba_frame_buffer.fill(Color::BLACK);
67
68    let row_offset = display_mode.starting_row();
69    let visible_screen_height = display_mode.visible_screen_height();
70
71    let num_rows_rendered =
72        visible_screen_height.saturating_sub(overscan.top).saturating_sub(overscan.bottom) as usize;
73    let num_cols_rendered =
74        ppu::SCREEN_WIDTH.saturating_sub(overscan.left).saturating_sub(overscan.right) as usize;
75
76    for (row, scanline) in ppu_frame_buffer
77        .iter()
78        .skip(row_offset as usize + overscan.top as usize)
79        .take(num_rows_rendered)
80        .enumerate()
81    {
82        for (col, &(nes_color, color_emphasis)) in
83            scanline.iter().skip(overscan.left as usize).take(num_cols_rendered).enumerate()
84        {
85            let rgba_color = if NTSC_OUTPUT {
86                // NTSC shader expects R to contain the 6-bit color and G to contain color emphasis
87                Color::rgb(nes_color, color_emphasis.into(), 0)
88            } else {
89                nes_color_to_rgba(nes_color, color_emphasis, palette)
90            };
91            rgba_frame_buffer[row * num_cols_rendered + col] = rgba_color;
92        }
93    }
94}
95
96pub fn nes_color_to_rgba(
97    nes_color: u8,
98    color_emphasis: ColorEmphasis,
99    palette: &NesPalette,
100) -> Color {
101    let palette_idx = usize::from(nes_color)
102        + 64 * usize::from(color_emphasis.red())
103        + 128 * usize::from(color_emphasis.green())
104        + 256 * usize::from(color_emphasis.blue());
105
106    let (r, g, b) = palette[palette_idx];
107    Color::rgb(r, g, b)
108}