gb/ppu/map.rs
1//! Tilemaps: two 32 by 32 grids of tile indices.
2//!
3//! One byte per cell, naming a tile the way [`tile`](super::tile) describes.
4//! Which grid a layer reads is set on the layer, by [`bg::set_map`](super::bg::set_map)
5//! and [`window::set_map`](super::window::set_map); the two layers can share one. See <https://gbdev.io/pandocs/Tile_Maps.html>.
6//!
7//! # Wrapping
8//!
9//! The grid is 32 cells square where the screen shows 20 by 18, and it wraps on
10//! both axes: cell 31 is followed by cell 0. Scrolling relies on that, so
11//! coordinates here are taken modulo 32 rather than checked. There is no
12//! out-of-range cell on a torus.
13
14use crate::mmio::{TILEMAP_0, TILEMAP_1};
15
16use super::{Access, wait_blank};
17
18/// Cells along one side of a tilemap.
19pub const SIDE: u8 = 32;
20
21/// Which of the two tilemaps.
22#[derive(Clone, Copy, PartialEq, Eq, Debug)]
23pub enum Map {
24 /// The grid at `0x9800`.
25 Zero,
26 /// The grid at `0x9C00`.
27 One,
28}
29
30impl Map {
31 const fn base(self) -> usize {
32 match self {
33 Map::Zero => TILEMAP_0.as_usize(),
34 Map::One => TILEMAP_1.as_usize(),
35 }
36 }
37}
38
39/// What [`TileGrid`] and [`AttrGrid`] are both made of: a rectangle inside a
40/// larger grid, carrying that grid's row stride.
41#[derive(Clone, Copy)]
42struct Grid<'a> {
43 data: &'a [u8],
44 stride: u8,
45 x: u8,
46 y: u8,
47 w: u8,
48 h: u8,
49}
50
51impl<'a> Grid<'a> {
52 const fn new(data: &'a [u8], width: u8) -> Self {
53 let rows = data.len() / width as usize;
54 let h = if rows > u8::MAX as usize { u8::MAX } else { rows as u8 };
55 Grid { data, stride: width, x: 0, y: 0, w: width, h }
56 }
57
58 const fn sub(self, x: u8, y: u8, w: u8, h: u8) -> Self {
59 // The origin is carried rather than re-slicing, which keeps this const
60 // and spares recomputing the stride. Saturating, so that an origin
61 // past the end is refused by the write rather than wrapping back into
62 // range.
63 Grid { x: self.x.saturating_add(x), y: self.y.saturating_add(y), w, h, ..self }
64 }
65}
66
67macro_rules! grid_kind {
68 ($(#[$m:meta])* $name:ident, $what:literal) => {
69 #[doc = concat!("A rectangle of ", $what, ", with the row stride of the grid it sits in.")]
70 ///
71 /// A level map is wider than a tilemap, so the strip that scrolls into
72 /// view is not contiguous in the source. Binding the stride to the data
73 /// rather than passing it alongside keeps the two from being paired
74 /// wrongly, and the two kinds of grid are separate types so that one
75 /// cannot be written where the other belongs.
76 ///
77 /// ```ignore
78 #[doc = concat!("const LEVEL: ", stringify!($name), " = ", stringify!($name), "::new(&DATA, 100);")]
79 #[doc = concat!("map::write(d, Map::Zero, x, 0, LEVEL.sub(col, 0, 1, 18));")]
80 /// ```
81 $(#[$m])*
82 #[derive(Clone, Copy)]
83 pub struct $name<'a>(Grid<'a>);
84
85 $(#[$m])*
86 impl<'a> $name<'a> {
87 /// All of `data`, laid out `width` cells per row.
88 ///
89 /// The height follows from the length, so a partial last row is
90 /// dropped, and so is anything past row 255.
91 ///
92 /// # Panics
93 ///
94 /// If `width` is zero.
95 pub const fn new(data: &'a [u8], width: u8) -> Self {
96 $name(Grid::new(data, width))
97 }
98
99 /// The `w` by `h` rectangle at `(x, y)` within this one.
100 ///
101 /// Not checked here. One reaching outside the data it was built from
102 /// is refused when it is written, not when it is taken.
103 pub const fn sub(self, x: u8, y: u8, w: u8, h: u8) -> Self {
104 $name(self.0.sub(x, y, w, h))
105 }
106
107 /// Cells across.
108 pub const fn width(self) -> u8 {
109 self.0.w
110 }
111
112 /// Cells down.
113 pub const fn height(self) -> u8 {
114 self.0.h
115 }
116 }
117 };
118}
119
120grid_kind!(TileGrid, "tile indices");
121grid_kind!(
122 #[cfg(feature = "cgb")]
123 #[cfg_attr(docsrs, doc(cfg(feature = "cgb")))]
124 AttrGrid,
125 "attribute bytes"
126);
127
128/// Put `tile` in one cell.
129#[inline]
130pub fn set(access: Access<'_>, map: Map, x: u8, y: u8, tile: u8) {
131 let dst = cell(map, x, y);
132 unsafe { access.write(dst, &[tile]) };
133}
134
135/// Put `tile` in every cell of a `w` by `h` rectangle.
136///
137/// # Panics
138///
139/// If `w` or `h` is past [`SIDE`], which would wrap over what was just written.
140pub fn fill(access: Access<'_>, map: Map, x: u8, y: u8, w: u8, h: u8, tile: u8) {
141 match access {
142 Access::Polled => unsafe { fill_rect::<true>(map, x, y, w, h, tile) },
143 _ => unsafe { fill_rect::<false>(map, x, y, w, h, tile) },
144 }
145}
146
147/// Copy `src` in with its top left at `(x, y)`.
148///
149/// # Panics
150///
151/// If `src` is wider or taller than [`SIDE`], which would wrap over what was
152/// just written, or if it does not fit the data it was built from.
153pub fn write(access: Access<'_>, map: Map, x: u8, y: u8, src: TileGrid<'_>) {
154 match access {
155 Access::Polled => unsafe { blit::<true>(map, x, y, src.0) },
156 _ => unsafe { blit::<false>(map, x, y, src.0) },
157 }
158}
159
160#[inline(always)]
161fn cell(map: Map, x: u8, y: u8) -> *mut u8 {
162 let x = (x % SIDE) as usize;
163 let y = (y % SIDE) as usize;
164 (map.base() + y * SIDE as usize + x) as *mut u8
165}
166
167unsafe fn blit<const WAIT: bool>(map: Map, x: u8, y: u8, src: Grid<'_>) {
168 if src.w == 0 || src.h == 0 {
169 return;
170 }
171 assert!(src.w <= SIDE && src.h <= SIDE, "a write wider or taller than the grid would overwrite itself");
172 // Check the rectangle once so the run below can index unchecked: it has to
173 // fit the stride, or a row would read into its neighbour, and its far corner
174 // has to be inside the data. That corner is computed in `usize` and checked:
175 // `usize` is sixteen bits here, and the coordinates are bytes, so either step
176 // can carry past what the other operand can represent.
177 let last = (src.y as usize + src.h as usize - 1)
178 .checked_mul(src.stride as usize)
179 .and_then(|v| v.checked_add(src.x as usize + src.w as usize - 1));
180 assert!(
181 src.x as usize + src.w as usize <= src.stride as usize
182 && matches!(last, Some(l) if l < src.data.len()),
183 "a rectangle reaching outside the grid it was taken from"
184 );
185
186 let x = x % SIDE;
187 // A row reaching past the right edge continues at column zero, so it is one
188 // contiguous run or two, decided once rather than per cell.
189 let head = if src.w < SIDE - x { src.w } else { SIDE - x };
190
191 // Step the source by one stride per row rather than multiplying again: the
192 // multiply is a software routine on this target.
193 let mut s = unsafe {
194 src.data
195 .as_ptr()
196 .add(src.y as usize * src.stride as usize + src.x as usize)
197 };
198 let mut off = row_offset(y);
199 for _ in 0..src.h {
200 let d = (map.base() | off) as *mut u8;
201
202 unsafe { copy::<WAIT>(d.add(x as usize), s, head) };
203 if src.w > head {
204 unsafe { copy::<WAIT>(d, s.add(head as usize), src.w - head) };
205 }
206 s = unsafe { s.add(src.stride as usize) };
207 off = next_row(off);
208 }
209}
210
211unsafe fn fill_rect<const WAIT: bool>(map: Map, x: u8, y: u8, w: u8, h: u8, tile: u8) {
212 if w == 0 || h == 0 {
213 return;
214 }
215 assert!(w <= SIDE && h <= SIDE, "a fill wider or taller than the grid would overwrite itself");
216
217 let x = x % SIDE;
218 let head = if w < SIDE - x { w } else { SIDE - x };
219
220 let mut off = row_offset(y);
221 for _ in 0..h {
222 let d = (map.base() | off) as *mut u8;
223 unsafe { spread::<WAIT>(d.add(x as usize), head, tile) };
224 if w > head {
225 unsafe { spread::<WAIT>(d, w - head, tile) };
226 }
227 off = next_row(off);
228 }
229}
230
231/// Byte offset of row `y` from the start of a grid.
232#[inline(always)]
233fn row_offset(y: u8) -> usize {
234 (y % SIDE) as usize * SIDE as usize
235}
236
237/// The row below, wrapping at the bottom.
238///
239/// A grid is 1024 bytes and both sit at a 1024 byte boundary, so an offset needs
240/// only masking and the base needs only an `or`. Stepping beats recomputing the
241/// row address, which costs a multiply the row loop would otherwise repeat.
242#[inline(always)]
243fn next_row(off: usize) -> usize {
244 (off + SIDE as usize) & (SIDE as usize * SIDE as usize - 1)
245}
246
247#[inline]
248unsafe fn copy<const WAIT: bool>(dst: *mut u8, src: *const u8, n: u8) {
249 for i in 0..n as usize {
250 let b = unsafe { *src.add(i) };
251 if WAIT {
252 wait_blank();
253 }
254 unsafe { core::ptr::write_volatile(dst.add(i), b) };
255 }
256}
257
258#[inline]
259unsafe fn spread<const WAIT: bool>(dst: *mut u8, n: u8, tile: u8) {
260 for i in 0..n as usize {
261 if WAIT {
262 wait_blank();
263 }
264 unsafe { core::ptr::write_volatile(dst.add(i), tile) };
265 }
266}
267/// Proof that the attribute plane is the grid mapped at the tilemap addresses.
268///
269/// The plane is a second 32 by 32 grid in VRAM bank 1, sharing the tilemap's
270/// addresses. Each byte holds the palette, the flips, the object priority and
271/// the tile bank for the cell of the same coordinates. Pan Docs calls these the
272/// BG map attributes, but they reach the window too: whichever layer reads a
273/// tilemap reads its attributes alongside.
274///
275/// They belong to the cell, not to the tile it names. Two cells showing one tile
276/// can be drawn from different palettes, and giving a cell a different tile
277/// leaves its attributes where they were.
278///
279/// [`BgAttr::bank`](crate::mmio::cgb::BgAttr::bank) says which VRAM bank the PPU
280/// fetches that cell's tile from, a separate question from which bank the CPU
281/// has mapped. [`edit_attrs`] settles the second so that a write lands in the
282/// plane rather than in the tile indices.
283///
284/// Handed out by [`edit_attrs`] and bounded to its closure, the way
285/// [`Access::Direct`] is. See
286/// <https://gbdev.io/pandocs/Tile_Maps.html#bg-map-attributes-cgb-mode-only>.
287#[cfg(feature = "cgb")]
288#[cfg_attr(docsrs, doc(cfg(feature = "cgb")))]
289#[derive(Clone, Copy)]
290pub struct AttrAccess<'a> {
291 _private: core::marker::PhantomData<&'a ()>,
292}
293
294#[cfg(feature = "cgb")]
295impl<'a> !Send for AttrAccess<'a> {}
296#[cfg(feature = "cgb")]
297impl<'a> !Sync for AttrAccess<'a> {}
298
299/// Map the attribute plane and leave it mapped for `f`.
300///
301/// [`with_vram_bank`](super::with_vram_bank) nested inside `f` puts the mapping
302/// back as it leaves, so the token is good again after it. During it the token
303/// is not: a write through it then lands in whatever that scope mapped. So does
304/// one made after a handler switched the bank and left it switched.
305///
306/// There is no plane to map on an original Game Boy, and nothing refuses the
307/// attempt: `f` writes attribute bytes over the tile indices instead. A
308/// cartridge that runs on both machines has to ask [`is_cgb`](crate::is_cgb)
309/// first.
310///
311/// ```ignore
312/// map::edit_attrs(|a| {
313/// a.set(d, Map::Zero, 3, 3, highlight);
314/// a.set(d, Map::Zero, 4, 3, highlight);
315/// });
316/// ```
317#[cfg(feature = "cgb")]
318#[cfg_attr(docsrs, doc(cfg(feature = "cgb")))]
319#[inline]
320pub fn edit_attrs<R>(f: impl FnOnce(AttrAccess<'_>) -> R) -> R {
321 crate::ppu::with_vram_bank(crate::ppu::VramBank::One, || {
322 f(AttrAccess { _private: core::marker::PhantomData })
323 })
324}
325
326#[cfg(feature = "cgb")]
327impl AttrAccess<'_> {
328 /// Give one cell its attributes.
329 #[inline]
330 pub fn set(self, access: Access<'_>, map: Map, x: u8, y: u8, attr: crate::mmio::cgb::BgAttr) {
331 set(access, map, x, y, attr.into_bits());
332 }
333
334 /// Give every cell of a rectangle the same attributes.
335 ///
336 /// # Panics
337 ///
338 /// As [`fill`].
339 #[inline]
340 pub fn fill(
341 self,
342 access: Access<'_>,
343 map: Map,
344 x: u8,
345 y: u8,
346 w: u8,
347 h: u8,
348 attr: crate::mmio::cgb::BgAttr,
349 ) {
350 fill(access, map, x, y, w, h, attr.into_bits());
351 }
352
353 /// Copy a rectangle of attribute bytes in, its top left at `(x, y)`.
354 ///
355 /// # Panics
356 ///
357 /// As [`write()`].
358 #[inline]
359 pub fn write(self, access: Access<'_>, map: Map, x: u8, y: u8, src: AttrGrid<'_>) {
360 // Straight to the blit: `write` takes tile indices, and the bytes travel
361 // the same way whichever plane is mapped.
362 match access {
363 Access::Polled => unsafe { blit::<true>(map, x, y, src.0) },
364 _ => unsafe { blit::<false>(map, x, y, src.0) },
365 }
366 }
367}