// SPDX-License-Identifier: MPL-2.0 use alloc::sync::Arc; use ostd::{ boot::boot_info, io::IoMem, mm::{HasSize, VmIo}, Result, }; use spin::Once; use crate::{Pixel, PixelFormat, RenderedPixel}; /// The framebuffer used for text or graphical output. /// /// # Notes /// /// It is highly recommended to use a synchronization primitive, such as a `SpinLock`, to /// lock the framebuffer before performing any operation on it. /// Failing to properly synchronize access can result in corrupted framebuffer content /// or unspecified behavior during rendering. #[derive(Debug)] pub struct FrameBuffer { io_mem: IoMem, width: usize, height: usize, line_size: usize, pixel_format: PixelFormat, } pub static FRAMEBUFFER: Once> = Once::new(); pub(crate) fn init() { let Some(framebuffer_arg) = boot_info().framebuffer_arg else { log::warn!("Framebuffer not found"); return; }; if framebuffer_arg.address == 0 { log::error!("Framebuffer address is zero"); return; } // FIXME: There are several pixel formats that have the same BPP. We lost the information // during the boot phase, so here we guess the pixel format on a best effort basis. let pixel_format = match framebuffer_arg.bpp { 8 => PixelFormat::Grayscale8, 16 => PixelFormat::Rgb565, 24 => PixelFormat::Rgb888, 32 => PixelFormat::BgrReserved, _ => { log::error!( "Unsupported framebuffer pixel format: {} bpp", framebuffer_arg.bpp ); return; } }; let framebuffer = { // FIXME: There can be more than `width` pixels per framebuffer line due to alignment // purposes. We need to collect this information during the boot phase. let line_size = framebuffer_arg .width .checked_mul(pixel_format.nbytes()) .unwrap(); let fb_size = framebuffer_arg.height.checked_mul(line_size).unwrap(); let fb_base = framebuffer_arg.address; let io_mem = IoMem::acquire(fb_base..fb_base.checked_add(fb_size).unwrap()).unwrap(); FrameBuffer { io_mem, width: framebuffer_arg.width, height: framebuffer_arg.height, line_size, pixel_format, } }; framebuffer.clear(); FRAMEBUFFER.call_once(|| Arc::new(framebuffer)); } impl FrameBuffer { /// Returns the width of the framebuffer in pixels. pub fn width(&self) -> usize { self.width } /// Returns the height of the framebuffer in pixels. pub fn height(&self) -> usize { self.height } /// Returns a reference to the `IoMem` instance of the framebuffer. pub fn io_mem(&self) -> &IoMem { &self.io_mem } /// Returns the pixel format of the framebuffer. pub fn pixel_format(&self) -> PixelFormat { self.pixel_format } /// Renders the pixel according to the pixel format of the framebuffer. pub fn render_pixel(&self, pixel: Pixel) -> RenderedPixel { pixel.render(self.pixel_format) } /// Calculates the offset of a pixel at the specified position. pub fn calc_offset(&self, x: usize, y: usize) -> PixelOffset { PixelOffset { fb: self, offset: (x * self.pixel_format.nbytes() + y * self.line_size) as isize, } } /// Writes a pixel at the specified position. pub fn write_pixel_at(&self, offset: PixelOffset, pixel: RenderedPixel) -> Result<()> { self.io_mem.write_bytes(offset.as_usize(), pixel.as_slice()) } /// Writes raw bytes at the specified offset. pub fn write_bytes_at(&self, offset: usize, bytes: &[u8]) -> Result<()> { self.io_mem.write_bytes(offset, bytes) } /// Clears the framebuffer with default color (black). pub fn clear(&self) { let frame = alloc::vec![0u8; self.io_mem().size()]; self.write_bytes_at(0, &frame).unwrap(); } } /// The offset of a pixel in the framebuffer. #[derive(Debug, Clone, Copy)] pub struct PixelOffset<'a> { fb: &'a FrameBuffer, offset: isize, } impl PixelOffset<'_> { /// Adds the specified delta to the x coordinate. pub fn x_add(&mut self, x_delta: isize) { let delta = x_delta * self.fb.pixel_format.nbytes() as isize; self.offset += delta; } /// Adds the specified delta to the y coordinate. pub fn y_add(&mut self, y_delta: isize) { let delta = y_delta * self.fb.line_size as isize; self.offset += delta; } /// Returns the offset value as a `usize`. pub fn as_usize(&self) -> usize { self.offset as usize } }