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// - UEFI bootloader framebuffer (bootloader-provided, UC on every boot alias)
5// - VirtIO GPU framebuffer (native driver, zero-copy backing)
6// - Future/TODO : 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// - SIMD pixel ops with streaming stores above the L2-derived threshold
14
15#![allow(dead_code)]
16
17use crate::{
18    framebuffer::{CanvasBuffer, DirtyRectSet, FramebufferOps, MAX_DIRTY_RECTS, PRESENT_MIN_TICKS},
19    hardware::virtio::gpu,
20    memory::{self, phys_to_virt},
21};
22use core::sync::atomic::{AtomicBool, Ordering};
23use spin::{Mutex, Once};
24
25/// Maximum supported resolution
26const MAX_WIDTH: u32 = 3840;
27const MAX_HEIGHT: u32 = 2160;
28
29/// Framebuffer source
30#[derive(Clone, Copy, PartialEq, Eq, Debug)]
31pub enum FramebufferSource {
32    Bootloader,
33    VirtioGpu,
34    AmdGpu,
35    None,
36}
37
38/// Pixel format
39#[derive(Clone, Copy, Debug)]
40pub struct PixelFormat {
41    pub red_mask: u32,
42    pub red_shift: u8,
43    pub green_mask: u32,
44    pub green_shift: u8,
45    pub blue_mask: u32,
46    pub blue_shift: u8,
47    pub bits_per_pixel: u8,
48}
49
50impl Default for PixelFormat {
51    /// Builds a default instance.
52    fn default() -> Self {
53        Self {
54            red_mask: 0x00FF0000,
55            red_shift: 16,
56            green_mask: 0x0000FF00,
57            green_shift: 8,
58            blue_mask: 0x000000FF,
59            blue_shift: 0,
60            bits_per_pixel: 32,
61        }
62    }
63}
64
65/// Framebuffer information
66#[derive(Clone, Copy, Debug)]
67pub struct FramebufferInfo {
68    pub base: u64,
69    pub base_virt: usize,
70    pub width: u32,
71    pub height: u32,
72    pub stride: u32,
73    pub format: PixelFormat,
74    pub source: FramebufferSource,
75}
76
77#[derive(Clone, Copy, Debug)]
78pub struct FramebufferRenderStats {
79    pub present_pending: bool,
80    pub dirty_region_count: usize,
81    pub last_present_tick: u64,
82}
83
84unsafe impl Send for FramebufferInfo {}
85unsafe impl Sync for FramebufferInfo {}
86
87/// Main framebuffer structure
88pub struct Framebuffer {
89    info: FramebufferInfo,
90    /// Shared double-buffered canvas (dirty tracking, present throttling, SIMD ops)
91    pub canvas: CanvasBuffer,
92    /// Raw double buffer for VirtIO GPU (physically contiguous)
93    double_buffer: Option<*mut u8>,
94    /// Whether the raw double buffer should be used
95    use_double_buffer: bool,
96    /// Total framebuffer size in bytes
97    fb_size: usize,
98    /// Whether console deferred present is active
99    console_defer_present: bool,
100    /// True when the VirtIO backing IS our double buffer (zero-copy present)
101    zero_copy_backing: bool,
102}
103
104unsafe impl Send for Framebuffer {}
105unsafe impl Sync for Framebuffer {}
106
107static FRAMEBUFFER: Mutex<Option<Framebuffer>> = Mutex::new(None);
108/// Lockless snapshot of the display geometry, published once at init.
109///
110/// Getters (`info`/`width`/`height`/`stride`/`source`) read this instead of
111/// taking the framebuffer mutex, so hot paths and frequent queries never
112/// contend with drawing (G4).
113static FB_INFO: Once<FramebufferInfo> = Once::new();
114static FRAMEBUFFER_INITIALIZED: AtomicBool = AtomicBool::new(false);
115
116fn publish_info(info: FramebufferInfo) {
117    FB_INFO.call_once(|| info);
118}
119
120impl Framebuffer {
121    fn request_present(&mut self) {
122        self.canvas.present_pending = true;
123    }
124
125    fn present_if_due(&mut self, force: bool) {
126        if !self.canvas.present_pending {
127            return;
128        }
129        if self.use_double_buffer {
130            return;
131        }
132        let now = crate::process::scheduler::ticks();
133        if force || now.saturating_sub(self.canvas.last_present_tick) >= PRESENT_MIN_TICKS {
134            self.canvas.present_pending = false;
135            self.canvas.last_present_tick = now;
136        } else {
137            return;
138        }
139
140        if self.info.source != FramebufferSource::VirtioGpu {
141            self.canvas.dirty.clear();
142            return;
143        }
144
145        if let Some(gpu) = gpu::get_gpu() {
146            let mut idx = 0;
147            while idx < self.canvas.dirty.len {
148                let rect = self.canvas.dirty.rects[idx];
149                if rect.is_valid() {
150                    let _ = unsafe {
151                        gpu.present_from_linear(
152                            self.info.base_virt as *const u8,
153                            self.info.stride,
154                            rect.x0,
155                            rect.y0,
156                            rect.x1.saturating_sub(rect.x0),
157                            rect.y1.saturating_sub(rect.y0),
158                        )
159                    };
160                }
161                idx += 1;
162            }
163        }
164        self.canvas.dirty.clear();
165    }
166
167    /// Initialize framebuffer with bootloader-provided buffer
168    pub fn init_bootloader(
169        addr: u64,
170        width: u32,
171        height: u32,
172        stride: u32,
173        format: PixelFormat,
174    ) -> Result<(), &'static str> {
175        if addr == 0 || width == 0 || height == 0 {
176            return Err("Invalid framebuffer parameters");
177        }
178
179        let base_virt = addr as usize;
180
181        let info = FramebufferInfo {
182            base: addr,
183            base_virt,
184            width,
185            height,
186            stride,
187            format,
188            source: FramebufferSource::Bootloader,
189        };
190
191        let canvas = CanvasBuffer {
192            addr: base_virt as *mut u8,
193            width: width as usize,
194            height: height as usize,
195            pitch: stride as usize,
196            bpp: format.bits_per_pixel as u16,
197            back_buffer: None,
198            draw_to_back: false,
199            dirty: DirtyRectSet::empty(),
200            track_dirty: true,
201            present_pending: false,
202            last_present_tick: 0,
203            ops: FramebufferOps::detect(),
204            present_row_buf: None,
205        };
206
207        let fb = Framebuffer {
208            info,
209            canvas,
210            double_buffer: None,
211            use_double_buffer: false,
212            fb_size: (stride as usize) * (height as usize),
213            console_defer_present: false,
214            zero_copy_backing: false,
215        };
216
217        *FRAMEBUFFER.lock() = Some(fb);
218        publish_info(info);
219        FRAMEBUFFER_INITIALIZED.store(true, Ordering::SeqCst);
220
221        log::info!(
222            "[FB] Bootloader framebuffer: {}x{} @ {}bpp, stride={}",
223            width,
224            height,
225            format.bits_per_pixel,
226            stride
227        );
228
229        Ok(())
230    }
231
232    /// Initialize framebuffer with VirtIO GPU
233    ///
234    /// G3: the double buffer is allocated FIRST and attached as the scanout
235    /// resource backing itself, so draws land directly in what
236    /// TRANSFER_TO_HOST_2D reads : presentation becomes zero-copy.
237    pub fn init_virtio_gpu() -> Result<(), &'static str> {
238        let gpu = crate::hardware::virtio::gpu::get_gpu().ok_or("VirtIO GPU not initialized")?;
239        let gpu_info = gpu.info();
240        if gpu_info.width == 0 || gpu_info.height == 0 {
241            return Err("Invalid VirtIO GPU dimensions");
242        }
243
244        let format = PixelFormat {
245            red_mask: 0x00FF0000,
246            red_shift: 16,
247            green_mask: 0x0000FF00,
248            green_shift: 8,
249            blue_mask: 0x000000FF,
250            blue_shift: 0,
251            bits_per_pixel: 32,
252        };
253
254        // Allocate double buffer for VirtIO GPU (physically contiguous).
255        let stride = gpu_info.stride;
256        let db_size = (stride as usize) * (gpu_info.height as usize);
257        if db_size == 0 {
258            return Err("Invalid VirtIO framebuffer size");
259        }
260        let db_pages = (db_size + 4095) / 4096;
261        let db_order = db_pages.next_power_of_two().trailing_zeros() as u8;
262        let db_frame = crate::sync::with_irqs_disabled(|token| {
263            memory::allocate_phys_contiguous(token, db_order)
264        })
265        .map_err(|_| "Failed to allocate double buffer")?;
266        let db_phys = db_frame.start_address.as_u64();
267        let db_virt = phys_to_virt(db_phys) as *mut u8;
268        unsafe {
269            // SAFETY: `db_virt` is a freshly allocated contiguous buffer of at
270            // least `db_size` bytes.
271            core::ptr::write_bytes(db_virt, 0, db_size);
272        }
273
274        // Attach the double buffer AS the resource backing (zero-copy path).
275        // On failure we fall back to the legacy bounce-buffer behaviour.
276        let zero_copy = match gpu.attach_external_backing(db_phys, db_size) {
277            Ok(()) => {
278                // Push the zeroed buffer to the scanout for a clean start.
279                let _ = gpu.present_scanout_rect(0, 0, gpu_info.width, gpu_info.height);
280                true
281            }
282            Err(e) => {
283                log::warn!(
284                    "[FB] external backing attach failed ({}): using bounce copy",
285                    e
286                );
287                false
288            }
289        };
290
291        let info = FramebufferInfo {
292            base: db_phys,
293            base_virt: db_virt as usize,
294            width: gpu_info.width,
295            height: gpu_info.height,
296            stride: gpu_info.stride,
297            format,
298            source: FramebufferSource::VirtioGpu,
299        };
300
301        let canvas = CanvasBuffer {
302            addr: db_virt,
303            width: info.width as usize,
304            height: info.height as usize,
305            pitch: info.stride as usize,
306            bpp: 32,
307            back_buffer: None,
308            draw_to_back: false,
309            dirty: DirtyRectSet::empty(),
310            track_dirty: true,
311            present_pending: false,
312            last_present_tick: 0,
313            ops: FramebufferOps::detect(),
314            present_row_buf: None,
315        };
316
317        let fb = Framebuffer {
318            info,
319            canvas,
320            double_buffer: Some(db_virt),
321            use_double_buffer: true,
322            fb_size: (stride as usize) * (gpu_info.height as usize),
323            console_defer_present: false,
324            zero_copy_backing: zero_copy,
325        };
326
327        *FRAMEBUFFER.lock() = Some(fb);
328        publish_info(info);
329        FRAMEBUFFER_INITIALIZED.store(true, Ordering::SeqCst);
330
331        log::info!(
332            "[FB] VirtIO GPU framebuffer: {}x{} @ {}bpp, stride={} ({})",
333            info.width,
334            info.height,
335            info.format.bits_per_pixel,
336            info.stride,
337            if zero_copy {
338                "zero-copy backing"
339            } else {
340                "bounce copy"
341            }
342        );
343
344        Ok(())
345    }
346
347    /// Initialize framebuffer with AMDGPU
348    pub fn init_amd_gpu() -> Result<(), &'static str> {
349        let gpu_guard = crate::hardware::amdgpu::get_gpu().ok_or("AMDGPU not available")?;
350        let gpu = gpu_guard.as_ref().ok_or("AMDGPU not initialized")?;
351
352        let (phys, width, height, pitch) = gpu.framebuffer_info();
353
354        let format = PixelFormat {
355            red_mask: 0x00FF0000,
356            red_shift: 16,
357            green_mask: 0x0000FF00,
358            green_shift: 8,
359            blue_mask: 0x000000FF,
360            blue_shift: 0,
361            bits_per_pixel: 32,
362        };
363
364        let hhdm = crate::memory::hhdm_offset();
365        let base_virt = (phys as u64 + hhdm) as usize;
366
367        let info = FramebufferInfo {
368            base: phys as u64,
369            base_virt,
370            width,
371            height,
372            stride: pitch,
373            format,
374            source: FramebufferSource::AmdGpu,
375        };
376
377        let canvas = CanvasBuffer {
378            addr: base_virt as *mut u8,
379            width: width as usize,
380            height: height as usize,
381            pitch: pitch as usize,
382            bpp: 32,
383            back_buffer: None,
384            draw_to_back: false,
385            dirty: DirtyRectSet::empty(),
386            track_dirty: true,
387            present_pending: false,
388            last_present_tick: 0,
389            ops: FramebufferOps::detect(),
390            present_row_buf: None,
391        };
392
393        *FRAMEBUFFER.lock() = Some(Framebuffer {
394            info,
395            canvas,
396            double_buffer: None,
397            use_double_buffer: false,
398            fb_size: (pitch as usize) * (height as usize),
399            console_defer_present: false,
400            zero_copy_backing: false,
401        });
402        publish_info(info);
403
404        log::info!(
405            "[FB] AMDGPU framebuffer: {}x{} @ {}bpp, {} bytes",
406            info.width,
407            info.height,
408            info.format.bits_per_pixel,
409            info.stride
410        );
411
412        Ok(())
413    }
414
415    /// Get framebuffer info (lockless: reads the init-time snapshot)
416    pub fn info() -> Option<FramebufferInfo> {
417        FB_INFO.get().copied()
418    }
419
420    /// Get framebuffer width (lockless)
421    pub fn width() -> u32 {
422        FB_INFO.get().map(|i| i.width).unwrap_or(0)
423    }
424
425    /// Get framebuffer height (lockless)
426    pub fn height() -> u32 {
427        FB_INFO.get().map(|i| i.height).unwrap_or(0)
428    }
429
430    /// Get stride in bytes per row (lockless)
431    pub fn stride() -> u32 {
432        FB_INFO.get().map(|i| i.stride).unwrap_or(0)
433    }
434
435    /// Check if framebuffer is initialized
436    pub fn is_available() -> bool {
437        FRAMEBUFFER_INITIALIZED.load(Ordering::Relaxed)
438    }
439
440    /// Get framebuffer source (lockless)
441    pub fn source() -> FramebufferSource {
442        FB_INFO
443            .get()
444            .map(|i| i.source)
445            .unwrap_or(FramebufferSource::None)
446    }
447
448    pub fn render_stats() -> Option<FramebufferRenderStats> {
449        FRAMEBUFFER
450            .lock()
451            .as_ref()
452            .map(|fb| FramebufferRenderStats {
453                present_pending: fb.canvas.present_pending,
454                dirty_region_count: fb.canvas.dirty.len,
455                last_present_tick: fb.canvas.last_present_tick,
456            })
457    }
458
459    /// Set a pixel at (x, y) with RGB color.
460    ///
461    /// Delegates to [`CanvasBuffer::write_pixel`] (S6: single implementation
462    /// of target selection). Takes the framebuffer lock for a single pixel:
463    /// fine for sparse dots, but batched drawing MUST go through
464    /// [`Self::blit_rect`] instead.
465    pub fn set_pixel(x: u32, y: u32, r: u8, g: u8, b: u8) {
466        let mut guard = FRAMEBUFFER.lock();
467        let fb = match guard.as_mut() {
468            Some(f) => f,
469            None => return,
470        };
471
472        if x >= fb.info.width || y >= fb.info.height {
473            return;
474        }
475
476        let pixel = ((r as u32) << fb.info.format.red_shift)
477            | ((g as u32) << fb.info.format.green_shift)
478            | ((b as u32) << fb.info.format.blue_shift);
479
480        fb.canvas.write_pixel(x as usize, y as usize, pixel);
481        fb.canvas.dirty.include(x, y, 1, 1);
482        fb.request_present();
483    }
484
485    /// Fill rectangle with color.
486    ///
487    /// Delegates to [`CanvasBuffer::fill_rect`] (S6: one implementation of
488    /// clipping, SIMD dispatch, target selection and dirty marking). Takes
489    /// the framebuffer lock once for the whole rectangle. Does NOT present;
490    /// call `swap_buffers()` after a batch of draw operations.
491    pub fn fill_rect(x: u32, y: u32, width: u32, height: u32, r: u8, g: u8, b: u8) {
492        if width == 0 || height == 0 {
493            return;
494        }
495
496        let mut guard = FRAMEBUFFER.lock();
497        let fb = match guard.as_mut() {
498            Some(f) => f,
499            None => return,
500        };
501
502        if x >= fb.info.width || y >= fb.info.height {
503            return;
504        }
505
506        let width = width.min(fb.info.width - x);
507        let height = height.min(fb.info.height - y);
508        if width == 0 || height == 0 {
509            return;
510        }
511
512        let pixel = ((r as u32) << fb.info.format.red_shift)
513            | ((g as u32) << fb.info.format.green_shift)
514            | ((b as u32) << fb.info.format.blue_shift);
515
516        fb.canvas.fill_rect(
517            x as usize,
518            y as usize,
519            width as usize,
520            height as usize,
521            pixel,
522        );
523        fb.request_present();
524    }
525
526    /// Blit a packed 32bpp XRGB rectangle into the current draw target
527    /// (double buffer when enabled, else the hardware framebuffer).
528    ///
529    /// This is the batch primitive: the framebuffer lock is taken exactly
530    /// once and a single dirty rectangle is marked, whatever the size.
531    /// `src` holds `h` rows of `w` pixels with a tight row stride of `w*4`
532    /// bytes; the rect is clipped against screen bounds.
533    pub fn blit_rect(src: &[u8], x: u32, y: u32, w: u32, h: u32) {
534        if w == 0 || h == 0 || x >= Self::width() || y >= Self::height() {
535            return;
536        }
537        let needed = w as usize * h as usize * 4;
538        if src.len() < needed {
539            return;
540        }
541
542        let mut guard = FRAMEBUFFER.lock();
543        let fb = match guard.as_mut() {
544            Some(f) => f,
545            None => return,
546        };
547
548        let max_w = fb.info.width - x;
549        let max_h = fb.info.height - y;
550        let w = w.min(max_w);
551        let h = h.min(max_h);
552        if w == 0 || h == 0 {
553            return;
554        }
555
556        let offset = if fb.use_double_buffer {
557            fb.double_buffer.unwrap_or(fb.info.base_virt as *mut u8)
558        } else {
559            fb.info.base_virt as *mut u8
560        };
561
562        let stride = fb.info.stride as usize;
563        let stride_pixels = stride / 4;
564        let src_row_bytes = w as usize * 4;
565        let full_width_contiguous =
566            x == 0 && w as usize == fb.info.width as usize && stride_pixels == w as usize;
567
568        unsafe {
569            if full_width_contiguous {
570                let dst = offset.add(y as usize * stride) as *mut u32;
571                let s = src.as_ptr() as *const u32;
572                (fb.canvas.ops.blit)(dst, s, w as usize * h as usize);
573            } else {
574                for dy in 0..h as usize {
575                    let dst = offset.add((y as usize + dy) * stride + x as usize * 4) as *mut u32;
576                    let s = src.as_ptr().add(dy * src_row_bytes) as *const u32;
577                    (fb.canvas.ops.blit)(dst, s, w as usize);
578                }
579            }
580        }
581
582        fb.canvas.dirty.include(x, y, w, h);
583        fb.request_present();
584    }
585
586    /// Draw a horizontal line
587    pub fn draw_hline(x: u32, y: u32, length: u32, r: u8, g: u8, b: u8) {
588        Self::fill_rect(x, y, length, 1, r, g, b);
589    }
590
591    /// Draw a vertical line
592    pub fn draw_vline(x: u32, y: u32, length: u32, r: u8, g: u8, b: u8) {
593        Self::fill_rect(x, y, 1, length, r, g, b);
594    }
595
596    /// Clear screen to black
597    pub fn clear() {
598        let info = Self::info();
599        if let Some(info) = info {
600            Self::fill_rect(0, 0, info.width, info.height, 0, 0, 0);
601        }
602    }
603
604    /// Swap buffers (for double buffering)
605    pub fn swap_buffers() {
606        enum VirtioPresent {
607            ZeroCopy {
608                x: u32,
609                y: u32,
610                w: u32,
611                h: u32,
612            },
613            BounceCopy {
614                src: *const u8,
615                stride: u32,
616                regions: [(u32, u32, u32, u32); MAX_DIRTY_RECTS],
617                count: usize,
618            },
619        }
620
621        let mut virtio_present = None;
622        {
623            let mut guard = FRAMEBUFFER.lock();
624            let fb = match guard.as_mut() {
625                Some(f) => f,
626                None => return,
627            };
628
629            if !fb.use_double_buffer || fb.double_buffer.is_none() {
630                return;
631            }
632
633            let db = fb.double_buffer.unwrap();
634            if fb.canvas.dirty.len == 0 {
635                return;
636            }
637
638            if fb.info.source == FramebufferSource::VirtioGpu {
639                // G3: coalesce dirty rects into their bounding box, then
640                // present. With an attached external backing this is a
641                // single TRANSFER+FLUSH pair straight from our draw target
642                // (zero copy); otherwise fall back to the bounce-copy path
643                // per rect.
644                let mut bx0 = u32::MAX;
645                let mut by0 = u32::MAX;
646                let mut bx1 = 0u32;
647                let mut by1 = 0u32;
648                let mut idx = 0;
649                while idx < fb.canvas.dirty.len {
650                    let rect = fb.canvas.dirty.rects[idx];
651                    if rect.is_valid() {
652                        bx0 = bx0.min(rect.x0);
653                        by0 = by0.min(rect.y0);
654                        bx1 = bx1.max(rect.x1);
655                        by1 = by1.max(rect.y1);
656                    }
657                    idx += 1;
658                }
659
660                virtio_present = if fb.zero_copy_backing && bx1 > bx0 && by1 > by0 {
661                    Some(VirtioPresent::ZeroCopy {
662                        x: bx0,
663                        y: by0,
664                        w: bx1 - bx0,
665                        h: by1 - by0,
666                    })
667                } else {
668                    let mut regions = [(0u32, 0u32, 0u32, 0u32); MAX_DIRTY_RECTS];
669                    let mut idx = 0;
670                    while idx < fb.canvas.dirty.len {
671                        let rect = fb.canvas.dirty.rects[idx];
672                        regions[idx] = (
673                            rect.x0,
674                            rect.y0,
675                            rect.x1.saturating_sub(rect.x0),
676                            rect.y1.saturating_sub(rect.y0),
677                        );
678                        idx += 1;
679                    }
680                    Some(VirtioPresent::BounceCopy {
681                        src: db as *const u8,
682                        stride: fb.info.stride,
683                        regions,
684                        count: fb.canvas.dirty.len,
685                    })
686                };
687            } else {
688                let dst = fb.info.base_virt as *mut u32;
689                let src_base = db as *const u32;
690                let stride_pixels = (fb.info.stride / 4) as usize;
691                let mut idx = 0;
692                while idx < fb.canvas.dirty.len {
693                    let rect = fb.canvas.dirty.rects[idx];
694                    let x = rect.x0 as usize;
695                    let y = rect.y0 as usize;
696                    let width = rect.x1.saturating_sub(rect.x0) as usize;
697                    let height = rect.y1.saturating_sub(rect.y0) as usize;
698                    // Blit groupé : si stride == width, tout est contigu, un seul appel
699                    if stride_pixels == width {
700                        let off = y * stride_pixels + x;
701                        unsafe {
702                            (fb.canvas.ops.blit)(dst.add(off), src_base.add(off), width * height);
703                        }
704                    } else {
705                        for row in 0..height {
706                            let row_off = (y + row) * stride_pixels + x;
707                            unsafe {
708                                (fb.canvas.ops.blit)(
709                                    dst.add(row_off),
710                                    src_base.add(row_off),
711                                    width,
712                                );
713                            }
714                        }
715                    }
716                    idx += 1;
717                }
718            }
719            fb.canvas.dirty.clear();
720            fb.canvas.present_pending = false;
721            fb.canvas.last_present_tick = crate::process::scheduler::ticks();
722        }
723
724        match virtio_present {
725            Some(VirtioPresent::ZeroCopy { x, y, w, h }) => {
726                if let Some(gpu) = gpu::get_gpu() {
727                    let _ = gpu.present_scanout_rect(x, y, w, h);
728                }
729            }
730            Some(VirtioPresent::BounceCopy {
731                src,
732                stride,
733                regions,
734                count: region_count,
735            }) => {
736                if let Some(gpu) = gpu::get_gpu() {
737                    let mut idx = 0;
738                    while idx < region_count {
739                        let (px, py, pw, ph) = regions[idx];
740                        let _ = unsafe { gpu.present_from_linear(src, stride, px, py, pw, ph) };
741                        idx += 1;
742                    }
743                }
744            }
745            None => {}
746        }
747    }
748
749    /// Enable/disable double buffering
750    pub fn set_double_buffering(enable: bool) {
751        let mut fb = FRAMEBUFFER.lock();
752        if let Some(ref mut f) = fb.as_mut() {
753            f.use_double_buffer = enable && f.double_buffer.is_some();
754        }
755    }
756}
757
758pub use crate::framebuffer::RgbColor;
759
760/// Initialize framebuffer subsystem
761pub fn init() {
762    log::info!("[FB] Initializing framebuffer subsystem...");
763
764    // Try VirtIO GPU first (native driver)
765    if gpu::is_available() {
766        if let Err(e) = Framebuffer::init_virtio_gpu() {
767            log::warn!("[FB] VirtIO GPU init failed: {}", e);
768        } else {
769            log::info!("[FB] Using VirtIO GPU framebuffer");
770        }
771    }
772
773    // Try AMDGPU (real hardware)
774    if !Framebuffer::is_available() && crate::hardware::amdgpu::is_available() {
775        if let Err(e) = Framebuffer::init_amd_gpu() {
776            log::warn!("[FB] AMDGPU init failed: {}", e);
777        } else {
778            log::info!("[FB] Using AMDGPU framebuffer");
779        }
780    }
781
782    // VirtIO GPU and AMDGPU not available, UEFI bootloader framebuffer is already set up in boot
783    if Framebuffer::is_available() {
784        log::info!("[FB] Using UEFI bootloader framebuffer");
785    } else {
786        log::warn!("[FB] No framebuffer available");
787    }
788
789    // Register /dev/display/ scheme
790    if Framebuffer::is_available() {
791        if let Err(e) = super::display_scheme::register_display_scheme() {
792            log::warn!("[FB] Failed to register display scheme: {:?}", e);
793        }
794    }
795}