Skip to main content

dotzuki_renderer/
tilemap.rs

1//! Tile map constants and tile cache for background rendering.
2//!
3//! The Game Boy background map is 32×32 tiles (256×256 pixels).
4//! The old 8-bit `TileMap` has been replaced by [`dotzuki_engine::tilemap::Tilemap`];
5//! the `pub use` alias below provides backward-compatible naming.
6//!
7//! [`TileCache`] stores pre-rendered RGBA tiles for a (tileset, palette) pair
8//! to avoid per-pixel palette lookups on repeat renders.
9
10use crate::palette::{ColorIndex, GbColor, Palette};
11use crate::tile::{RgbaTile, TileFormat, TileSet, TILE_PIXELS};
12use crate::TILE_SIZE;
13
14/// Game Boy background map dimensions in tiles.
15pub const BG_MAP_WIDTH: u32 = 32;
16pub const BG_MAP_HEIGHT: u32 = 32;
17/// Background map size in pixels (256×256).
18pub const BG_MAP_PIXEL_WIDTH: u32 = BG_MAP_WIDTH * TILE_SIZE;
19pub const BG_MAP_PIXEL_HEIGHT: u32 = BG_MAP_HEIGHT * TILE_SIZE;
20/// Total tile entries in one background map (1024).
21pub const BG_MAP_SIZE: usize = (BG_MAP_WIDTH * BG_MAP_HEIGHT) as usize;
22
23/// Pre-rendered RGBA tile cache for a (tileset, palette) pair.
24///
25/// Stores each tile from a [`TileSet`] rendered through a [`Palette`]
26/// as an [`RgbaTile`], avoiding per-pixel palette lookups on repeat renders.
27pub struct TileCache {
28    cached: Vec<RgbaTile>,
29    valid: bool,
30}
31
32impl TileCache {
33    pub fn new() -> Self {
34        Self {
35            cached: Vec::new(),
36            valid: false,
37        }
38    }
39
40    /// Populate the cache from a tileset and palette, unless already valid.
41    pub fn ensure(&mut self, tileset: &TileSet, palette: &Palette) {
42        if self.valid {
43            return;
44        }
45        let count = tileset.len();
46        self.cached.clear();
47        self.cached.reserve(count);
48        for i in 0..count {
49            let tile = tileset.get(i);
50            let mut rgba = RgbaTile::blank();
51            for row in 0..TILE_PIXELS {
52                for col in 0..TILE_PIXELS {
53                    let color_idx = tile.pixels[row][col];
54                    rgba.pixels[row][col] = palette.color(GbColor::from_u8(color_idx));
55                }
56            }
57            self.cached.push(rgba);
58        }
59        self.valid = true;
60    }
61
62    /// Populate the cache from a tileset and palette using a generic [`ColorIndex`].
63    ///
64    /// Unlike [`ensure`](Self::ensure), this method is format-aware:
65    /// - [`TileFormat::FullColor`]: uses [`TileSet::get_rgba`] directly — no palette lookup.
66    /// - [`TileFormat::Gb2bpp`] / [`TileFormat::Gba4bpp`]: applies `palette.color(C::from_u8(...))`.
67    pub fn ensure_with_format<C: ColorIndex>(&mut self, tileset: &TileSet, palette: &Palette<C>) {
68        if self.valid {
69            return;
70        }
71        let format = tileset.tile_format();
72        let count = tileset.len();
73        self.cached.clear();
74        self.cached.reserve(count);
75        for i in 0..count {
76            match format {
77                TileFormat::FullColor => {
78                    if let Some(rgba_tile) = tileset.get_rgba(i) {
79                        self.cached.push(rgba_tile.clone());
80                    } else {
81                        self.cached.push(RgbaTile::blank());
82                    }
83                }
84                _ => {
85                    let tile = tileset.get(i);
86                    let mut rgba = RgbaTile::blank();
87                    for row in 0..TILE_PIXELS {
88                        for col in 0..TILE_PIXELS {
89                            let color_idx = tile.pixels[row][col];
90                            rgba.pixels[row][col] = palette.color(C::from_u8(color_idx));
91                        }
92                    }
93                    self.cached.push(rgba);
94                }
95            }
96        }
97        self.valid = true;
98    }
99
100    /// Invalidate the cache so the next `ensure` rebuilds it.
101    pub fn invalidate(&mut self) {
102        self.valid = false;
103    }
104
105    /// Get a cached RGBA tile by tile index, or `None` if out of bounds.
106    #[inline]
107    pub fn get(&self, index: usize) -> Option<&RgbaTile> {
108        self.cached.get(index)
109    }
110}
111
112impl Default for TileCache {
113    fn default() -> Self {
114        Self::new()
115    }
116}