Skip to main content

embedded_3dgfx/bsp/
coverage.rs

1//! Per-pixel coverage buffer for BSP front-to-back rendering (Milestone 5).
2//!
3//! In front-to-back BSP traversal the *first* draw to any pixel is always the
4//! correct (nearest) surface.  A 1-bit coverage bitmap then replaces the full
5//! 32-bit z-buffer, saving ~128 KB RAM at 240×135.
6//!
7//! # Usage
8//! ```rust,ignore
9//! const W: usize = 240;
10//! const H: usize = 135;
11//! let mut cov_data = [0u8; CoverageBuffer::bytes_for(W, H)];
12//! let mut cov = CoverageBuffer::new(&mut cov_data, W, H).unwrap();
13//!
14//! // each frame:
15//! cov.clear();
16//! engine.render_bsp_coverage(&world, &mut scratch, &texture_mgr, fb, &mut cov, tel);
17//! ```
18
19/// Single-bit-per-pixel coverage bitmap.
20///
21/// Bits are packed LSB-first within each byte:
22/// pixel `(x, y)` occupies bit `(y*width + x) % 8` of byte `(y*width + x) / 8`.
23pub struct CoverageBuffer<'a> {
24    data: &'a mut [u8],
25    pub width: usize,
26    pub height: usize,
27}
28
29impl<'a> CoverageBuffer<'a> {
30    /// Returns the number of bytes needed for `width × height` pixels.
31    pub const fn bytes_for(width: usize, height: usize) -> usize {
32        (width * height + 7) / 8
33    }
34
35    /// Create from a caller-owned byte slice.
36    ///
37    /// Returns `None` if `data` is too short.
38    pub fn new(data: &'a mut [u8], width: usize, height: usize) -> Option<Self> {
39        let needed = Self::bytes_for(width, height);
40        if data.len() < needed {
41            return None;
42        }
43        Some(Self {
44            data,
45            width,
46            height,
47        })
48    }
49
50    /// Zero all coverage bits (call once per frame before rendering).
51    #[inline]
52    pub fn clear(&mut self) {
53        self.data.fill(0);
54    }
55
56    /// Returns `true` if pixel `(x, y)` has already been drawn this frame.
57    #[inline]
58    pub fn is_covered(&self, x: usize, y: usize) -> bool {
59        if x >= self.width || y >= self.height {
60            return false;
61        }
62        let idx = y * self.width + x;
63        let byte = idx >> 3;
64        let bit = idx & 7;
65        // Safety: byte < bytes_for(width, height) ≤ data.len()
66        (self.data[byte] >> bit) & 1 != 0
67    }
68
69    /// Mark pixel `(x, y)` as drawn.
70    #[inline]
71    pub fn mark_covered(&mut self, x: usize, y: usize) {
72        if x >= self.width || y >= self.height {
73            return;
74        }
75        let idx = y * self.width + x;
76        let byte = idx >> 3;
77        let bit = idx & 7;
78        self.data[byte] |= 1 << bit;
79    }
80
81    /// Returns `true` when all pixels are covered (early-exit for the renderer).
82    pub fn is_full(&self) -> bool {
83        let pixels = self.width * self.height;
84        let full_bytes = pixels >> 3;
85        let rem_bits = pixels & 7;
86
87        for &b in &self.data[..full_bytes] {
88            if b != 0xFF {
89                return false;
90            }
91        }
92        if rem_bits > 0 {
93            let mask = (1u8 << rem_bits) - 1;
94            let last = self.data.get(full_bytes).copied().unwrap_or(0);
95            if last & mask != mask {
96                return false;
97            }
98        }
99        true
100    }
101
102    /// Returns the fraction of pixels covered, as a value in [0.0, 1.0].
103    /// Useful for telemetry / performance tuning.
104    #[cfg(feature = "std")]
105    pub fn fill_ratio(&self) -> f32 {
106        let pixels = self.width * self.height;
107        let covered = self
108            .data
109            .iter()
110            .map(|b| b.count_ones() as usize)
111            .sum::<usize>();
112        let covered = covered.min(pixels);
113        covered as f32 / pixels as f32
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    extern crate std;
120    use super::*;
121
122    #[test]
123    fn bytes_for_240x135() {
124        assert_eq!(CoverageBuffer::bytes_for(240, 135), (240 * 135 + 7) / 8);
125    }
126
127    #[test]
128    fn new_rejects_undersized_buffer() {
129        let mut buf = [0u8; 1];
130        assert!(CoverageBuffer::new(&mut buf, 240, 135).is_none());
131    }
132
133    #[test]
134    fn mark_and_query() {
135        let mut buf = [0u8; CoverageBuffer::bytes_for(8, 8)];
136        let mut cov = CoverageBuffer::new(&mut buf, 8, 8).unwrap();
137
138        assert!(!cov.is_covered(3, 2));
139        cov.mark_covered(3, 2);
140        assert!(cov.is_covered(3, 2));
141        // Neighbours untouched
142        assert!(!cov.is_covered(2, 2));
143        assert!(!cov.is_covered(4, 2));
144        assert!(!cov.is_covered(3, 1));
145        assert!(!cov.is_covered(3, 3));
146    }
147
148    #[test]
149    fn clear_resets_coverage() {
150        let mut buf = [0u8; CoverageBuffer::bytes_for(4, 4)];
151        let mut cov = CoverageBuffer::new(&mut buf, 4, 4).unwrap();
152        cov.mark_covered(1, 1);
153        assert!(cov.is_covered(1, 1));
154        cov.clear();
155        assert!(!cov.is_covered(1, 1));
156    }
157
158    #[test]
159    fn is_full_after_marking_all() {
160        let mut buf = [0u8; CoverageBuffer::bytes_for(4, 4)];
161        let mut cov = CoverageBuffer::new(&mut buf, 4, 4).unwrap();
162        assert!(!cov.is_full());
163        for y in 0..4 {
164            for x in 0..4 {
165                cov.mark_covered(x, y);
166            }
167        }
168        assert!(cov.is_full());
169    }
170
171    #[test]
172    fn out_of_bounds_is_covered_returns_false() {
173        let mut buf = [0u8; CoverageBuffer::bytes_for(4, 4)];
174        let cov = CoverageBuffer::new(&mut buf, 4, 4).unwrap();
175        assert!(!cov.is_covered(4, 0));
176        assert!(!cov.is_covered(0, 4));
177        assert!(!cov.is_covered(100, 100));
178    }
179}