Skip to main content

strat9_kernel/framebuffer/
mod.rs

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// Shared RgbColor type used by all framebuffer/VGA code.
16#[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    /// Creates a new instance.
25    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// Shared dirty-rect tracking used by VGA writer and video framebuffer.
41#[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
172// Type definitions for our framebuffer operations
173pub 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
186// ---------------------------------------------------------------------------
187// CanvasBuffer : shared double-buffered framebuffer canvas
188// ---------------------------------------------------------------------------
189
190/// A raw framebuffer canvas with optional double buffering.
191///
192/// Encapsulates the HW framebuffer pointer, optional back-buffer (always
193/// 32bpp), dirty-region tracking, SIMD-accelerated pixel ops, and a
194/// `present()` that flushes dirty regions to the real hardware.
195///
196/// Used by both the VGA text-mode writer (`VgaWriter`) and the raw
197/// hardware video driver (`hardware::video::Framebuffer`), eliminating
198/// the duplicated `present()` / dirty-tracking logic between them.
199pub struct CanvasBuffer {
200    /// Pointer to the start of the HW framebuffer (HHDM-mapped virtual).
201    pub addr: *mut u8,
202    /// Width in pixels.
203    pub width: usize,
204    /// Height in pixels.
205    pub height: usize,
206    /// Bytes per row (stride / pitch).
207    pub pitch: usize,
208    /// Bits per pixel (24 or 32).
209    pub bpp: u16,
210    /// Optional back-buffer (always 32bpp / `u32` per pixel).
211    pub back_buffer: Option<Vec<u32>>,
212    /// If `true`, draw operations write to `back_buffer` instead of HW.
213    pub draw_to_back: bool,
214    /// Set of dirty rectangles pending a `present()`.
215    pub dirty: DirtyRectSet,
216    /// If `true`, dirty rectangles are tracked.
217    pub track_dirty: bool,
218    /// Whether a `present()` has been requested.
219    pub present_pending: bool,
220    /// Tick of the last `present()` (for rate-limiting).
221    pub last_present_tick: u64,
222    /// SIMD-accelerated pixel ops (fill, blit, blend, convert).
223    pub ops: FramebufferOps,
224}
225
226unsafe impl Send for CanvasBuffer {}
227
228impl CanvasBuffer {
229    /// Enable double-buffering by allocating and syncing a back-buffer.
230    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        // Sync current HW content into back-buffer.
240        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    /// Disable double-buffering, optionally presenting first.
263    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    /// Byte offset in HW memory for pixel `(x, y)`.
273    #[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    /// Read a packed pixel directly from HW memory.
286    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    /// Write a packed pixel to HW memory.
305    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    /// Read pixel `(x, y)` : from back-buffer if available, else HW.
325    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    /// Write pixel `(x, y)` : to back-buffer if available, else HW.
338    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    /// Fill a rectangle with a packed colour.
353    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    /// Flush dirty back-buffer regions to the hardware framebuffer.
402    ///
403    /// Uses SIMD-accelerated `ops.blit` when the back-buffer's row stride
404    /// matches the frame-buffer's stride, falling back to row-by-row copies.
405    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        // Collect regions to present.
417        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; // stride in u32 units
433        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            // Fast path: row stride matches, blit contiguous block.
441            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                        // (ops.blit) uses copy_nonoverlapping internally
453                    }
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                // 24bpp fallback: convert row-by-row.
466                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    /// Request a present (throttled).
494    pub fn request_present(&mut self) {
495        if !self.draw_to_back {
496            return;
497        }
498        self.present_pending = true;
499    }
500
501    /// Present if a request is pending and the throttle delay has elapsed.
502    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
513/// Local constant for present throttling (matches VgaWriter's PRESENT_MIN_TICKS).
514const 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            // ARM CPUID detection for NEON could be done here,
526            // but for now we fallback to generic or neon if compiled with it.
527            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}