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}