asterinas/kernel/comps/framebuffer/src/framebuffer.rs

162 lines
4.6 KiB
Rust

// 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<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 = {
// 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
}
}