1use alloc::{vec, vec::Vec};
2
3pub mod generic;
4pub mod gpu;
5
6#[cfg(test)]
7pub mod tests;
8
9#[cfg(target_arch = "x86_64")]
10pub mod x86;
11
12#[cfg(target_arch = "aarch64")]
13pub mod aarch64;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct RgbColor {
18 pub r: u8,
19 pub g: u8,
20 pub b: u8,
21}
22
23impl RgbColor {
24 pub const fn new(r: u8, g: u8, b: u8) -> Self {
26 Self { r, g, b }
27 }
28
29 pub const BLACK: Self = Self::new(0x00, 0x00, 0x00);
30 pub const WHITE: Self = Self::new(0xFF, 0xFF, 0xFF);
31 pub const RED: Self = Self::new(0xFF, 0x00, 0x00);
32 pub const GREEN: Self = Self::new(0x00, 0xFF, 0x00);
33 pub const BLUE: Self = Self::new(0x00, 0x00, 0xFF);
34 pub const CYAN: Self = Self::new(0x00, 0xFF, 0xFF);
35 pub const MAGENTA: Self = Self::new(0xFF, 0x00, 0xFF);
36 pub const YELLOW: Self = Self::new(0xFF, 0xFF, 0x00);
37 pub const LIGHT_GREY: Self = Self::new(0xAA, 0xAA, 0xAA);
38}
39
40#[derive(Clone, Copy, Debug, Default)]
42pub struct DirtyRect {
43 pub x0: u32,
44 pub y0: u32,
45 pub x1: u32,
46 pub y1: u32,
47}
48
49impl DirtyRect {
50 pub const fn empty() -> Self {
51 Self {
52 x0: 0,
53 y0: 0,
54 x1: 0,
55 y1: 0,
56 }
57 }
58
59 pub fn is_valid(self) -> bool {
60 self.x0 < self.x1 && self.y0 < self.y1
61 }
62
63 pub fn width(self) -> u32 {
64 self.x1.saturating_sub(self.x0)
65 }
66
67 pub fn height(self) -> u32 {
68 self.y1.saturating_sub(self.y0)
69 }
70
71 pub fn include(&mut self, x: u32, y: u32, w: u32, h: u32) {
72 if w == 0 || h == 0 {
73 return;
74 }
75 let nx1 = x.saturating_add(w);
76 let ny1 = y.saturating_add(h);
77 if !self.is_valid() {
78 self.x0 = x;
79 self.y0 = y;
80 self.x1 = nx1;
81 self.y1 = ny1;
82 } else {
83 self.x0 = self.x0.min(x);
84 self.y0 = self.y0.min(y);
85 self.x1 = self.x1.max(nx1);
86 self.y1 = self.y1.max(ny1);
87 }
88 }
89
90 pub fn overlaps(self, other: DirtyRect) -> bool {
91 self.x0 < other.x1 && self.x1 > other.x0 && self.y0 < other.y1 && self.y1 > other.y0
92 }
93}
94
95pub const MAX_DIRTY_RECTS: usize = 8;
96
97#[derive(Clone, Copy)]
98pub struct DirtyRectSet {
99 pub rects: [DirtyRect; MAX_DIRTY_RECTS],
100 pub len: usize,
101}
102
103impl DirtyRectSet {
104 pub const fn empty() -> Self {
105 Self {
106 rects: [DirtyRect::empty(); MAX_DIRTY_RECTS],
107 len: 0,
108 }
109 }
110
111 pub fn clear(&mut self) {
112 self.len = 0;
113 }
114
115 pub fn include(&mut self, x: u32, y: u32, w: u32, h: u32) {
116 if w == 0 || h == 0 {
117 return;
118 }
119 let mut next = DirtyRect::empty();
120 next.include(x, y, w, h);
121
122 let mut idx = 0;
123 while idx < self.len {
124 let cur = self.rects[idx];
125 if !cur.is_valid() {
126 idx += 1;
127 continue;
128 }
129 if cur.overlaps(next) {
130 next.include(cur.x0, cur.y0, cur.width(), cur.height());
131 self.rects[idx] = self.rects[self.len - 1];
132 self.len -= 1;
133 idx = 0;
134 continue;
135 }
136 idx += 1;
137 }
138
139 if self.len < MAX_DIRTY_RECTS {
140 self.rects[self.len] = next;
141 self.len += 1;
142 return;
143 }
144 self.rects[0].include(next.x0, next.y0, next.width(), next.height());
145 }
146
147 pub fn iter(&self) -> DirtyRectIter {
148 DirtyRectIter { set: self, idx: 0 }
149 }
150}
151
152pub struct DirtyRectIter<'a> {
153 set: &'a DirtyRectSet,
154 idx: usize,
155}
156
157impl<'a> Iterator for DirtyRectIter<'a> {
158 type Item = &'a DirtyRect;
159
160 fn next(&mut self) -> Option<Self::Item> {
161 while self.idx < self.set.len {
162 let r = &self.set.rects[self.idx];
163 self.idx += 1;
164 if r.is_valid() {
165 return Some(r);
166 }
167 }
168 None
169 }
170}
171
172pub type FnFill = unsafe fn(dst: *mut u32, color: u32, count: usize);
174pub type FnBlit = unsafe fn(dst: *mut u32, src: *const u32, count: usize);
175pub type FnBlend = unsafe fn(dst: *mut u32, src: *const u32, alpha: u8, count: usize);
176pub type FnConvert = unsafe fn(dst: *mut u32, src: *const u8, count: usize);
177
178#[derive(Clone, Copy)]
179pub struct FramebufferOps {
180 pub fill: FnFill,
181 pub blit: FnBlit,
182 pub blend: FnBlend,
183 pub convert: FnConvert,
184}
185
186pub struct CanvasBuffer {
200 pub addr: *mut u8,
202 pub width: usize,
204 pub height: usize,
206 pub pitch: usize,
208 pub bpp: u16,
210 pub back_buffer: Option<Vec<u32>>,
212 pub draw_to_back: bool,
214 pub dirty: DirtyRectSet,
216 pub track_dirty: bool,
218 pub present_pending: bool,
220 pub last_present_tick: u64,
222 pub ops: FramebufferOps,
224}
225
226unsafe impl Send for CanvasBuffer {}
227
228impl CanvasBuffer {
229 pub fn enable_back_buffer(&mut self) -> bool {
231 if self.back_buffer.is_some() {
232 return true;
233 }
234 let total = self.width.saturating_mul(self.height);
235 if total == 0 || self.addr.is_null() {
236 return false;
237 }
238 let mut buf = alloc::vec![0u32; total];
239 if self.bpp == 32 {
241 unsafe {
242 core::ptr::copy_nonoverlapping(
243 self.addr as *const u8,
244 buf.as_mut_ptr() as *mut u8,
245 total * 4,
246 );
247 }
248 } else {
249 for y in 0..self.height {
250 for x in 0..self.width {
251 buf[y * self.width + x] = self.read_hw_pixel(x, y);
252 }
253 }
254 }
255 self.back_buffer = Some(buf);
256 self.draw_to_back = true;
257 self.track_dirty = true;
258 self.dirty.clear();
259 true
260 }
261
262 pub fn disable_back_buffer(&mut self, do_present: bool) {
264 if do_present {
265 self.present();
266 }
267 self.draw_to_back = false;
268 self.track_dirty = false;
269 self.dirty.clear();
270 }
271
272 #[inline]
274 pub fn pixel_offset(&self, x: usize, y: usize) -> Option<usize> {
275 if x >= self.width || y >= self.height {
276 return None;
277 }
278 let bytes_pp = (self.bpp / 8) as usize;
279 Some(
280 y.checked_mul(self.pitch)?
281 .checked_add(x.checked_mul(bytes_pp)?)?,
282 )
283 }
284
285 pub fn read_hw_pixel(&self, x: usize, y: usize) -> u32 {
287 let Some(off) = self.pixel_offset(x, y) else {
288 return 0;
289 };
290 unsafe {
291 match self.bpp {
292 32 => core::ptr::read_volatile(self.addr.add(off) as *const u32),
293 24 => {
294 let b0 = core::ptr::read_volatile(self.addr.add(off)) as u32;
295 let b1 = core::ptr::read_volatile(self.addr.add(off + 1)) as u32;
296 let b2 = core::ptr::read_volatile(self.addr.add(off + 2)) as u32;
297 b0 | (b1 << 8) | (b2 << 16)
298 }
299 _ => 0,
300 }
301 }
302 }
303
304 pub fn write_hw_pixel(&mut self, x: usize, y: usize, color: u32) {
306 let Some(off) = self.pixel_offset(x, y) else {
307 return;
308 };
309 unsafe {
310 match self.bpp {
311 32 => {
312 core::ptr::write_volatile(self.addr.add(off) as *mut u32, color);
313 }
314 24 => {
315 core::ptr::write_volatile(self.addr.add(off), (color & 0xFF) as u8);
316 core::ptr::write_volatile(self.addr.add(off + 1), ((color >> 8) & 0xFF) as u8);
317 core::ptr::write_volatile(self.addr.add(off + 2), ((color >> 16) & 0xFF) as u8);
318 }
319 _ => {}
320 }
321 }
322 }
323
324 pub fn read_pixel(&self, x: usize, y: usize) -> u32 {
326 if x >= self.width || y >= self.height {
327 return 0;
328 }
329 if self.draw_to_back {
330 if let Some(buf) = self.back_buffer.as_ref() {
331 return buf[y * self.width + x];
332 }
333 }
334 self.read_hw_pixel(x, y)
335 }
336
337 pub fn write_pixel(&mut self, x: usize, y: usize, color: u32) {
339 if x >= self.width || y >= self.height {
340 return;
341 }
342 if self.draw_to_back {
343 if let Some(buf) = self.back_buffer.as_mut() {
344 buf[y * self.width + x] = color;
345 self.dirty.include(x as u32, y as u32, 1, 1);
346 return;
347 }
348 }
349 self.write_hw_pixel(x, y, color);
350 }
351
352 pub fn fill_rect(&mut self, x: usize, y: usize, w: usize, h: usize, color: u32) {
354 if w == 0 || h == 0 {
355 return;
356 }
357 let x_end = core::cmp::min(x.saturating_add(w), self.width);
358 let y_end = core::cmp::min(y.saturating_add(h), self.height);
359 if x_end <= x || y_end <= y {
360 return;
361 }
362 let fw = self.width;
363 let sw = x_end - x;
364 let sh = y_end - y;
365
366 if self.draw_to_back {
367 if let Some(buf) = self.back_buffer.as_mut() {
368 for py in y..y_end {
369 let start = py * fw + x;
370 buf[start..start + sw].fill(color);
371 }
372 self.dirty.include(x as u32, y as u32, sw as u32, sh as u32);
373 return;
374 }
375 }
376
377 if self.bpp == 32 {
378 for py in y..y_end {
379 if let Some(off) = py
380 .checked_mul(self.pitch)
381 .and_then(|r| r.checked_add(x * 4))
382 {
383 unsafe {
384 let ptr = self.addr.add(off) as *mut u32;
385 (self.ops.fill)(ptr, color, sw);
386 }
387 }
388 }
389 } else {
390 for py in y..y_end {
391 for px in x..x_end {
392 self.write_hw_pixel(px, py, color);
393 }
394 }
395 }
396 if self.track_dirty {
397 self.dirty.include(x as u32, y as u32, sw as u32, sh as u32);
398 }
399 }
400
401 pub fn present(&mut self) {
406 let Some(buf) = self.back_buffer.as_ref() else {
407 return;
408 };
409 if self.track_dirty && self.dirty.len == 0 {
410 return;
411 }
412 let buf_ptr = buf.as_ptr();
413 let pitch = self.pitch;
414 let fb_addr = self.addr;
415
416 let mut regions: [(u32, u32, u32, u32); MAX_DIRTY_RECTS] = [(0, 0, 0, 0); MAX_DIRTY_RECTS];
418 let mut region_count = 0usize;
419
420 if self.track_dirty {
421 for r in self.dirty.iter() {
422 if region_count < MAX_DIRTY_RECTS {
423 regions[region_count] = (r.x0, r.y0, r.width(), r.height());
424 region_count += 1;
425 }
426 }
427 } else {
428 regions[0] = (0, 0, self.width as u32, self.height as u32);
429 region_count = 1;
430 }
431
432 let stride_pixels_u32 = pitch / 4; for i in 0..region_count {
434 let (rx, ry, rw, rh) = regions[i];
435 if rw == 0 || rh == 0 {
436 continue;
437 }
438 let (rx, ry, rw, rh) = (rx as usize, ry as usize, rw as usize, rh as usize);
439
440 if self.bpp == 32 {
442 if self.width == stride_pixels_u32 {
443 let off = ry * self.width + rx;
444 let dst_off = ry * pitch + rx * 4;
445 unsafe {
446 let src = buf_ptr.add(off) as *const u8;
447 (self.ops.blit)(
448 fb_addr.add(dst_off) as *mut u32,
449 src as *const u32,
450 rw * rh,
451 );
452 }
454 } else {
455 for row in 0..rh {
456 let src_off = (ry + row) * self.width + rx;
457 let dst_off = (ry + row) * pitch + rx * 4;
458 unsafe {
459 let src = buf_ptr.add(src_off) as *const u32;
460 (self.ops.blit)(fb_addr.add(dst_off) as *mut u32, src, rw);
461 }
462 }
463 }
464 } else {
465 let row_bytes = rw * 3;
467 let mut row_buf = alloc::vec![0u8; row_bytes];
468 for row in 0..rh {
469 let src_row = (ry + row) * self.width + rx;
470 for x in 0..rw {
471 let packed = unsafe { *buf_ptr.add(src_row + x) };
472 let off = x * 3;
473 row_buf[off] = packed as u8;
474 row_buf[off + 1] = (packed >> 8) as u8;
475 row_buf[off + 2] = (packed >> 16) as u8;
476 }
477 let dst_off = (ry + row) * pitch + rx * 3;
478 unsafe {
479 core::ptr::copy_nonoverlapping(
480 row_buf.as_ptr(),
481 fb_addr.add(dst_off),
482 row_bytes,
483 );
484 }
485 }
486 }
487 }
488
489 self.dirty.clear();
490 self.present_pending = false;
491 }
492
493 pub fn request_present(&mut self) {
495 if !self.draw_to_back {
496 return;
497 }
498 self.present_pending = true;
499 }
500
501 pub fn present_if_due(&mut self, force: bool, now: u64) {
503 if !self.present_pending || !self.draw_to_back {
504 return;
505 }
506 if force || now.saturating_sub(self.last_present_tick) >= PRESENT_MIN_TICKS_CB {
507 self.last_present_tick = now;
508 self.present();
509 }
510 }
511}
512
513const PRESENT_MIN_TICKS_CB: u64 = 1;
515
516impl FramebufferOps {
517 pub fn detect() -> Self {
518 #[cfg(target_arch = "x86_64")]
519 {
520 return x86::detect_and_init_ops();
521 }
522
523 #[cfg(target_arch = "aarch64")]
524 {
525 return aarch64::detect_and_init_ops();
528 }
529
530 #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
531 {
532 Self {
533 fill: generic::fill_generic,
534 blit: generic::blit_generic,
535 blend: generic::blend_generic,
536 convert: generic::convert_bgr_to_argb_generic,
537 }
538 }
539 }
540}