// SPDX-License-Identifier: MPL-2.0 use alloc::sync::Arc; use ostd::{boot::boot_info, io::IoMem, mm::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, 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 = { let fb_base = framebuffer_arg.address; let fb_size = framebuffer_arg.width * framebuffer_arg.height * (framebuffer_arg.bpp / u8::BITS as usize); let io_mem = IoMem::acquire(fb_base..fb_base + fb_size).unwrap(); FrameBuffer { io_mem, width: framebuffer_arg.width, height: framebuffer_arg.height, pixel_format, } }; framebuffer.clear(); FRAMEBUFFER.call_once(|| Arc::new(framebuffer)); } impl FrameBuffer { /// Returns the size of the framebuffer in bytes. pub fn size(&self) -> usize { self.io_mem.length() } /// 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 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: ((y * self.width + x) * self.pixel_format.nbytes()) 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.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.width * self.fb.pixel_format.nbytes()) as isize; self.offset += delta; } pub fn as_usize(&self) -> usize { self.offset as _ } }