mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-08 12:56:48 +00:00
Refactor framebuffer
This commit is contained in:
parent
ef898e572c
commit
88f08de3af
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -116,6 +116,7 @@ dependencies = [
|
||||
name = "aster-framebuffer"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"aster-console",
|
||||
"component",
|
||||
"font8x8",
|
||||
"log",
|
||||
@ -184,6 +185,7 @@ dependencies = [
|
||||
"aster-bigtcp",
|
||||
"aster-block",
|
||||
"aster-console",
|
||||
"aster-framebuffer",
|
||||
"aster-input",
|
||||
"aster-logger",
|
||||
"aster-mlsdisk",
|
||||
|
@ -11,6 +11,7 @@ aster-input = { path = "comps/input" }
|
||||
aster-block = { path = "comps/block" }
|
||||
aster-network = { path = "comps/network" }
|
||||
aster-console = { path = "comps/console" }
|
||||
aster-framebuffer = { path = "comps/framebuffer" }
|
||||
aster-softirq = { path = "comps/softirq" }
|
||||
aster-logger = { path = "comps/logger" }
|
||||
aster-mlsdisk = { path = "comps/mlsdisk" }
|
||||
|
@ -8,11 +8,10 @@ edition = "2021"
|
||||
[dependencies]
|
||||
ostd = { path = "../../../ostd" }
|
||||
component = { path = "../../libs/comp-sys/component" }
|
||||
aster-console = { path = "../console" }
|
||||
log = "0.4"
|
||||
spin = "0.9.4"
|
||||
font8x8 = { version = "0.2.5", default-features = false, features = [
|
||||
"unicode",
|
||||
] }
|
||||
font8x8 = { version = "0.2.5", default-features = false, features = [ "unicode" ] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
212
kernel/comps/framebuffer/src/console.rs
Normal file
212
kernel/comps/framebuffer/src/console.rs
Normal file
@ -0,0 +1,212 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use alloc::{sync::Arc, vec::Vec};
|
||||
|
||||
use aster_console::{AnyConsoleDevice, ConsoleCallback};
|
||||
use font8x8::UnicodeFonts;
|
||||
use ostd::{
|
||||
sync::{LocalIrqDisabled, SpinLock},
|
||||
Error, Result,
|
||||
};
|
||||
use spin::Once;
|
||||
|
||||
use crate::{FrameBuffer, Pixel, FRAMEBUFFER};
|
||||
|
||||
/// The font width in pixels when using `font8x8`.
|
||||
const FONT_WIDTH: usize = 8;
|
||||
|
||||
/// The font height in pixels when using `font8x8`.
|
||||
const FONT_HEIGHT: usize = 8;
|
||||
|
||||
/// A text console rendered onto the framebuffer.
|
||||
#[derive(Debug)]
|
||||
pub struct FramebufferConsole {
|
||||
state: SpinLock<ConsoleState, LocalIrqDisabled>,
|
||||
}
|
||||
|
||||
pub static CONSOLE_NAME: &str = "Framebuffer-Console";
|
||||
|
||||
pub static FRAMEBUFFER_CONSOLE: Once<Arc<FramebufferConsole>> = Once::new();
|
||||
|
||||
pub(crate) fn init() {
|
||||
let Some(fb) = FRAMEBUFFER.get() else {
|
||||
log::warn!("Framebuffer not initialized");
|
||||
return;
|
||||
};
|
||||
|
||||
FRAMEBUFFER_CONSOLE.call_once(|| Arc::new(FramebufferConsole::new(fb.clone())));
|
||||
}
|
||||
|
||||
impl AnyConsoleDevice for FramebufferConsole {
|
||||
fn send(&self, buf: &[u8]) {
|
||||
self.state.lock().send_buf(buf);
|
||||
}
|
||||
|
||||
fn register_callback(&self, _: &'static ConsoleCallback) {
|
||||
// Unsupported, do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
impl FramebufferConsole {
|
||||
/// Creates a new framebuffer console.
|
||||
pub fn new(framebuffer: Arc<FrameBuffer>) -> Self {
|
||||
let bytes = alloc::vec![0u8; framebuffer.size()];
|
||||
Self {
|
||||
state: SpinLock::new(ConsoleState {
|
||||
enabled: true,
|
||||
x_pos: 0,
|
||||
y_pos: 0,
|
||||
fg_color: Pixel::WHITE,
|
||||
bg_color: Pixel::BLACK,
|
||||
bytes,
|
||||
backend: framebuffer,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the console is enabled.
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
self.state.lock().enabled
|
||||
}
|
||||
|
||||
/// Enables the console.
|
||||
pub fn enable(&self) {
|
||||
self.state.lock().enabled = true;
|
||||
}
|
||||
|
||||
/// Disables the console.
|
||||
pub fn disable(&self) {
|
||||
self.state.lock().enabled = false;
|
||||
}
|
||||
|
||||
/// Returns the current cursor position.
|
||||
pub fn cursor(&self) -> (usize, usize) {
|
||||
let state = self.state.lock();
|
||||
(state.x_pos, state.y_pos)
|
||||
}
|
||||
|
||||
/// Sets the cursor position.
|
||||
pub fn set_cursor(&self, x: usize, y: usize) -> Result<()> {
|
||||
let mut state = self.state.lock();
|
||||
if x > state.backend.width() - FONT_WIDTH || y > state.backend.height() - FONT_HEIGHT {
|
||||
log::warn!("Invalid framebuffer cursor position: ({}, {})", x, y);
|
||||
return Err(Error::InvalidArgs);
|
||||
}
|
||||
state.x_pos = x;
|
||||
state.y_pos = y;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the foreground color.
|
||||
pub fn fg_color(&self) -> Pixel {
|
||||
self.state.lock().fg_color
|
||||
}
|
||||
|
||||
/// Sets the foreground color.
|
||||
pub fn set_fg_color(&self, val: Pixel) {
|
||||
self.state.lock().fg_color = val;
|
||||
}
|
||||
|
||||
/// Returns the background color.
|
||||
pub fn bg_color(&self) -> Pixel {
|
||||
self.state.lock().bg_color
|
||||
}
|
||||
|
||||
/// Sets the background color.
|
||||
pub fn set_bg_color(&self, val: Pixel) {
|
||||
self.state.lock().bg_color = val;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ConsoleState {
|
||||
// FIXME: maybe we should drop the whole `ConsoleState` when it's disabled.
|
||||
enabled: bool,
|
||||
x_pos: usize,
|
||||
y_pos: usize,
|
||||
fg_color: Pixel,
|
||||
bg_color: Pixel,
|
||||
bytes: Vec<u8>,
|
||||
backend: Arc<FrameBuffer>,
|
||||
}
|
||||
|
||||
impl ConsoleState {
|
||||
fn carriage_return(&mut self) {
|
||||
self.x_pos = 0;
|
||||
}
|
||||
|
||||
fn newline(&mut self) {
|
||||
if self.y_pos >= self.backend.height() - FONT_HEIGHT {
|
||||
self.shift_lines_up();
|
||||
}
|
||||
self.y_pos += FONT_HEIGHT;
|
||||
self.x_pos = 0;
|
||||
}
|
||||
|
||||
fn shift_lines_up(&mut self) {
|
||||
let offset = self.backend.calc_offset(0, FONT_HEIGHT).as_usize();
|
||||
self.bytes.copy_within(offset.., 0);
|
||||
self.bytes[self.backend.size() - offset..].fill(0);
|
||||
self.backend.write_bytes_at(0, &self.bytes).unwrap();
|
||||
self.y_pos -= FONT_HEIGHT;
|
||||
}
|
||||
|
||||
/// Sends a single character to be drawn on the framebuffer.
|
||||
fn send_char(&mut self, c: char) {
|
||||
if c == '\n' {
|
||||
self.newline();
|
||||
return;
|
||||
} else if c == '\r' {
|
||||
self.carriage_return();
|
||||
return;
|
||||
}
|
||||
|
||||
if self.x_pos + FONT_WIDTH > self.backend.width() {
|
||||
self.newline();
|
||||
}
|
||||
|
||||
let rendered = font8x8::BASIC_FONTS
|
||||
.get(c)
|
||||
.expect("character not found in basic font");
|
||||
let fg_pixel = self.backend.render_pixel(self.fg_color);
|
||||
let bg_pixel = self.backend.render_pixel(self.bg_color);
|
||||
let mut offset = self.backend.calc_offset(self.x_pos, self.y_pos);
|
||||
for byte in rendered.iter() {
|
||||
for bit in 0..8 {
|
||||
let on = *byte & (1 << bit) != 0;
|
||||
let pixel = if on { fg_pixel } else { bg_pixel };
|
||||
|
||||
// Cache the rendered pixel
|
||||
self.bytes[offset.as_usize()..offset.as_usize() + pixel.nbytes()]
|
||||
.copy_from_slice(pixel.as_slice());
|
||||
// Write the pixel to the framebuffer
|
||||
self.backend.write_pixel_at(offset, pixel).unwrap();
|
||||
|
||||
offset.x_add(1);
|
||||
}
|
||||
offset.x_add(-(FONT_WIDTH as isize));
|
||||
offset.y_add(1);
|
||||
}
|
||||
self.x_pos += FONT_WIDTH;
|
||||
}
|
||||
|
||||
/// Sends a buffer of bytes to be drawn on the framebuffer.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This method will panic if the buffer contains any characters
|
||||
/// other than Basic Latin characters (`U+0000` - `U+007F`).
|
||||
fn send_buf(&mut self, buf: &[u8]) {
|
||||
if !self.enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: handle ANSI escape sequences.
|
||||
for &byte in buf.iter() {
|
||||
if byte != 0 {
|
||||
let char = char::from_u32(byte as u32).unwrap();
|
||||
self.send_char(char);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
147
kernel/comps/framebuffer/src/framebuffer.rs
Normal file
147
kernel/comps/framebuffer/src/framebuffer.rs
Normal file
@ -0,0 +1,147 @@
|
||||
// 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 _
|
||||
}
|
||||
}
|
@ -6,209 +6,18 @@
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::{vec, vec::Vec};
|
||||
use core::{
|
||||
fmt,
|
||||
ops::{Index, IndexMut},
|
||||
};
|
||||
mod console;
|
||||
mod framebuffer;
|
||||
mod pixel;
|
||||
|
||||
use component::{init_component, ComponentInitError};
|
||||
use font8x8::UnicodeFonts;
|
||||
use ostd::{
|
||||
boot::{boot_info, memory_region::MemoryRegionType},
|
||||
io::IoMem,
|
||||
mm::{VmIo, PAGE_SIZE},
|
||||
sync::SpinLock,
|
||||
};
|
||||
use spin::Once;
|
||||
pub use console::{FramebufferConsole, CONSOLE_NAME, FRAMEBUFFER_CONSOLE};
|
||||
pub use framebuffer::{FrameBuffer, FRAMEBUFFER};
|
||||
pub use pixel::{Pixel, PixelFormat, RenderedPixel};
|
||||
|
||||
#[init_component]
|
||||
fn framebuffer_init() -> Result<(), ComponentInitError> {
|
||||
init();
|
||||
fn init() -> Result<(), ComponentInitError> {
|
||||
framebuffer::init();
|
||||
console::init();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) static WRITER: Once<SpinLock<Writer>> = Once::new();
|
||||
|
||||
// ignore the warnings since we use the `todo!` macro.
|
||||
#[expect(unused_variables)]
|
||||
#[expect(unreachable_code)]
|
||||
#[expect(clippy::diverging_sub_expression)]
|
||||
pub(crate) fn init() {
|
||||
let mut writer = {
|
||||
let Some(framebuffer) = boot_info().framebuffer_arg else {
|
||||
return;
|
||||
};
|
||||
let mut size = 0;
|
||||
for region in boot_info().memory_regions.iter() {
|
||||
if region.typ() == MemoryRegionType::Framebuffer {
|
||||
size = region.len();
|
||||
}
|
||||
}
|
||||
|
||||
let page_size = size / PAGE_SIZE;
|
||||
|
||||
let start_paddr = framebuffer.address;
|
||||
let io_mem = todo!("IoMem is private for components now, should fix it.");
|
||||
|
||||
let mut buffer: Vec<u8> = vec![0; size];
|
||||
log::debug!("Found framebuffer:{:?}", framebuffer);
|
||||
|
||||
Writer {
|
||||
io_mem,
|
||||
x_pos: 0,
|
||||
y_pos: 0,
|
||||
bytes_per_pixel: (framebuffer.bpp / 8),
|
||||
width: framebuffer.width,
|
||||
height: framebuffer.height,
|
||||
buffer: buffer.leak(),
|
||||
}
|
||||
};
|
||||
writer.clear();
|
||||
|
||||
WRITER.call_once(|| SpinLock::new(writer));
|
||||
}
|
||||
|
||||
pub(crate) struct Writer {
|
||||
io_mem: IoMem,
|
||||
/// FIXME: remove buffer. The meaning of buffer is to facilitate the various operations of framebuffer
|
||||
buffer: &'static mut [u8],
|
||||
|
||||
bytes_per_pixel: usize,
|
||||
width: usize,
|
||||
height: usize,
|
||||
|
||||
x_pos: usize,
|
||||
y_pos: usize,
|
||||
}
|
||||
|
||||
impl Writer {
|
||||
fn newline(&mut self) {
|
||||
self.y_pos += 8;
|
||||
self.carriage_return();
|
||||
}
|
||||
|
||||
fn carriage_return(&mut self) {
|
||||
self.x_pos = 0;
|
||||
}
|
||||
|
||||
/// Erases all text on the screen
|
||||
pub fn clear(&mut self) {
|
||||
self.x_pos = 0;
|
||||
self.y_pos = 0;
|
||||
self.buffer.fill(0);
|
||||
self.io_mem.write_bytes(0, self.buffer).unwrap();
|
||||
}
|
||||
|
||||
/// Everything moves up one letter in size
|
||||
fn shift_lines_up(&mut self) {
|
||||
let offset = self.bytes_per_pixel * 8;
|
||||
self.buffer.copy_within(offset.., 0);
|
||||
self.io_mem.write_bytes(0, self.buffer).unwrap();
|
||||
self.y_pos -= 8;
|
||||
}
|
||||
|
||||
fn width(&self) -> usize {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> usize {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn write_char(&mut self, c: char) {
|
||||
match c {
|
||||
'\n' => self.newline(),
|
||||
'\r' => self.carriage_return(),
|
||||
c => {
|
||||
if self.x_pos >= self.width() {
|
||||
self.newline();
|
||||
}
|
||||
while self.y_pos >= (self.height() - 8) {
|
||||
self.shift_lines_up();
|
||||
}
|
||||
let rendered = font8x8::BASIC_FONTS
|
||||
.get(c)
|
||||
.expect("character not found in basic font");
|
||||
self.write_rendered_char(rendered);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_rendered_char(&mut self, rendered_char: [u8; 8]) {
|
||||
for (y, byte) in rendered_char.iter().enumerate() {
|
||||
for (x, bit) in (0..8).enumerate() {
|
||||
let on = *byte & (1 << bit) != 0;
|
||||
self.write_pixel(self.x_pos + x, self.y_pos + y, on);
|
||||
}
|
||||
}
|
||||
self.x_pos += 8;
|
||||
}
|
||||
|
||||
fn write_pixel(&mut self, x: usize, y: usize, on: bool) {
|
||||
let pixel_offset = y * self.width + x;
|
||||
let color = if on {
|
||||
[0x33, 0xff, 0x66, 0]
|
||||
} else {
|
||||
[0, 0, 0, 0]
|
||||
};
|
||||
let bytes_per_pixel = self.bytes_per_pixel;
|
||||
let byte_offset = pixel_offset * bytes_per_pixel;
|
||||
self.buffer
|
||||
.index_mut(byte_offset..(byte_offset + bytes_per_pixel))
|
||||
.copy_from_slice(&color[..bytes_per_pixel]);
|
||||
self.io_mem
|
||||
.write_bytes(
|
||||
byte_offset,
|
||||
self.buffer
|
||||
.index(byte_offset..(byte_offset + bytes_per_pixel)),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Writes the given ASCII string to the buffer.
|
||||
///
|
||||
/// Wraps lines at `BUFFER_WIDTH`. Supports the `\n` newline character. Does **not**
|
||||
/// support strings with non-ASCII characters, since they can't be printed in the VGA text
|
||||
/// mode.
|
||||
fn write_string(&mut self, s: &str) {
|
||||
for char in s.chars() {
|
||||
self.write_char(char);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Write for Writer {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
self.write_string(s);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Like the `print!` macro in the standard library, but prints to the VGA text buffer.
|
||||
#[macro_export]
|
||||
macro_rules! print {
|
||||
($($arg:tt)*) => ($crate::_print(format_args!($($arg)*)));
|
||||
}
|
||||
|
||||
/// Like the `println!` macro in the standard library, but prints to the VGA text buffer.
|
||||
#[macro_export]
|
||||
macro_rules! println {
|
||||
() => ($crate::print!("\n"));
|
||||
($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*)));
|
||||
}
|
||||
|
||||
/// Prints the given formatted string to the VGA text buffer
|
||||
/// through the global `WRITER` instance.
|
||||
#[doc(hidden)]
|
||||
pub fn _print(args: fmt::Arguments) {
|
||||
use core::fmt::Write;
|
||||
|
||||
WRITER
|
||||
.get()
|
||||
.unwrap()
|
||||
.disable_irq()
|
||||
.lock()
|
||||
.write_fmt(args)
|
||||
.unwrap();
|
||||
}
|
||||
|
104
kernel/comps/framebuffer/src/pixel.rs
Normal file
104
kernel/comps/framebuffer/src/pixel.rs
Normal file
@ -0,0 +1,104 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
/// Individual pixel data containing raw channel values.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct Pixel {
|
||||
pub red: u8,
|
||||
pub green: u8,
|
||||
pub blue: u8,
|
||||
}
|
||||
|
||||
/// Pixel format that defines the memory layout of each pixel in the framebuffer.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum PixelFormat {
|
||||
/// Each pixel uses 8 bits to represent its grayscale intensity, ranging from 0 (black) to 255 (white).
|
||||
Grayscale8,
|
||||
/// Each pixel uses 16 bits, with 5 bits for Red, 6 bits for Green, and 5 bits for Blue.
|
||||
Rgb565,
|
||||
/// Each pixel uses 24 bits, with 8 bits for Red, 8 bits for Green, and 8 bits for Blue.
|
||||
Rgb888,
|
||||
/// Each pixel uses 32 bits, with 8 bits for Blue, 8 bits for Green, 8 bits for Red, and 8 bits reserved.
|
||||
BgrReserved,
|
||||
}
|
||||
|
||||
/// A rendered pixel in a specific format.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct RenderedPixel {
|
||||
buf: [u8; 4],
|
||||
len: u8,
|
||||
}
|
||||
|
||||
impl Pixel {
|
||||
/// Renders the pixel into a specific format.
|
||||
pub fn render(&self, format: PixelFormat) -> RenderedPixel {
|
||||
let mut buf = [0; 4];
|
||||
match format {
|
||||
PixelFormat::Grayscale8 => {
|
||||
// Calculate the grayscale value
|
||||
let red_weight = 77 * self.red as u16; // Equivalent to 0.299 * 256
|
||||
let green_weight = 150 * self.green as u16; // Equivalent to 0.587 * 256
|
||||
let blue_weight = 29 * self.blue as u16; // Equivalent to 0.114 * 256
|
||||
let grayscale = (red_weight + green_weight + blue_weight) >> 8; // Normalize to 0-255
|
||||
buf[0] = grayscale as u8;
|
||||
RenderedPixel { buf, len: 1 }
|
||||
}
|
||||
PixelFormat::Rgb565 => {
|
||||
let r = (self.red >> 3) as u16; // Red (5 bits)
|
||||
let g = (self.green >> 2) as u16; // Green (6 bits)
|
||||
let b = (self.blue >> 3) as u16; // Blue (5 bits)
|
||||
let rgb565 = (r << 11) | (g << 5) | b; // Combine into RGB565 format
|
||||
buf[0..2].copy_from_slice(&rgb565.to_be_bytes());
|
||||
RenderedPixel { buf, len: 2 }
|
||||
}
|
||||
PixelFormat::Rgb888 => {
|
||||
buf[0] = self.red;
|
||||
buf[1] = self.green;
|
||||
buf[2] = self.blue;
|
||||
RenderedPixel { buf, len: 3 }
|
||||
}
|
||||
PixelFormat::BgrReserved => {
|
||||
buf[0] = self.blue;
|
||||
buf[1] = self.green;
|
||||
buf[2] = self.red;
|
||||
RenderedPixel { buf, len: 4 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelFormat {
|
||||
/// Returns the number of bytes per pixel (color depth).
|
||||
pub fn nbytes(&self) -> usize {
|
||||
match self {
|
||||
PixelFormat::Grayscale8 => 1,
|
||||
PixelFormat::Rgb565 => 2,
|
||||
PixelFormat::Rgb888 => 3,
|
||||
PixelFormat::BgrReserved => 4,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderedPixel {
|
||||
/// Returns the number of bytes in the rendered pixel.
|
||||
pub fn nbytes(&self) -> usize {
|
||||
self.len as usize
|
||||
}
|
||||
|
||||
/// Returns a slice to the rendered pixel data.
|
||||
pub fn as_slice(&self) -> &[u8] {
|
||||
&self.buf[..self.nbytes()]
|
||||
}
|
||||
}
|
||||
|
||||
impl Pixel {
|
||||
pub const WHITE: Pixel = Pixel {
|
||||
red: 0xFF,
|
||||
green: 0xFF,
|
||||
blue: 0xFF,
|
||||
};
|
||||
pub const BLACK: Pixel = Pixel {
|
||||
red: 0x00,
|
||||
green: 0x00,
|
||||
blue: 0x00,
|
||||
};
|
||||
}
|
@ -1,5 +1,8 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use alloc::string::ToString;
|
||||
|
||||
use aster_framebuffer::{CONSOLE_NAME, FRAMEBUFFER_CONSOLE};
|
||||
use log::info;
|
||||
|
||||
pub fn init() {
|
||||
@ -7,4 +10,8 @@ pub fn init() {
|
||||
for (name, _) in aster_input::all_devices() {
|
||||
info!("Found Input device, name:{}", name);
|
||||
}
|
||||
|
||||
if let Some(console) = FRAMEBUFFER_CONSOLE.get() {
|
||||
aster_console::register_device(CONSOLE_NAME.to_string(), console.clone());
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,7 @@
|
||||
#![feature(associated_type_defaults)]
|
||||
#![register_tool(component_access_control)]
|
||||
|
||||
use aster_framebuffer::FRAMEBUFFER_CONSOLE;
|
||||
use kcmdline::KCmdlineArg;
|
||||
use ostd::{
|
||||
arch::qemu::{exit_qemu, QemuExitCode},
|
||||
@ -145,6 +146,13 @@ fn init_thread() {
|
||||
|
||||
print_banner();
|
||||
|
||||
// FIXME: CI fails due to suspected performance issues with the framebuffer console.
|
||||
// Additionally, userspace program may render GUIs using the framebuffer,
|
||||
// so we disable the framebuffer console here.
|
||||
if let Some(console) = FRAMEBUFFER_CONSOLE.get() {
|
||||
console.disable();
|
||||
};
|
||||
|
||||
let karg: KCmdlineArg = boot_info().kernel_cmdline.as_str().into();
|
||||
|
||||
let initproc = Process::spawn_user_process(
|
||||
|
@ -12,6 +12,7 @@
|
||||
# - VSOCK: "off" or "on";
|
||||
# - SMP: number of CPUs;
|
||||
# - MEM: amount of memory, e.g. "8G".
|
||||
# - VNC_PORT: VNC port, default is "42".
|
||||
|
||||
OVMF=${OVMF:-"on"}
|
||||
VHOST=${VHOST:-"off"}
|
||||
@ -78,7 +79,7 @@ COMMON_QEMU_ARGS="\
|
||||
-m ${MEM:-8G} \
|
||||
--no-reboot \
|
||||
-nographic \
|
||||
-display none \
|
||||
-display vnc=0.0.0.0:${VNC_PORT:-42} \
|
||||
-serial chardev:mux \
|
||||
-monitor chardev:mux \
|
||||
-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log \
|
||||
|
Loading…
x
Reference in New Issue
Block a user