Skip to main content

strat9_kernel/hardware/video/
framebuffer.rs

1// Note : _mm512_stream_si512 (NT store) is not available for x86_64-unknown-none;
2// Framebuffer abstraction layer
3//
4// Provides a unified framebuffer interface that can use:
5// - Limine framebuffer (bootloader-provided)
6// - VirtIO GPU framebuffer (native driver)
7// - Future/TODO : other GPU drivers (Bochs DRM, etc.)
8//
9// Features:
10// - Resolution switching
11// - Double buffering
12// - Basic 2D drawing primitives
13// - Text rendering support
14
15#![allow(dead_code)]
16
17use crate::{
18    framebuffer::{CanvasBuffer, DirtyRect, DirtyRectSet, FramebufferOps, MAX_DIRTY_RECTS},
19    hardware::virtio::gpu,
20    memory::{self, phys_to_virt},
21};
22use core::sync::atomic::{AtomicBool, Ordering};
23use spin::Mutex;
24
25/// Maximum supported resolution
26const MAX_WIDTH: u32 = 3840;
27const MAX_HEIGHT: u32 = 2160;
28const PRESENT_MIN_TICKS: u64 = 1;
29
30/// Framebuffer source
31#[derive(Clone, Copy, PartialEq, Eq, Debug)]
32pub enum FramebufferSource {
33    Limine,
34    VirtioGpu,
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}
97
98unsafe impl Send for Framebuffer {}
99unsafe impl Sync for Framebuffer {}
100
101static FRAMEBUFFER: Mutex<Option<Framebuffer>> = Mutex::new(None);
102static FRAMEBUFFER_INITIALIZED: AtomicBool = AtomicBool::new(false);
103
104impl Framebuffer {
105    fn request_present(&mut self) {
106        self.canvas.present_pending = true;
107    }
108
109    fn present_if_due(&mut self, force: bool) {
110        if !self.canvas.present_pending {
111            return;
112        }
113        if self.use_double_buffer {
114            return;
115        }
116        let now = crate::process::scheduler::ticks();
117        if force || now.saturating_sub(self.canvas.last_present_tick) >= PRESENT_MIN_TICKS {
118            self.canvas.present_pending = false;
119            self.canvas.last_present_tick = now;
120        } else {
121            return;
122        }
123
124        if self.info.source != FramebufferSource::VirtioGpu {
125            self.canvas.dirty.clear();
126            return;
127        }
128
129        if let Some(gpu) = gpu::get_gpu() {
130            let mut idx = 0;
131            while idx < self.canvas.dirty.len {
132                let rect = self.canvas.dirty.rects[idx];
133                if rect.is_valid() {
134                    let _ = gpu.present_from_linear(
135                        self.info.base_virt as *const u8,
136                        self.info.stride,
137                        rect.x0,
138                        rect.y0,
139                        rect.x1.saturating_sub(rect.x0),
140                        rect.y1.saturating_sub(rect.y0),
141                    );
142                }
143                idx += 1;
144            }
145        }
146        self.canvas.dirty.clear();
147    }
148
149    /// Initialize framebuffer with Limine-provided buffer
150    pub fn init_limine(
151        addr: u64,
152        width: u32,
153        height: u32,
154        stride: u32,
155        format: PixelFormat,
156    ) -> Result<(), &'static str> {
157        if addr == 0 || width == 0 || height == 0 {
158            return Err("Invalid framebuffer parameters");
159        }
160
161        let base_virt = addr as usize;
162
163        let info = FramebufferInfo {
164            base: addr,
165            base_virt,
166            width,
167            height,
168            stride,
169            format,
170            source: FramebufferSource::Limine,
171        };
172
173        let canvas = CanvasBuffer {
174            addr: base_virt as *mut u8,
175            width: width as usize,
176            height: height as usize,
177            pitch: stride as usize,
178            bpp: format.bits_per_pixel as u16,
179            back_buffer: None,
180            draw_to_back: false,
181            dirty: DirtyRectSet::empty(),
182            track_dirty: false,
183            present_pending: false,
184            last_present_tick: 0,
185            ops: FramebufferOps::detect(),
186        };
187
188        let fb = Framebuffer {
189            info,
190            canvas,
191            double_buffer: None,
192            use_double_buffer: false,
193        };
194
195        *FRAMEBUFFER.lock() = Some(fb);
196        FRAMEBUFFER_INITIALIZED.store(true, Ordering::SeqCst);
197
198        log::info!(
199            "[FB] Limine framebuffer: {}x{} @ {}bpp, stride={}",
200            width,
201            height,
202            format.bits_per_pixel,
203            stride
204        );
205
206        Ok(())
207    }
208
209    /// Initialize framebuffer with VirtIO GPU
210    pub fn init_virtio_gpu() -> Result<(), &'static str> {
211        let gpu_info = gpu::get_framebuffer_info().ok_or("VirtIO GPU not initialized")?;
212
213        let format = PixelFormat {
214            red_mask: 0x00FF0000,
215            red_shift: 16,
216            green_mask: 0x0000FF00,
217            green_shift: 8,
218            blue_mask: 0x000000FF,
219            blue_shift: 0,
220            bits_per_pixel: 32,
221        };
222
223        let info = FramebufferInfo {
224            base: gpu_info.framebuffer_phys,
225            base_virt: gpu_info.framebuffer_virt as usize,
226            width: gpu_info.width,
227            height: gpu_info.height,
228            stride: gpu_info.stride,
229            format,
230            source: FramebufferSource::VirtioGpu,
231        };
232
233        // Allocate double buffer for VirtIO GPU
234        let db_size = (info.stride as usize) * (info.height as usize);
235        if db_size == 0 {
236            return Err("Invalid VirtIO framebuffer size");
237        }
238        let db_pages = (db_size + 4095) / 4096;
239        let db_order = db_pages.next_power_of_two().trailing_zeros() as u8;
240        let db_frame = crate::sync::with_irqs_disabled(|token| {
241            memory::allocate_phys_contiguous(token, db_order)
242        })
243        .map_err(|_| "Failed to allocate double buffer")?;
244        let db_virt = phys_to_virt(db_frame.start_address.as_u64()) as *mut u8;
245        unsafe {
246            // SAFETY: `db_virt` is a freshly allocated contiguous buffer of at
247            // least `db_size` bytes.
248            core::ptr::write_bytes(db_virt, 0, db_size);
249        }
250
251        let canvas = CanvasBuffer {
252            addr: info.base_virt as *mut u8,
253            width: info.width as usize,
254            height: info.height as usize,
255            pitch: info.stride as usize,
256            bpp: 32,
257            back_buffer: None,
258            draw_to_back: false,
259            dirty: DirtyRectSet::empty(),
260            track_dirty: false,
261            present_pending: false,
262            last_present_tick: 0,
263            ops: FramebufferOps::detect(),
264        };
265
266        let fb = Framebuffer {
267            info,
268            canvas,
269            double_buffer: Some(db_virt),
270            use_double_buffer: true,
271        };
272
273        *FRAMEBUFFER.lock() = Some(fb);
274        FRAMEBUFFER_INITIALIZED.store(true, Ordering::SeqCst);
275
276        log::info!(
277            "[FB] VirtIO GPU framebuffer: {}x{} @ {}bpp, stride={}",
278            info.width,
279            info.height,
280            info.format.bits_per_pixel,
281            info.stride
282        );
283
284        Ok(())
285    }
286
287    /// Get framebuffer info
288    pub fn info() -> Option<FramebufferInfo> {
289        FRAMEBUFFER.lock().as_ref().map(|fb| fb.info)
290    }
291
292    /// Get framebuffer width
293    pub fn width() -> u32 {
294        FRAMEBUFFER
295            .lock()
296            .as_ref()
297            .map(|fb| fb.info.width)
298            .unwrap_or(0)
299    }
300
301    /// Get framebuffer height
302    pub fn height() -> u32 {
303        FRAMEBUFFER
304            .lock()
305            .as_ref()
306            .map(|fb| fb.info.height)
307            .unwrap_or(0)
308    }
309
310    /// Get stride (bytes per row)
311    pub fn stride() -> u32 {
312        FRAMEBUFFER
313            .lock()
314            .as_ref()
315            .map(|fb| fb.info.stride)
316            .unwrap_or(0)
317    }
318
319    /// Check if framebuffer is initialized
320    pub fn is_available() -> bool {
321        FRAMEBUFFER_INITIALIZED.load(Ordering::Relaxed)
322    }
323
324    /// Get framebuffer source
325    pub fn source() -> FramebufferSource {
326        FRAMEBUFFER
327            .lock()
328            .as_ref()
329            .map(|fb| fb.info.source)
330            .unwrap_or(FramebufferSource::None)
331    }
332
333    pub fn render_stats() -> Option<FramebufferRenderStats> {
334        FRAMEBUFFER
335            .lock()
336            .as_ref()
337            .map(|fb| FramebufferRenderStats {
338                present_pending: fb.canvas.present_pending,
339                dirty_region_count: fb.canvas.dirty.len,
340                last_present_tick: fb.canvas.last_present_tick,
341            })
342    }
343
344    /// Set a pixel at (x, y) with RGB color.
345    ///
346    /// Does NOT present to screen. Call `swap_buffers()` or `present()`
347    /// after a batch of draw operations.
348    pub fn set_pixel(x: u32, y: u32, r: u8, g: u8, b: u8) {
349        {
350            let mut guard = FRAMEBUFFER.lock();
351            let fb = match guard.as_mut() {
352                Some(f) => f,
353                None => return,
354            };
355
356            if x >= fb.info.width || y >= fb.info.height {
357                return;
358            }
359
360            let pixel = ((r as u32) << fb.info.format.red_shift)
361                | ((g as u32) << fb.info.format.green_shift)
362                | ((b as u32) << fb.info.format.blue_shift);
363
364            let offset = if fb.use_double_buffer {
365                fb.double_buffer.unwrap_or(fb.info.base_virt as *mut u8)
366            } else {
367                fb.info.base_virt as *mut u8
368            };
369
370            unsafe {
371                let pixel_ptr = offset.add((y * fb.info.stride + x * 4) as usize);
372                core::ptr::write(pixel_ptr as *mut u32, pixel);
373            }
374
375            fb.canvas.dirty.include(x, y, 1, 1);
376            fb.request_present();
377        }
378    }
379
380    /// Fill rectangle with color.
381    ///
382    /// Does NOT present to screen. Call `swap_buffers()` or `present()`
383    /// after a batch of draw operations.
384    pub fn fill_rect(x: u32, y: u32, width: u32, height: u32, r: u8, g: u8, b: u8) {
385        if width == 0 || height == 0 {
386            return;
387        }
388
389        {
390            let mut guard = FRAMEBUFFER.lock();
391            let fb = match guard.as_mut() {
392                Some(f) => f,
393                None => return,
394            };
395
396            if x >= fb.info.width || y >= fb.info.height {
397                return;
398            }
399
400            let max_w = fb.info.width - x;
401            let max_h = fb.info.height - y;
402            let width = width.min(max_w);
403            let height = height.min(max_h);
404            if width == 0 || height == 0 {
405                return;
406            }
407
408            let pixel = ((r as u32) << fb.info.format.red_shift)
409                | ((g as u32) << fb.info.format.green_shift)
410                | ((b as u32) << fb.info.format.blue_shift);
411
412            let offset = if fb.use_double_buffer {
413                fb.double_buffer.unwrap_or(fb.info.base_virt as *mut u8)
414            } else {
415                fb.info.base_virt as *mut u8
416            };
417
418            let stride = fb.info.stride as usize;
419            let stride_pixels = fb.info.stride as usize / 4;
420            let width_pixels = width as usize;
421            if stride_pixels == width_pixels {
422                let first = unsafe { offset.add(y as usize * stride + x as usize * 4) as *mut u32 };
423                unsafe {
424                    (fb.canvas.ops.fill)(first, pixel, width_pixels * height as usize);
425                }
426            } else {
427                for dy in 0..height as usize {
428                    let row_ptr = unsafe {
429                        offset.add((y as usize + dy) * stride + x as usize * 4) as *mut u32
430                    };
431                    unsafe {
432                        (fb.canvas.ops.fill)(row_ptr, pixel, width_pixels);
433                    }
434                }
435            }
436
437            fb.canvas.dirty.include(x, y, width, height);
438            fb.request_present();
439        }
440    }
441
442    /// Draw a horizontal line
443    pub fn draw_hline(x: u32, y: u32, length: u32, r: u8, g: u8, b: u8) {
444        Self::fill_rect(x, y, length, 1, r, g, b);
445    }
446
447    /// Draw a vertical line
448    pub fn draw_vline(x: u32, y: u32, length: u32, r: u8, g: u8, b: u8) {
449        Self::fill_rect(x, y, 1, length, r, g, b);
450    }
451
452    /// Clear screen to black
453    pub fn clear() {
454        let info = Self::info();
455        if let Some(info) = info {
456            Self::fill_rect(0, 0, info.width, info.height, 0, 0, 0);
457        }
458    }
459
460    /// Swap buffers (for double buffering)
461    pub fn swap_buffers() {
462        let mut virtio_present = None;
463        {
464            let mut guard = FRAMEBUFFER.lock();
465            let fb = match guard.as_mut() {
466                Some(f) => f,
467                None => return,
468            };
469
470            if !fb.use_double_buffer || fb.double_buffer.is_none() {
471                return;
472            }
473
474            let db = fb.double_buffer.unwrap();
475            if fb.canvas.dirty.len == 0 {
476                return;
477            }
478
479            if fb.info.source == FramebufferSource::VirtioGpu {
480                let mut regions = [(0u32, 0u32, 0u32, 0u32); MAX_DIRTY_RECTS];
481                let mut idx = 0;
482                while idx < fb.canvas.dirty.len {
483                    let rect = fb.canvas.dirty.rects[idx];
484                    regions[idx] = (
485                        rect.x0,
486                        rect.y0,
487                        rect.x1.saturating_sub(rect.x0),
488                        rect.y1.saturating_sub(rect.y0),
489                    );
490                    idx += 1;
491                }
492                virtio_present = Some((
493                    db as *const u8,
494                    fb.info.stride,
495                    regions,
496                    fb.canvas.dirty.len,
497                ));
498            } else {
499                let dst = fb.info.base_virt as *mut u32;
500                let src_base = db as *const u32;
501                let stride_pixels = (fb.info.stride / 4) as usize;
502                let mut idx = 0;
503                while idx < fb.canvas.dirty.len {
504                    let rect = fb.canvas.dirty.rects[idx];
505                    let x = rect.x0 as usize;
506                    let y = rect.y0 as usize;
507                    let width = rect.x1.saturating_sub(rect.x0) as usize;
508                    let height = rect.y1.saturating_sub(rect.y0) as usize;
509                    // Blit groupé : si stride == width, tout est contigu, un seul appel
510                    if stride_pixels == width {
511                        let off = y * stride_pixels + x;
512                        unsafe {
513                            (fb.canvas.ops.blit)(dst.add(off), src_base.add(off), width * height);
514                        }
515                    } else {
516                        for row in 0..height {
517                            let row_off = (y + row) * stride_pixels + x;
518                            unsafe {
519                                (fb.canvas.ops.blit)(
520                                    dst.add(row_off),
521                                    src_base.add(row_off),
522                                    width,
523                                );
524                            }
525                        }
526                    }
527                    idx += 1;
528                }
529            }
530            fb.canvas.dirty.clear();
531            fb.canvas.present_pending = false;
532            fb.canvas.last_present_tick = crate::process::scheduler::ticks();
533        }
534
535        if let Some((src, src_stride, regions, region_count)) = virtio_present {
536            if let Some(gpu) = gpu::get_gpu() {
537                let mut idx = 0;
538                while idx < region_count {
539                    let (px, py, pw, ph) = regions[idx];
540                    let _ = gpu.present_from_linear(src, src_stride, px, py, pw, ph);
541                    idx += 1;
542                }
543            }
544        }
545    }
546
547    /// Enable/disable double buffering
548    pub fn set_double_buffering(enable: bool) {
549        let mut fb = FRAMEBUFFER.lock();
550        if let Some(ref mut f) = fb.as_mut() {
551            f.use_double_buffer = enable && f.double_buffer.is_some();
552        }
553    }
554}
555
556pub use crate::framebuffer::RgbColor;
557
558/// Initialize framebuffer subsystem
559pub fn init() {
560    log::info!("[FB] Initializing framebuffer subsystem...");
561
562    // Try VirtIO GPU first (native driver)
563    if gpu::is_available() {
564        if let Err(e) = Framebuffer::init_virtio_gpu() {
565            log::warn!("[FB] VirtIO GPU init failed: {}", e);
566        } else {
567            log::info!("[FB] Using VirtIO GPU framebuffer");
568        }
569    }
570
571    // VirtIO GPU not available, Limine framebuffer is already set up in boot
572    // The Limine framebuffer is initialized in boot/limine.rs
573    if Framebuffer::is_available() {
574        log::info!("[FB] Using Limine framebuffer");
575    } else {
576        log::warn!("[FB] No framebuffer available");
577    }
578
579    // Register /dev/display/ scheme
580    if Framebuffer::is_available() {
581        if let Err(e) = super::display_scheme::register_display_scheme() {
582            log::warn!("[FB] Failed to register display scheme: {:?}", e);
583        }
584    }
585}