Files
asterinas/kernel/comps/framebuffer/src/framebuffer.rs
2025-04-29 19:48:45 +08:00

148 lines
4.3 KiB
Rust

// 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<Arc<FrameBuffer>> = 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 _
}
}