Skip to main content

strat9_kernel/hardware/video/
framebuffer.rs

1// Framebuffer abstraction layer
2//
3// Provides a unified framebuffer interface that can use:
4// - Limine framebuffer (bootloader-provided)
5// - VirtIO GPU framebuffer (native driver)
6// - Future: Other GPU drivers (Bochs DRM, etc.)
7//
8// Features:
9// - Resolution switching
10// - Double buffering
11// - Basic 2D drawing primitives
12// - Text rendering support
13
14#![allow(dead_code)]
15
16use crate::{
17    hardware::virtio::gpu,
18    memory::{self, phys_to_virt},
19};
20use core::sync::atomic::{AtomicBool, Ordering};
21use spin::Mutex;
22
23/// Maximum supported resolution
24const MAX_WIDTH: u32 = 3840;
25const MAX_HEIGHT: u32 = 2160;
26
27/// Framebuffer source
28#[derive(Clone, Copy, PartialEq, Eq, Debug)]
29pub enum FramebufferSource {
30    Limine,
31    VirtioGpu,
32    None,
33}
34
35/// Pixel format
36#[derive(Clone, Copy, Debug)]
37pub struct PixelFormat {
38    pub red_mask: u32,
39    pub red_shift: u8,
40    pub green_mask: u32,
41    pub green_shift: u8,
42    pub blue_mask: u32,
43    pub blue_shift: u8,
44    pub bits_per_pixel: u8,
45}
46
47impl Default for PixelFormat {
48    /// Builds a default instance.
49    fn default() -> Self {
50        Self {
51            red_mask: 0x00FF0000,
52            red_shift: 16,
53            green_mask: 0x0000FF00,
54            green_shift: 8,
55            blue_mask: 0x000000FF,
56            blue_shift: 0,
57            bits_per_pixel: 32,
58        }
59    }
60}
61
62/// Framebuffer information
63#[derive(Clone, Copy, Debug)]
64pub struct FramebufferInfo {
65    pub base: u64,
66    pub base_virt: usize,
67    pub width: u32,
68    pub height: u32,
69    pub stride: u32,
70    pub format: PixelFormat,
71    pub source: FramebufferSource,
72}
73
74unsafe impl Send for FramebufferInfo {}
75unsafe impl Sync for FramebufferInfo {}
76
77/// Main framebuffer structure
78pub struct Framebuffer {
79    info: FramebufferInfo,
80    double_buffer: Option<*mut u8>,
81    use_double_buffer: bool,
82    dirty: DirtyRect,
83}
84
85unsafe impl Send for Framebuffer {}
86unsafe impl Sync for Framebuffer {}
87
88static FRAMEBUFFER: Mutex<Option<Framebuffer>> = Mutex::new(None);
89static FRAMEBUFFER_INITIALIZED: AtomicBool = AtomicBool::new(false);
90
91#[derive(Clone, Copy)]
92struct DirtyRect {
93    valid: bool,
94    x0: u32,
95    y0: u32,
96    x1: u32,
97    y1: u32,
98}
99
100impl DirtyRect {
101    /// Performs the empty operation.
102    const fn empty() -> Self {
103        Self {
104            valid: false,
105            x0: 0,
106            y0: 0,
107            x1: 0,
108            y1: 0,
109        }
110    }
111
112    /// Performs the include operation.
113    fn include(&mut self, x: u32, y: u32, width: u32, height: u32) {
114        if width == 0 || height == 0 {
115            return;
116        }
117        let x1 = x.saturating_add(width);
118        let y1 = y.saturating_add(height);
119        if !self.valid {
120            self.valid = true;
121            self.x0 = x;
122            self.y0 = y;
123            self.x1 = x1;
124            self.y1 = y1;
125        } else {
126            self.x0 = self.x0.min(x);
127            self.y0 = self.y0.min(y);
128            self.x1 = self.x1.max(x1);
129            self.y1 = self.y1.max(y1);
130        }
131    }
132
133    /// Performs the take operation.
134    fn take(&mut self) -> Option<(u32, u32, u32, u32)> {
135        if !self.valid {
136            return None;
137        }
138        let x = self.x0;
139        let y = self.y0;
140        let width = self.x1.saturating_sub(self.x0);
141        let height = self.y1.saturating_sub(self.y0);
142        *self = Self::empty();
143        Some((x, y, width, height))
144    }
145}
146
147impl Framebuffer {
148    /// Initialize framebuffer with Limine-provided buffer
149    pub fn init_limine(
150        addr: u64,
151        width: u32,
152        height: u32,
153        stride: u32,
154        format: PixelFormat,
155    ) -> Result<(), &'static str> {
156        if addr == 0 || width == 0 || height == 0 {
157            return Err("Invalid framebuffer parameters");
158        }
159
160        let base_virt = addr as usize;
161
162        let info = FramebufferInfo {
163            base: addr,
164            base_virt,
165            width,
166            height,
167            stride,
168            format,
169            source: FramebufferSource::Limine,
170        };
171
172        let fb = Framebuffer {
173            info,
174            double_buffer: None,
175            use_double_buffer: false,
176            dirty: DirtyRect::empty(),
177        };
178
179        *FRAMEBUFFER.lock() = Some(fb);
180        FRAMEBUFFER_INITIALIZED.store(true, Ordering::SeqCst);
181
182        log::info!(
183            "[FB] Limine framebuffer: {}x{} @ {}bpp, stride={}",
184            width,
185            height,
186            format.bits_per_pixel,
187            stride
188        );
189
190        Ok(())
191    }
192
193    /// Initialize framebuffer with VirtIO GPU
194    pub fn init_virtio_gpu() -> Result<(), &'static str> {
195        let gpu_info = gpu::get_framebuffer_info().ok_or("VirtIO GPU not initialized")?;
196
197        let format = PixelFormat {
198            red_mask: 0x00FF0000,
199            red_shift: 16,
200            green_mask: 0x0000FF00,
201            green_shift: 8,
202            blue_mask: 0x000000FF,
203            blue_shift: 0,
204            bits_per_pixel: 32,
205        };
206
207        let info = FramebufferInfo {
208            base: gpu_info.framebuffer_phys,
209            base_virt: gpu_info.framebuffer_virt as usize,
210            width: gpu_info.width,
211            height: gpu_info.height,
212            stride: gpu_info.stride,
213            format,
214            source: FramebufferSource::VirtioGpu,
215        };
216
217        // Allocate double buffer for VirtIO GPU
218        let db_size = (info.stride as usize) * (info.height as usize);
219        if db_size == 0 {
220            return Err("Invalid VirtIO framebuffer size");
221        }
222        let db_pages = (db_size + 4095) / 4096;
223        let db_order = db_pages.next_power_of_two().trailing_zeros() as u8;
224        let db_frame = crate::sync::with_irqs_disabled(|token| {
225            memory::allocate_frames(token, db_order)
226        })
227        .map_err(|_| "Failed to allocate double buffer")?;
228        let db_virt = phys_to_virt(db_frame.start_address.as_u64()) as *mut u8;
229        unsafe {
230            // SAFETY: `db_virt` is a freshly allocated contiguous buffer of at
231            // least `db_size` bytes.
232            core::ptr::write_bytes(db_virt, 0, db_size);
233        }
234
235        let fb = Framebuffer {
236            info,
237            double_buffer: Some(db_virt),
238            use_double_buffer: true,
239            dirty: DirtyRect::empty(),
240        };
241
242        *FRAMEBUFFER.lock() = Some(fb);
243        FRAMEBUFFER_INITIALIZED.store(true, Ordering::SeqCst);
244
245        log::info!(
246            "[FB] VirtIO GPU framebuffer: {}x{} @ {}bpp, stride={}",
247            info.width,
248            info.height,
249            info.format.bits_per_pixel,
250            info.stride
251        );
252
253        Ok(())
254    }
255
256    /// Get framebuffer info
257    pub fn info() -> Option<FramebufferInfo> {
258        FRAMEBUFFER.lock().as_ref().map(|fb| fb.info)
259    }
260
261    /// Get framebuffer width
262    pub fn width() -> u32 {
263        FRAMEBUFFER
264            .lock()
265            .as_ref()
266            .map(|fb| fb.info.width)
267            .unwrap_or(0)
268    }
269
270    /// Get framebuffer height
271    pub fn height() -> u32 {
272        FRAMEBUFFER
273            .lock()
274            .as_ref()
275            .map(|fb| fb.info.height)
276            .unwrap_or(0)
277    }
278
279    /// Get stride (bytes per row)
280    pub fn stride() -> u32 {
281        FRAMEBUFFER
282            .lock()
283            .as_ref()
284            .map(|fb| fb.info.stride)
285            .unwrap_or(0)
286    }
287
288    /// Check if framebuffer is initialized
289    pub fn is_available() -> bool {
290        FRAMEBUFFER_INITIALIZED.load(Ordering::Relaxed)
291    }
292
293    /// Get framebuffer source
294    pub fn source() -> FramebufferSource {
295        FRAMEBUFFER
296            .lock()
297            .as_ref()
298            .map(|fb| fb.info.source)
299            .unwrap_or(FramebufferSource::None)
300    }
301
302    /// Set a pixel at (x, y) with RGB color
303    pub fn set_pixel(x: u32, y: u32, r: u8, g: u8, b: u8) {
304        let mut flush_region = None;
305        {
306            let mut guard = FRAMEBUFFER.lock();
307            let fb = match guard.as_mut() {
308                Some(f) => f,
309                None => return,
310            };
311
312            if x >= fb.info.width || y >= fb.info.height {
313                return;
314            }
315
316            let pixel = ((r as u32) << fb.info.format.red_shift)
317                | ((g as u32) << fb.info.format.green_shift)
318                | ((b as u32) << fb.info.format.blue_shift);
319
320            let offset = if fb.use_double_buffer {
321                fb.double_buffer.unwrap_or(fb.info.base_virt as *mut u8)
322            } else {
323                fb.info.base_virt as *mut u8
324            };
325
326            unsafe {
327                let pixel_ptr = offset.add((y * fb.info.stride + x * 4) as usize);
328                core::ptr::write(pixel_ptr as *mut u32, pixel);
329            }
330
331            fb.dirty.include(x, y, 1, 1);
332            if fb.info.source == FramebufferSource::VirtioGpu && !fb.use_double_buffer {
333                flush_region = Some((x, y, 1, 1));
334            }
335        }
336
337        if let Some((fx, fy, fw, fh)) = flush_region {
338            if let Some(gpu) = gpu::get_gpu() {
339                gpu.flush(fx, fy, fw, fh);
340                gpu.flush_now();
341            }
342        }
343    }
344
345    /// Fill rectangle with color
346    pub fn fill_rect(x: u32, y: u32, width: u32, height: u32, r: u8, g: u8, b: u8) {
347        if width == 0 || height == 0 {
348            return;
349        }
350
351        let mut flush_region = None;
352        {
353            let mut guard = FRAMEBUFFER.lock();
354            let fb = match guard.as_mut() {
355                Some(f) => f,
356                None => return,
357            };
358
359            if x >= fb.info.width || y >= fb.info.height {
360                return;
361            }
362
363            let max_w = fb.info.width - x;
364            let max_h = fb.info.height - y;
365            let width = width.min(max_w);
366            let height = height.min(max_h);
367            if width == 0 || height == 0 {
368                return;
369            }
370
371            let pixel = ((r as u32) << fb.info.format.red_shift)
372                | ((g as u32) << fb.info.format.green_shift)
373                | ((b as u32) << fb.info.format.blue_shift);
374
375            let offset = if fb.use_double_buffer {
376                fb.double_buffer.unwrap_or(fb.info.base_virt as *mut u8)
377            } else {
378                fb.info.base_virt as *mut u8
379            };
380
381            let stride = fb.info.stride as usize;
382            for dy in 0..height as usize {
383                let row_ptr =
384                    unsafe { offset.add((y as usize + dy) * stride + x as usize * 4) as *mut u32 };
385                for dx in 0..width as usize {
386                    unsafe {
387                        core::ptr::write(row_ptr.add(dx), pixel);
388                    }
389                }
390            }
391
392            fb.dirty.include(x, y, width, height);
393            if fb.info.source == FramebufferSource::VirtioGpu && !fb.use_double_buffer {
394                flush_region = Some((x, y, width, height));
395            }
396        }
397
398        if let Some((fx, fy, fw, fh)) = flush_region {
399            if let Some(gpu) = gpu::get_gpu() {
400                gpu.flush(fx, fy, fw, fh);
401                gpu.flush_now();
402            }
403        }
404    }
405
406    /// Draw a horizontal line
407    pub fn draw_hline(x: u32, y: u32, length: u32, r: u8, g: u8, b: u8) {
408        Self::fill_rect(x, y, length, 1, r, g, b);
409    }
410
411    /// Draw a vertical line
412    pub fn draw_vline(x: u32, y: u32, length: u32, r: u8, g: u8, b: u8) {
413        Self::fill_rect(x, y, 1, length, r, g, b);
414    }
415
416    /// Clear screen to black
417    pub fn clear() {
418        let info = Self::info();
419        if let Some(info) = info {
420            Self::fill_rect(0, 0, info.width, info.height, 0, 0, 0);
421        }
422    }
423
424    /// Swap buffers (for double buffering)
425    pub fn swap_buffers() {
426        let mut virtio_present = None;
427        {
428            let mut guard = FRAMEBUFFER.lock();
429            let fb = match guard.as_mut() {
430                Some(f) => f,
431                None => return,
432            };
433
434            if !fb.use_double_buffer || fb.double_buffer.is_none() {
435                return;
436            }
437
438            let db = fb.double_buffer.unwrap();
439            let dirty = match fb.dirty.take() {
440                Some(d) => d,
441                None => return,
442            };
443            let (x, y, width, height) = dirty;
444            if width == 0 || height == 0 {
445                return;
446            }
447
448            if fb.info.source == FramebufferSource::VirtioGpu {
449                virtio_present = Some((db as *const u8, fb.info.stride, x, y, width, height));
450            } else {
451                let dst = fb.info.base_virt as *mut u8;
452                let row_bytes = width as usize * 4;
453                let stride = fb.info.stride as usize;
454                for row in 0..height as usize {
455                    let row_y = y as usize + row;
456                    let src_off = row_y * stride + x as usize * 4;
457                    let dst_off = src_off;
458                    unsafe {
459                        core::ptr::copy_nonoverlapping(
460                            db.add(src_off),
461                            dst.add(dst_off),
462                            row_bytes,
463                        );
464                    }
465                }
466            }
467        }
468
469        if let Some((src, src_stride, px, py, pw, ph)) = virtio_present {
470            if let Some(gpu) = gpu::get_gpu() {
471                let _ = gpu.present_from_linear(src, src_stride, px, py, pw, ph);
472            }
473        }
474    }
475
476    /// Enable/disable double buffering
477    pub fn set_double_buffering(enable: bool) {
478        let mut fb = FRAMEBUFFER.lock();
479        if let Some(ref mut f) = fb.as_mut() {
480            f.use_double_buffer = enable && f.double_buffer.is_some();
481        }
482    }
483}
484
485/// RGB color helper
486#[derive(Clone, Copy)]
487pub struct RgbColor {
488    pub r: u8,
489    pub g: u8,
490    pub b: u8,
491}
492
493impl RgbColor {
494    pub const BLACK: Self = Self { r: 0, g: 0, b: 0 };
495    pub const WHITE: Self = Self {
496        r: 255,
497        g: 255,
498        b: 255,
499    };
500    pub const RED: Self = Self { r: 255, g: 0, b: 0 };
501    pub const GREEN: Self = Self { r: 0, g: 255, b: 0 };
502    pub const BLUE: Self = Self { r: 0, g: 0, b: 255 };
503    pub const CYAN: Self = Self {
504        r: 0,
505        g: 255,
506        b: 255,
507    };
508    pub const MAGENTA: Self = Self {
509        r: 255,
510        g: 0,
511        b: 255,
512    };
513    pub const YELLOW: Self = Self {
514        r: 255,
515        g: 255,
516        b: 0,
517    };
518}
519
520/// Initialize framebuffer subsystem
521pub fn init() {
522    log::info!("[FB] Initializing framebuffer subsystem...");
523
524    // Try VirtIO GPU first (native driver)
525    if gpu::is_available() {
526        if let Err(e) = Framebuffer::init_virtio_gpu() {
527            log::warn!("[FB] VirtIO GPU init failed: {}", e);
528        } else {
529            log::info!("[FB] Using VirtIO GPU framebuffer");
530            return;
531        }
532    }
533
534    // VirtIO GPU not available, Limine framebuffer is already set up in boot
535    // The Limine framebuffer is initialized in boot/limine.rs
536    if Framebuffer::is_available() {
537        log::info!("[FB] Using Limine framebuffer");
538    } else {
539        log::warn!("[FB] No framebuffer available");
540    }
541}