Skip to main content

gb/ppu/
palette.rs

1//! Palettes: what the two bits of a pixel turn into.
2//!
3//! A tile pixel is two bits, so it names a colour *index* rather than a colour.
4//! What that index becomes is the palette's business, which is why the same tile
5//! can be drawn light in one place and dark in another.
6//!
7//! On the original Game Boy the four shades are fixed in hardware and a palette only
8//! chooses which index gets which of them. On the Game Boy Color a palette holds
9//! four real colours. See <https://gbdev.io/pandocs/Palettes.html>.
10//!
11//! Index 0 is transparent for objects, which leaves the lowest two bits of an
12//! object palette unused.
13//!
14//! An original Game Boy ignores the colour registers and keeps drawing with the shades
15//! [`set_background`] chose, so a cartridge that runs on both machines can set
16//! colours without asking which it is on.
17
18use crate::mmio::{BGP, OBP0, OBP1, Palette};
19
20/// Which of the two object palettes, as [`OamAttr::dmg_palette`] picks between.
21///
22/// [`OamAttr::dmg_palette`]: crate::mmio::OamAttr::dmg_palette
23#[derive(Clone, Copy, PartialEq, Eq, Debug)]
24pub enum ObjSlot {
25    /// `OBP0`.
26    Zero,
27    /// `OBP1`.
28    One,
29}
30
31/// The palette the background and window are drawn with.
32#[inline]
33pub fn background() -> Palette {
34    BGP.read()
35}
36
37/// Set the palette the background and window are drawn with.
38#[inline]
39pub fn set_background(palette: Palette) {
40    BGP.write(palette);
41}
42
43/// One of the two object palettes.
44#[inline]
45pub fn object(slot: ObjSlot) -> Palette {
46    match slot {
47        ObjSlot::Zero => OBP0.read(),
48        ObjSlot::One => OBP1.read(),
49    }
50}
51
52/// Set one of the two object palettes.
53///
54/// Its shade for index 0 is ignored: that index is transparent.
55#[inline]
56pub fn set_object(slot: ObjSlot, palette: Palette) {
57    match slot {
58        ObjSlot::Zero => OBP0.write(palette),
59        ObjSlot::One => OBP1.write(palette),
60    }
61}
62
63#[cfg(feature = "cgb")]
64pub use cgb::*;
65
66#[cfg(feature = "cgb")]
67mod cgb {
68    use crate::mmio::cgb::{BCPD, BCPS, OCPD, OCPS, PaletteIndex};
69    use crate::ppu::{Access, wait_blank};
70
71    /// Palettes of each kind: this many for the background, and as many again
72    /// for objects.
73    pub const PALETTES: u8 = 8;
74
75    /// Colours in one palette.
76    pub const COLORS: u8 = 4;
77
78    /// One colour, five bits per channel.
79    #[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
80    pub struct Color(u16);
81
82    impl Color {
83        /// Build a colour. Each channel runs 0 to 31; anything above is masked.
84        pub const fn new(red: u8, green: u8, blue: u8) -> Self {
85            Color(
86                (red as u16 & 0x1F) | ((green as u16 & 0x1F) << 5) | ((blue as u16 & 0x1F) << 10),
87            )
88        }
89
90        /// Red, 0 to 31.
91        pub const fn red(self) -> u8 {
92            (self.0 & 0x1F) as u8
93        }
94
95        /// Green, 0 to 31.
96        pub const fn green(self) -> u8 {
97            ((self.0 >> 5) & 0x1F) as u8
98        }
99
100        /// Blue, 0 to 31.
101        pub const fn blue(self) -> u8 {
102            ((self.0 >> 10) & 0x1F) as u8
103        }
104    }
105
106    /// Set one background palette.
107    ///
108    /// # Panics
109    ///
110    /// If `palette` is [`PALETTES`] or beyond.
111    pub fn set_background_colors(access: Access<'_>, palette: u8, colors: &[Color; COLORS as usize]) {
112        assert!(palette < PALETTES);
113        write(access, BCPS, BCPD, palette, colors);
114    }
115
116    /// Set one object palette.
117    ///
118    /// Its first colour is never drawn: index 0 is transparent for objects.
119    ///
120    /// # Panics
121    ///
122    /// If `palette` is [`PALETTES`] or beyond.
123    pub fn set_object_colors(access: Access<'_>, palette: u8, colors: &[Color; COLORS as usize]) {
124        assert!(palette < PALETTES);
125        write(access, OCPS, OCPD, palette, colors);
126    }
127
128    type Index = voladdress::VolAddress<PaletteIndex, voladdress::Safe, voladdress::Safe>;
129    type Data = voladdress::VolAddress<u8, voladdress::Safe, voladdress::Safe>;
130
131    #[inline]
132    fn write(
133        access: Access<'_>,
134        index: Index,
135        data: Data,
136        palette: u8,
137        colors: &[Color; COLORS as usize],
138    ) {
139        // The index advances on every write to the data port, including one the
140        // PPU threw away, so a byte lost to mode 3 does not go missing: it shifts
141        // every colour after it. That is why `Polled` waits here at all, where a
142        // tilemap write could simply let the byte drop.
143        //
144        // The index register itself is reachable in every mode and needs no
145        // wait of its own.
146        index.write(
147            PaletteIndex::new()
148                .with_address(palette * COLORS * 2)
149                .with_auto_increment(true),
150        );
151        match access {
152            Access::Polled => stream::<true>(data, colors),
153            _ => stream::<false>(data, colors),
154        }
155    }
156
157    #[inline]
158    fn stream<const WAIT: bool>(data: Data, colors: &[Color; COLORS as usize]) {
159        for c in colors {
160            for b in c.0.to_le_bytes() {
161                if WAIT {
162                    wait_blank();
163                }
164                data.write(b);
165            }
166        }
167    }
168}