1use alloc::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 fn adjacent(self, other: DirtyRect) -> bool {
96 (self.y0 == other.y0 && self.y1 == other.y1 && (self.x1 == other.x0 || other.x1 == self.x0))
97 || (self.x0 == other.x0
98 && self.x1 == other.x1
99 && (self.y1 == other.y0 || other.y1 == self.y0))
100 }
101}
102
103pub const MAX_DIRTY_RECTS: usize = 8;
104
105#[derive(Clone, Copy)]
106pub struct DirtyRectSet {
107 pub rects: [DirtyRect; MAX_DIRTY_RECTS],
108 pub len: usize,
109}
110
111impl DirtyRectSet {
112 pub const fn empty() -> Self {
113 Self {
114 rects: [DirtyRect::empty(); MAX_DIRTY_RECTS],
115 len: 0,
116 }
117 }
118
119 pub fn clear(&mut self) {
120 self.len = 0;
121 }
122
123 pub fn include(&mut self, x: u32, y: u32, w: u32, h: u32) {
124 if w == 0 || h == 0 {
125 return;
126 }
127 let mut next = DirtyRect::empty();
128 next.include(x, y, w, h);
129
130 let mut idx = 0;
131 while idx < self.len {
132 let cur = self.rects[idx];
133 if !cur.is_valid() {
134 idx += 1;
135 continue;
136 }
137 if cur.overlaps(next) || cur.adjacent(next) {
138 next.include(cur.x0, cur.y0, cur.width(), cur.height());
139 self.rects[idx] = self.rects[self.len - 1];
140 self.len -= 1;
141 idx = 0;
142 continue;
143 }
144 idx += 1;
145 }
146
147 if self.len < MAX_DIRTY_RECTS {
148 self.rects[self.len] = next;
149 self.len += 1;
150 return;
151 }
152 self.rects[0].include(next.x0, next.y0, next.width(), next.height());
153 }
154
155 pub fn iter(&self) -> DirtyRectIter<'_> {
156 DirtyRectIter { set: self, idx: 0 }
157 }
158}
159
160pub struct DirtyRectIter<'a> {
161 set: &'a DirtyRectSet,
162 idx: usize,
163}
164
165impl<'a> Iterator for DirtyRectIter<'a> {
166 type Item = &'a DirtyRect;
167
168 fn next(&mut self) -> Option<Self::Item> {
169 while self.idx < self.set.len {
170 let r = &self.set.rects[self.idx];
171 self.idx += 1;
172 if r.is_valid() {
173 return Some(r);
174 }
175 }
176 None
177 }
178}
179
180pub type FnFill = unsafe fn(dst: *mut u32, color: u32, count: usize);
182pub type FnBlit = unsafe fn(dst: *mut u32, src: *const u32, count: usize);
183pub type FnBlend = unsafe fn(dst: *mut u32, src: *const u32, alpha: u8, count: usize);
184pub type FnConvert = unsafe fn(dst: *mut u32, src: *const u8, count: usize);
185
186#[derive(Clone, Copy)]
187pub struct FramebufferOps {
188 pub fill: FnFill,
189 pub blit: FnBlit,
190 pub blend: FnBlend,
191 pub convert: FnConvert,
192}
193
194pub struct CanvasBuffer {
208 pub addr: *mut u8,
210 pub width: usize,
212 pub height: usize,
214 pub pitch: usize,
216 pub bpp: u16,
218 pub back_buffer: Option<Vec<u32>>,
220 pub draw_to_back: bool,
222 pub dirty: DirtyRectSet,
224 pub track_dirty: bool,
226 pub present_pending: bool,
228 pub last_present_tick: u64,
230 pub ops: FramebufferOps,
232 pub present_row_buf: Option<Vec<u8>>,
235}
236
237unsafe impl Send for CanvasBuffer {}
238
239impl CanvasBuffer {
240 pub fn enable_back_buffer(&mut self) -> bool {
242 if self.back_buffer.is_some() {
243 return true;
244 }
245 let total = self.width.saturating_mul(self.height);
246 if total == 0 || self.addr.is_null() {
247 return false;
248 }
249 let mut buf = alloc::vec![0u32; total];
250 if self.bpp == 32 {
252 unsafe {
253 for y in 0..self.height {
254 core::ptr::copy_nonoverlapping(
255 self.addr.add(y * self.pitch),
256 buf.as_mut_ptr().add(y * self.width) as *mut u8,
257 self.width * 4,
258 );
259 }
260 }
261 } else {
262 for y in 0..self.height {
263 for x in 0..self.width {
264 buf[y * self.width + x] = self.read_hw_pixel(x, y);
265 }
266 }
267 }
268 self.back_buffer = Some(buf);
269 self.draw_to_back = true;
270 self.track_dirty = true;
271 self.dirty.clear();
272 true
273 }
274
275 pub fn disable_back_buffer(&mut self, do_present: bool) {
277 if do_present {
278 self.present();
279 }
280 self.draw_to_back = false;
281 self.track_dirty = false;
282 self.dirty.clear();
283 }
284
285 #[inline]
287 pub fn pixel_offset(&self, x: usize, y: usize) -> Option<usize> {
288 if x >= self.width || y >= self.height {
289 return None;
290 }
291 let bytes_pp = (self.bpp / 8) as usize;
292 Some(
293 y.checked_mul(self.pitch)?
294 .checked_add(x.checked_mul(bytes_pp)?)?,
295 )
296 }
297
298 pub fn read_hw_pixel(&self, x: usize, y: usize) -> u32 {
300 let Some(off) = self.pixel_offset(x, y) else {
301 return 0;
302 };
303 unsafe {
304 match self.bpp {
305 32 => core::ptr::read_volatile(self.addr.add(off) as *const u32),
306 24 => {
307 let b0 = core::ptr::read_volatile(self.addr.add(off)) as u32;
308 let b1 = core::ptr::read_volatile(self.addr.add(off + 1)) as u32;
309 let b2 = core::ptr::read_volatile(self.addr.add(off + 2)) as u32;
310 b0 | (b1 << 8) | (b2 << 16)
311 }
312 _ => 0,
313 }
314 }
315 }
316
317 pub fn write_hw_pixel(&mut self, x: usize, y: usize, color: u32) {
319 let Some(off) = self.pixel_offset(x, y) else {
320 return;
321 };
322 unsafe {
323 match self.bpp {
324 32 => {
325 core::ptr::write_volatile(self.addr.add(off) as *mut u32, color);
326 }
327 24 => {
328 core::ptr::write_volatile(self.addr.add(off), (color & 0xFF) as u8);
329 core::ptr::write_volatile(self.addr.add(off + 1), ((color >> 8) & 0xFF) as u8);
330 core::ptr::write_volatile(self.addr.add(off + 2), ((color >> 16) & 0xFF) as u8);
331 }
332 _ => {}
333 }
334 }
335 }
336
337 pub fn read_pixel(&self, x: usize, y: usize) -> u32 {
339 if x >= self.width || y >= self.height {
340 return 0;
341 }
342 if self.draw_to_back {
343 if let Some(buf) = self.back_buffer.as_ref() {
344 return buf[y * self.width + x];
345 }
346 }
347 self.read_hw_pixel(x, y)
348 }
349
350 pub fn write_pixel(&mut self, x: usize, y: usize, color: u32) {
357 if x >= self.width || y >= self.height {
358 return;
359 }
360 if self.draw_to_back {
361 if let Some(buf) = self.back_buffer.as_mut() {
362 buf[y * self.width + x] = color;
363 return;
364 }
365 }
366 self.write_hw_pixel(x, y, color);
367 }
368
369 pub fn fill_rect(&mut self, x: usize, y: usize, w: usize, h: usize, color: u32) {
371 if w == 0 || h == 0 {
372 return;
373 }
374 let x_end = core::cmp::min(x.saturating_add(w), self.width);
375 let y_end = core::cmp::min(y.saturating_add(h), self.height);
376 if x_end <= x || y_end <= y {
377 return;
378 }
379 let fw = self.width;
380 let sw = x_end - x;
381 let sh = y_end - y;
382
383 if self.draw_to_back {
384 if let Some(buf) = self.back_buffer.as_mut() {
385 for py in y..y_end {
386 let start = py * fw + x;
387 buf[start..start + sw].fill(color);
388 }
389 self.dirty.include(x as u32, y as u32, sw as u32, sh as u32);
390 return;
391 }
392 }
393
394 if self.bpp == 32 {
395 for py in y..y_end {
396 if let Some(off) = py
397 .checked_mul(self.pitch)
398 .and_then(|r| r.checked_add(x * 4))
399 {
400 unsafe {
401 let ptr = self.addr.add(off) as *mut u32;
402 (self.ops.fill)(ptr, color, sw);
403 }
404 }
405 }
406 } else {
407 for py in y..y_end {
408 for px in x..x_end {
409 self.write_hw_pixel(px, py, color);
410 }
411 }
412 }
413 if self.track_dirty {
414 self.dirty.include(x as u32, y as u32, sw as u32, sh as u32);
415 }
416 }
417
418 pub fn present(&mut self) {
423 let Some(buf) = self.back_buffer.as_ref() else {
424 return;
425 };
426 if self.track_dirty && self.dirty.len == 0 {
427 return;
428 }
429 let buf_ptr = buf.as_ptr();
430 let pitch = self.pitch;
431 let fb_addr = self.addr;
432
433 let mut regions: [(u32, u32, u32, u32); MAX_DIRTY_RECTS] = [(0, 0, 0, 0); MAX_DIRTY_RECTS];
435 let mut region_count = 0usize;
436
437 if self.track_dirty {
438 for r in self.dirty.iter() {
439 if region_count < MAX_DIRTY_RECTS {
440 regions[region_count] = (r.x0, r.y0, r.width(), r.height());
441 region_count += 1;
442 }
443 }
444 } else {
445 regions[0] = (0, 0, self.width as u32, self.height as u32);
446 region_count = 1;
447 }
448
449 let stride_pixels_u32 = pitch / 4; for i in 0..region_count {
451 let (rx, ry, rw, rh) = regions[i];
452 if rw == 0 || rh == 0 {
453 continue;
454 }
455 let (rx, ry, rw, rh) = (rx as usize, ry as usize, rw as usize, rh as usize);
456
457 if self.bpp == 32 {
459 if self.width == stride_pixels_u32 && rx == 0 && rw == self.width {
460 let off = ry * self.width + rx;
461 let dst_off = ry * pitch + rx * 4;
462 unsafe {
463 let src = buf_ptr.add(off) as *const u8;
464 (self.ops.blit)(
465 fb_addr.add(dst_off) as *mut u32,
466 src as *const u32,
467 rw * rh,
468 );
469 }
471 } else {
472 for row in 0..rh {
473 let src_off = (ry + row) * self.width + rx;
474 let dst_off = (ry + row) * pitch + rx * 4;
475 unsafe {
476 let src = buf_ptr.add(src_off) as *const u32;
477 (self.ops.blit)(fb_addr.add(dst_off) as *mut u32, src, rw);
478 }
479 }
480 }
481 } else {
482 let row_bytes = rw * 3;
485 let row_buf = self
486 .present_row_buf
487 .get_or_insert_with(|| Vec::with_capacity(row_bytes));
488 row_buf.clear();
489 row_buf.resize(row_bytes, 0);
490 for row in 0..rh {
491 let src_row = (ry + row) * self.width + rx;
492 for x in 0..rw {
493 let packed = unsafe { *buf_ptr.add(src_row + x) };
494 let off = x * 3;
495 row_buf[off] = packed as u8;
496 row_buf[off + 1] = (packed >> 8) as u8;
497 row_buf[off + 2] = (packed >> 16) as u8;
498 }
499 let dst_off = (ry + row) * pitch + rx * 3;
500 unsafe {
501 core::ptr::copy_nonoverlapping(
502 row_buf.as_ptr(),
503 fb_addr.add(dst_off),
504 row_bytes,
505 );
506 }
507 }
508 }
509 }
510
511 self.dirty.clear();
512 self.present_pending = false;
513 }
514
515 pub fn request_present(&mut self) {
517 if !self.draw_to_back {
518 return;
519 }
520 self.present_pending = true;
521 }
522
523 pub fn present_if_due(&mut self, force: bool, now: u64) {
525 if !self.present_pending || !self.draw_to_back {
526 return;
527 }
528 if force || now.saturating_sub(self.last_present_tick) >= PRESENT_MIN_TICKS {
529 self.last_present_tick = now;
530 self.present();
531 }
532 }
533}
534
535pub const PRESENT_MIN_TICKS: u64 = 1;
542
543impl FramebufferOps {
544 pub fn detect() -> Self {
545 #[cfg(target_arch = "x86_64")]
546 {
547 return x86::detect_and_init_ops();
548 }
549
550 #[cfg(target_arch = "aarch64")]
551 {
552 return aarch64::detect_and_init_ops();
555 }
556
557 #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
558 {
559 Self {
560 fill: generic::fill_generic,
561 blit: generic::blit_generic,
562 blend: generic::blend_generic,
563 convert: generic::convert_bgr_to_argb_generic,
564 }
565 }
566 }
567}