1use crate::render::Rgba;
8use crate::render_config::RenderConfig;
9
10pub const TILE_SIZE: u32 = 8;
20
21pub const BYTES_PER_PIXEL: usize = 4;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub struct DirtyRegion {
34 pub x: i32,
35 pub y: i32,
36 pub width: u32,
37 pub height: u32,
38 pub present: bool,
41}
42
43impl DirtyRegion {
44 pub fn empty() -> Self {
46 Self {
47 x: 0,
48 y: 0,
49 width: 0,
50 height: 0,
51 present: false,
52 }
53 }
54
55 pub fn full(width: u32, height: u32) -> Self {
57 Self {
58 x: 0,
59 y: 0,
60 width,
61 height,
62 present: true,
63 }
64 }
65
66 pub fn new(x: i32, y: i32, width: u32, height: u32) -> Self {
68 Self {
69 x,
70 y,
71 width,
72 height,
73 present: true,
74 }
75 }
76
77 pub fn union(&self, other: &DirtyRegion) -> DirtyRegion {
79 if !self.present {
80 return *other;
81 }
82 if !other.present {
83 return *self;
84 }
85 let x1 = self.x.min(other.x);
86 let y1 = self.y.min(other.y);
87 let x2 = (self.x + self.width as i32).max(other.x + other.width as i32);
88 let y2 = (self.y + self.height as i32).max(other.y + other.height as i32);
89 DirtyRegion::new(x1, y1, (x2 - x1).max(0) as u32, (y2 - y1).max(0) as u32)
90 }
91
92 #[inline]
95 pub fn contains_pixel(&self, px: u32, py: u32) -> bool {
96 if !self.present {
97 return false;
98 }
99 px as i32 >= self.x
100 && (px as i32) < self.x + self.width as i32
101 && py as i32 >= self.y
102 && (py as i32) < self.y + self.height as i32
103 }
104
105 pub fn to_tile_rect(&self, tile_size: u32) -> (i32, i32, u32, u32) {
108 if !self.present {
109 return (0, 0, 0, 0);
110 }
111 let ts = tile_size as i32;
112 let tx = if self.x >= 0 {
113 self.x / ts
114 } else {
115 (self.x - ts + 1) / ts
116 };
117 let ty = if self.y >= 0 {
118 self.y / ts
119 } else {
120 (self.y - ts + 1) / ts
121 };
122 let right = self.x + self.width as i32;
123 let bottom = self.y + self.height as i32;
124 let tw = ((right + ts - 1) / ts - tx).max(0) as u32;
125 let th = ((bottom + ts - 1) / ts - ty).max(0) as u32;
126 (tx, ty, tw, th)
127 }
128}
129
130impl Default for DirtyRegion {
131 fn default() -> Self {
132 Self::empty()
133 }
134}
135
136#[derive(Debug, Clone)]
145pub struct FrameBuffer {
146 pub data: Vec<u8>,
148 pub width: u32,
150 pub height: u32,
152 pub dirty_region: DirtyRegion,
156}
157
158impl FrameBuffer {
159 pub fn new(config: RenderConfig, clear_color: Rgba) -> Self {
161 let fb_size =
162 (config.screen_width as usize) * (config.screen_height as usize) * BYTES_PER_PIXEL;
163 let mut fb = Self {
164 data: vec![0; fb_size],
165 width: config.screen_width,
166 height: config.screen_height,
167 dirty_region: DirtyRegion::full(config.screen_width, config.screen_height),
168 };
169 fb.clear(clear_color);
170 fb
171 }
172
173 pub fn mark_all_dirty(&mut self) {
175 self.dirty_region = DirtyRegion::full(self.width, self.height);
176 }
177
178 pub fn mark_dirty_rect(&mut self, x: i32, y: i32, w: u32, h: u32) {
180 let r = DirtyRegion::new(x, y, w, h);
181 self.dirty_region = self.dirty_region.union(&r);
182 }
183
184 pub fn mark_dirty_tile(&mut self, tx: i32, ty: i32) {
186 let tile_size = TILE_SIZE as i32;
187 self.mark_dirty_rect(tx * tile_size, ty * tile_size, TILE_SIZE, TILE_SIZE);
188 }
189
190 pub fn clear_dirty(&mut self) {
192 self.dirty_region = DirtyRegion::empty();
193 }
194
195 #[inline]
198 pub fn is_dirty_pixel(&self, x: u32, y: u32) -> bool {
199 self.dirty_region.contains_pixel(x, y)
200 }
201
202 #[inline]
204 pub fn width(&self) -> u32 {
205 self.width
206 }
207
208 #[inline]
210 pub fn height(&self) -> u32 {
211 self.height
212 }
213
214 pub fn clear(&mut self, color: Rgba) {
216 let rgba = color.to_array();
217 for pixel in self.data.chunks_exact_mut(BYTES_PER_PIXEL) {
218 pixel.copy_from_slice(&rgba);
219 }
220 }
221
222 pub fn set_pixel(&mut self, x: u32, y: u32, color: Rgba) -> bool {
224 if x >= self.width || y >= self.height {
225 return false;
226 }
227 let offset = ((y as usize) * (self.width as usize) + (x as usize)) * BYTES_PER_PIXEL;
228 self.data[offset..offset + BYTES_PER_PIXEL].copy_from_slice(&color.to_array());
229 true
230 }
231
232 pub fn get_pixel(&self, x: u32, y: u32) -> Option<Rgba> {
234 if x >= self.width || y >= self.height {
235 return None;
236 }
237 let offset = ((y as usize) * (self.width as usize) + (x as usize)) * BYTES_PER_PIXEL;
238 let mut c = [0u8; 4];
239 c.copy_from_slice(&self.data[offset..offset + BYTES_PER_PIXEL]);
240 Some(Rgba::from(c))
241 }
242
243 pub fn fill_rect(&mut self, x: u32, y: u32, rect_width: u32, rect_height: u32, color: Rgba) {
245 let x_start = x.min(self.width);
246 let y_start = y.min(self.height);
247 let x_end = (x + rect_width).min(self.width);
248 let y_end = (y + rect_height).min(self.height);
249 let rgba = color.to_array();
250
251 for row in y_start..y_end {
252 let row_offset = (row as usize) * (self.width as usize) * BYTES_PER_PIXEL;
253 for col in x_start..x_end {
254 let offset = row_offset + (col as usize) * BYTES_PER_PIXEL;
255 self.data[offset..offset + BYTES_PER_PIXEL].copy_from_slice(&rgba);
256 }
257 }
258 }
259
260 pub fn row_slice(&self, y: u32) -> Option<&[u8]> {
262 if y >= self.height {
263 return None;
264 }
265 let start = (y as usize) * (self.width as usize) * BYTES_PER_PIXEL;
266 let end = start + (self.width as usize) * BYTES_PER_PIXEL;
267 Some(&self.data[start..end])
268 }
269
270 pub fn row_slice_mut(&mut self, y: u32) -> Option<&mut [u8]> {
272 if y >= self.height {
273 return None;
274 }
275 let start = (y as usize) * (self.width as usize) * BYTES_PER_PIXEL;
276 let end = start + (self.width as usize) * BYTES_PER_PIXEL;
277 Some(&mut self.data[start..end])
278 }
279
280 pub fn blit_row(&mut self, x: u32, y: u32, src: &[u8], count: u32) -> bool {
284 if y >= self.height || x >= self.width {
285 return false;
286 }
287 let actual_count = count.min(self.width - x) as usize;
288 let src_bytes = actual_count * BYTES_PER_PIXEL;
289 if src.len() < src_bytes {
290 return false;
291 }
292 let offset = ((y as usize) * (self.width as usize) + (x as usize)) * BYTES_PER_PIXEL;
293 self.data[offset..offset + src_bytes].copy_from_slice(&src[..src_bytes]);
294 true
295 }
296
297 #[cfg(feature = "image")]
304 pub fn save_png(&self, path: &std::path::Path) -> std::io::Result<()> {
305 use image::{ImageBuffer, Rgba as ImgRgba};
306 let img: ImageBuffer<ImgRgba<u8>, _> =
307 ImageBuffer::from_raw(self.width, self.height, self.data.clone())
308 .expect("FrameBuffer data size mismatch");
309 img.save(path)
310 .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
311 }
312}