breakwater_parser/framebuffer/
simple.rs

1use core::slice;
2
3use super::FrameBuffer;
4
5pub struct SimpleFrameBuffer {
6    width: usize,
7    height: usize,
8    buffer: Vec<u32>,
9}
10
11impl SimpleFrameBuffer {
12    pub fn new(width: usize, height: usize) -> Self {
13        let mut buffer = Vec::with_capacity(width * height);
14        buffer.resize_with(width * height, || 0);
15        Self {
16            width,
17            height,
18            buffer,
19        }
20    }
21}
22
23impl FrameBuffer for SimpleFrameBuffer {
24    #[inline(always)]
25    fn get_width(&self) -> usize {
26        self.width
27    }
28
29    #[inline(always)]
30    fn get_height(&self) -> usize {
31        self.height
32    }
33
34    #[inline(always)]
35    unsafe fn get_unchecked(&self, x: usize, y: usize) -> u32 {
36        *self.buffer.get_unchecked(x + y * self.width)
37    }
38
39    #[inline(always)]
40    fn set(&self, x: usize, y: usize, rgba: u32) {
41        // https://github.com/sbernauer/breakwater/pull/11
42        // If we make the FrameBuffer large enough (e.g. 10_000 x 10_000) we don't need to check the bounds here
43        // (x and y are max 4 digit numbers). Flamegraph has shown 5.21% of runtime in this bound check. On the other
44        // hand this can increase the framebuffer size dramatically and lowers the cash locality.
45        // In the end we did *not* go with this change.
46        if x < self.width && y < self.height {
47            unsafe {
48                let ptr = self.buffer.as_ptr().add(x + y * self.width) as *mut u32;
49                *ptr = rgba;
50            }
51        }
52    }
53
54    #[inline(always)]
55    fn set_multi_from_start_index(&self, starting_index: usize, pixels: &[u8]) -> usize {
56        let num_pixels = pixels.len() / 4;
57
58        if starting_index + num_pixels > self.buffer.len() {
59            dbg!(
60                "Ignoring invalid set_multi call, which would exceed the screen",
61                starting_index,
62                num_pixels,
63                self.buffer.len()
64            );
65            // We did not move
66            return 0;
67        }
68
69        let starting_ptr = unsafe { self.buffer.as_ptr().add(starting_index) };
70        let target_slice =
71            unsafe { slice::from_raw_parts_mut(starting_ptr as *mut u8, pixels.len()) };
72        target_slice.copy_from_slice(pixels);
73
74        num_pixels
75    }
76
77    #[inline(always)]
78    fn as_bytes(&self) -> &[u8] {
79        let len = 4 * self.buffer.len();
80        let ptr = self.buffer.as_ptr() as *const u8;
81        unsafe { std::slice::from_raw_parts(ptr, len) }
82    }
83
84    #[inline(always)]
85    fn as_pixels(&self) -> &[u32] {
86        &self.buffer
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    use rstest::{fixture, rstest};
95
96    #[fixture]
97    fn fb() -> SimpleFrameBuffer {
98        // We keep the framebuffer so small, so that we can easily test all pixels in a test run
99        SimpleFrameBuffer::new(640, 480)
100    }
101
102    #[rstest]
103    #[case(0, 0, 0)]
104    #[case(0, 0, 0xff0000)]
105    #[case(0, 0, 0x0000ff)]
106    #[case(0, 0, 0x12345678)]
107    pub fn test_roundtrip(
108        fb: SimpleFrameBuffer,
109        #[case] x: usize,
110        #[case] y: usize,
111        #[case] rgba: u32,
112    ) {
113        fb.set(x, y, rgba);
114        assert_eq!(fb.get(x, y), Some(rgba));
115    }
116
117    #[rstest]
118    pub fn test_out_of_bounds(fb: SimpleFrameBuffer) {
119        assert_eq!(fb.get(usize::MAX, usize::MAX), None);
120        assert_eq!(fb.get(usize::MAX, usize::MAX), None);
121    }
122
123    #[rstest]
124    pub fn test_set_multi_from_beginning(fb: SimpleFrameBuffer) {
125        let pixels = (0..10_u32).collect::<Vec<_>>();
126        let pixel_bytes: Vec<u8> = pixels.iter().flat_map(|p| p.to_le_bytes()).collect();
127
128        let (current_x, current_y) = fb.set_multi(0, 0, &pixel_bytes);
129
130        assert_eq!(current_x, 10);
131        assert_eq!(current_y, 0);
132
133        for x in 0..10 {
134            assert_eq!(fb.get(x as usize, 0), Some(x), "Checking pixel {x}");
135        }
136
137        // The next pixel must not have been colored
138        assert_eq!(fb.get(11, 0), Some(0));
139    }
140
141    #[rstest]
142    pub fn test_set_multi_in_the_middle(fb: SimpleFrameBuffer) {
143        let mut x = 10;
144        let mut y = 100;
145
146        // Let's color exactly 3 lines and 42 pixels
147        let pixels = (0..3 * fb.width as u32 + 42).collect::<Vec<_>>();
148        let pixel_bytes: Vec<u8> = pixels.iter().flat_map(|p| p.to_le_bytes()).collect();
149        let (current_x, current_y) = fb.set_multi(x, y, &pixel_bytes);
150
151        assert_eq!(current_x, 52);
152        assert_eq!(current_y, 103);
153
154        // Let's check everything has been colored
155        for rgba in 0..3 * fb.width as u32 + 42 {
156            assert_eq!(fb.get(x, y), Some(rgba));
157
158            x += 1;
159            if x >= fb.width {
160                x = 0;
161                y += 1;
162            }
163        }
164
165        // Everything afterwards must have not been touched (let's check the next 10 lines)
166        for _ in 0..10 * fb.width as u32 {
167            assert_eq!(fb.get(x, y), Some(0));
168
169            x += 1;
170            if x >= fb.width {
171                x = 0;
172                y += 1;
173            }
174        }
175    }
176
177    #[rstest]
178    pub fn test_set_multi_does_nothing_when_too_long(fb: SimpleFrameBuffer) {
179        let mut too_long = Vec::with_capacity(fb.width * fb.height * 4 /* pixels per byte */);
180        too_long.fill_with(|| 42_u8);
181        let (current_x, current_y) = fb.set_multi(1, 0, &too_long);
182
183        // Should be unchanged
184        assert_eq!(current_x, 1);
185        assert_eq!(current_y, 0);
186
187        for x in 0..fb.width {
188            for y in 0..fb.height {
189                assert_eq!(fb.get(x, y), Some(0));
190            }
191        }
192    }
193}