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 { x: 0, y: 0, width: 0, height: 0, present: false }
47 }
48
49 pub fn full(width: u32, height: u32) -> Self {
51 Self { x: 0, y: 0, width, height, present: true }
52 }
53
54 pub fn new(x: i32, y: i32, width: u32, height: u32) -> Self {
56 Self { x, y, width, height, present: true }
57 }
58
59 pub fn union(&self, other: &DirtyRegion) -> DirtyRegion {
61 if !self.present {
62 return *other;
63 }
64 if !other.present {
65 return *self;
66 }
67 let x1 = self.x.min(other.x);
68 let y1 = self.y.min(other.y);
69 let x2 = (self.x + self.width as i32).max(other.x + other.width as i32);
70 let y2 = (self.y + self.height as i32).max(other.y + other.height as i32);
71 DirtyRegion::new(x1, y1, (x2 - x1).max(0) as u32, (y2 - y1).max(0) as u32)
72 }
73
74 #[inline]
77 pub fn contains_pixel(&self, px: u32, py: u32) -> bool {
78 if !self.present {
79 return false;
80 }
81 px as i32 >= self.x
82 && (px as i32) < self.x + self.width as i32
83 && py as i32 >= self.y
84 && (py as i32) < self.y + self.height as i32
85 }
86
87 pub fn to_tile_rect(&self, tile_size: u32) -> (i32, i32, u32, u32) {
90 if !self.present {
91 return (0, 0, 0, 0);
92 }
93 let ts = tile_size as i32;
94 let tx = if self.x >= 0 { self.x / ts } else { (self.x - ts + 1) / ts };
95 let ty = if self.y >= 0 { self.y / ts } else { (self.y - ts + 1) / ts };
96 let right = self.x + self.width as i32;
97 let bottom = self.y + self.height as i32;
98 let tw = ((right + ts - 1) / ts - tx).max(0) as u32;
99 let th = ((bottom + ts - 1) / ts - ty).max(0) as u32;
100 (tx, ty, tw, th)
101 }
102}
103
104impl Default for DirtyRegion {
105 fn default() -> Self {
106 Self::empty()
107 }
108}
109
110#[derive(Debug, Clone)]
119pub struct FrameBuffer {
120 pub data: Vec<u8>,
122 pub width: u32,
124 pub height: u32,
126 pub dirty_region: DirtyRegion,
130}
131
132impl FrameBuffer {
133 pub fn new(config: RenderConfig, clear_color: Rgba) -> Self {
135 let fb_size = (config.screen_width as usize)
136 * (config.screen_height as usize)
137 * BYTES_PER_PIXEL;
138 let mut fb = Self {
139 data: vec![0; fb_size],
140 width: config.screen_width,
141 height: config.screen_height,
142 dirty_region: DirtyRegion::full(config.screen_width, config.screen_height),
143 };
144 fb.clear(clear_color);
145 fb
146 }
147
148 pub fn mark_all_dirty(&mut self) {
150 self.dirty_region = DirtyRegion::full(self.width, self.height);
151 }
152
153 pub fn mark_dirty_rect(&mut self, x: i32, y: i32, w: u32, h: u32) {
155 let r = DirtyRegion::new(x, y, w, h);
156 self.dirty_region = self.dirty_region.union(&r);
157 }
158
159 pub fn mark_dirty_tile(&mut self, tx: i32, ty: i32) {
161 let tile_size = TILE_SIZE as i32;
162 self.mark_dirty_rect(tx * tile_size, ty * tile_size, TILE_SIZE, TILE_SIZE);
163 }
164
165 pub fn clear_dirty(&mut self) {
167 self.dirty_region = DirtyRegion::empty();
168 }
169
170 #[inline]
173 pub fn is_dirty_pixel(&self, x: u32, y: u32) -> bool {
174 self.dirty_region.contains_pixel(x, y)
175 }
176
177 #[inline]
179 pub fn width(&self) -> u32 {
180 self.width
181 }
182
183 #[inline]
185 pub fn height(&self) -> u32 {
186 self.height
187 }
188
189 pub fn clear(&mut self, color: Rgba) {
191 let rgba = color.to_array();
192 for pixel in self.data.chunks_exact_mut(BYTES_PER_PIXEL) {
193 pixel.copy_from_slice(&rgba);
194 }
195 }
196
197 pub fn set_pixel(&mut self, x: u32, y: u32, color: Rgba) -> bool {
199 if x >= self.width || y >= self.height {
200 return false;
201 }
202 let offset = ((y as usize) * (self.width as usize) + (x as usize)) * BYTES_PER_PIXEL;
203 self.data[offset..offset + BYTES_PER_PIXEL].copy_from_slice(&color.to_array());
204 true
205 }
206
207 pub fn get_pixel(&self, x: u32, y: u32) -> Option<Rgba> {
209 if x >= self.width || y >= self.height {
210 return None;
211 }
212 let offset = ((y as usize) * (self.width as usize) + (x as usize)) * BYTES_PER_PIXEL;
213 let mut c = [0u8; 4];
214 c.copy_from_slice(&self.data[offset..offset + BYTES_PER_PIXEL]);
215 Some(Rgba::from(c))
216 }
217
218 pub fn fill_rect(&mut self, x: u32, y: u32, rect_width: u32, rect_height: u32, color: Rgba) {
220 let x_start = x.min(self.width);
221 let y_start = y.min(self.height);
222 let x_end = (x + rect_width).min(self.width);
223 let y_end = (y + rect_height).min(self.height);
224 let rgba = color.to_array();
225
226 for row in y_start..y_end {
227 let row_offset = (row as usize) * (self.width as usize) * BYTES_PER_PIXEL;
228 for col in x_start..x_end {
229 let offset = row_offset + (col as usize) * BYTES_PER_PIXEL;
230 self.data[offset..offset + BYTES_PER_PIXEL].copy_from_slice(&rgba);
231 }
232 }
233 }
234
235 pub fn row_slice(&self, y: u32) -> Option<&[u8]> {
237 if y >= self.height {
238 return None;
239 }
240 let start = (y as usize) * (self.width as usize) * BYTES_PER_PIXEL;
241 let end = start + (self.width as usize) * BYTES_PER_PIXEL;
242 Some(&self.data[start..end])
243 }
244
245 pub fn row_slice_mut(&mut self, y: u32) -> Option<&mut [u8]> {
247 if y >= self.height {
248 return None;
249 }
250 let start = (y as usize) * (self.width as usize) * BYTES_PER_PIXEL;
251 let end = start + (self.width as usize) * BYTES_PER_PIXEL;
252 Some(&mut self.data[start..end])
253 }
254
255 pub fn blit_row(&mut self, x: u32, y: u32, src: &[u8], count: u32) -> bool {
259 if y >= self.height || x >= self.width {
260 return false;
261 }
262 let actual_count = count.min(self.width - x) as usize;
263 let src_bytes = actual_count * BYTES_PER_PIXEL;
264 if src.len() < src_bytes {
265 return false;
266 }
267 let offset = ((y as usize) * (self.width as usize) + (x as usize)) * BYTES_PER_PIXEL;
268 self.data[offset..offset + src_bytes].copy_from_slice(&src[..src_bytes]);
269 true
270 }
271
272 pub fn save_png(&self, path: &std::path::Path) -> std::io::Result<()> {
276 use image::{ImageBuffer, Rgba as ImgRgba};
277 let img: ImageBuffer<ImgRgba<u8>, _> =
278 ImageBuffer::from_raw(self.width, self.height, self.data.clone())
279 .expect("FrameBuffer data size mismatch");
280 img.save(path)
281 .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
282 }
283}
284
285