1use crate::api::GameBoyEmulatorConfig;
2use crate::ppu::PpuFrameBuffer;
3use crate::{HardwareMode, ppu};
4use bincode::{Decode, Encode};
5use gb_config::GbPalette;
6use jgenesis_common::boxedarray::BoxedColorArray;
7use jgenesis_common::frontend::Color;
8use std::iter;
9use std::ops::{Deref, DerefMut};
10
11// 0/0/0 = black and 255/255/255 = white, so linearly map [0,3] to [255,0]
12const GB_COLOR_TO_RGB_BW: [[u8; 3]; 4] =
13    [[255, 255, 255], [170, 170, 170], [85, 85, 85], [0, 0, 0]];
14
15// Render with a light green tint
16pub(crate) const GB_COLOR_TO_RGB_GREEN_TINT: [[u8; 3]; 4] =
17    [[0xAE, 0xD2, 0x8D], [0x75, 0x9C, 0x68], [0x40, 0x5E, 0x2D], [0x0C, 0x1E, 0x09]];
18
19// Render with an intense green tint that somewhat mimics the original Game Boy LCD screen
20const GB_COLOR_TO_RGB_LIME_GREEN: [[u8; 3]; 4] =
21    [[0x80, 0xA6, 0x08], [0x5D, 0x7F, 0x07], [0x25, 0x5C, 0x1A], [0x00, 0x32, 0x00]];
22
23// [round(255 * i / 31) for i in range(32)]
24pub(crate) const RGB_5_TO_8: &[u8; 32] = &[
25    0, 8, 16, 25, 33, 41, 49, 58, 66, 74, 82, 90, 99, 107, 115, 123, 132, 140, 148, 156, 165, 173,
26    181, 189, 197, 206, 214, 222, 230, 239, 247, 255,
27];
28
29#[derive(Debug, Clone, Encode, Decode)]
30pub(crate) struct RgbaFrameBuffer(BoxedColorArray<{ ppu::FRAME_BUFFER_LEN }>);
31
32impl RgbaFrameBuffer {
33    pub(crate) fn copy_from(
34        &mut self,
35        ppu_frame_buffer: &PpuFrameBuffer,
36        hardware_mode: HardwareMode,
37        config: &GameBoyEmulatorConfig,
38    ) {
39        match hardware_mode {
40            HardwareMode::Dmg => {
41                self.do_copy(ppu_frame_buffer, dmg_map_color(config));
42            }
43            HardwareMode::Cgb => {
44                self.do_copy(ppu_frame_buffer, cgb_map_color);
45            }
46        }
47    }
48
49    fn do_copy(&mut self, ppu_frame_buffer: &PpuFrameBuffer, map_color: impl Fn(u16) -> Color) {
50        for (&ppu_color, rgba_color) in iter::zip(ppu_frame_buffer.iter(), self.iter_mut()) {
51            *rgba_color = map_color(ppu_color);
52        }
53    }
54}
55
56fn dmg_map_color(config: &GameBoyEmulatorConfig) -> impl Fn(u16) -> Color {
57    let color_mapping = match config.gb_palette {
58        GbPalette::BlackAndWhite => GB_COLOR_TO_RGB_BW,
59        GbPalette::GreenTint => GB_COLOR_TO_RGB_GREEN_TINT,
60        GbPalette::LimeGreen => GB_COLOR_TO_RGB_LIME_GREEN,
61        GbPalette::Custom => config.gb_custom_palette.map(|(r, g, b)| [r, g, b]),
62    };
63
64    move |ppu_color| {
65        let [r, g, b] = color_mapping[ppu_color as usize];
66        Color::rgb(r, g, b)
67    }
68}
69
70fn cgb_map_color(ppu_color: u16) -> Color {
71    let (r, g, b) = parse_cgb_color(ppu_color);
72    Color::rgb(RGB_5_TO_8[r as usize], RGB_5_TO_8[g as usize], RGB_5_TO_8[b as usize])
73}
74
75pub(crate) fn parse_cgb_color(ppu_color: u16) -> (u8, u8, u8) {
76    let r = ppu_color & 0x1F;
77    let g = (ppu_color >> 5) & 0x1F;
78    let b = (ppu_color >> 10) & 0x1F;
79
80    (r as u8, g as u8, b as u8)
81}
82
83impl Default for RgbaFrameBuffer {
84    fn default() -> Self {
85        Self(BoxedColorArray::new())
86    }
87}
88
89impl Deref for RgbaFrameBuffer {
90    type Target = [Color; ppu::FRAME_BUFFER_LEN];
91
92    fn deref(&self) -> &Self::Target {
93        &self.0
94    }
95}
96
97impl DerefMut for RgbaFrameBuffer {
98    fn deref_mut(&mut self) -> &mut Self::Target {
99        &mut self.0
100    }
101}