Files
yggdrasil/kernel/libk/src/device/display/fb_console.rs
T
2025-07-17 17:47:24 +03:00

248 lines
6.4 KiB
Rust

//! Framebuffer console driver
use alloc::sync::Arc;
use libk_util::sync::IrqSafeSpinlock;
use yggdrasil_abi::error::Error;
use crate::debug::DebugSink;
use super::{
access::KernelFramebufferAccess,
console::{self, Attributes, ConsoleBuffer, ConsoleState, DisplayConsole},
font::PcScreenFont,
Color, DisplayDevice,
};
struct Inner {
display: Arc<dyn DisplayDevice>,
font: PcScreenFont<'static>,
char_width: u32,
char_height: u32,
width: u32,
height: u32,
cursor_row: u32,
cursor_col: u32,
boot_framebuffer: bool,
}
struct DrawGlyph {
sx: u32,
sy: u32,
c: u8,
fg: Color,
bg: Color,
bytes_per_line: usize,
}
/// Framebuffer console device wrapper
pub struct FramebufferConsole {
inner: IrqSafeSpinlock<Inner>,
state: IrqSafeSpinlock<ConsoleState>,
}
impl DebugSink for FramebufferConsole {
fn putc(&self, c: u8) -> Result<(), Error> {
self.write_char(c);
Ok(())
}
fn supports_control_sequences(&self) -> bool {
true
}
}
impl DisplayConsole for FramebufferConsole {
fn state(&self) -> &IrqSafeSpinlock<ConsoleState> {
&self.state
}
fn flush(&self, state: &mut ConsoleState) {
let mut inner = self.inner.lock();
let Ok(mut framebuffer) = KernelFramebufferAccess::acquire(inner.display.clone()) else {
return;
};
let font = inner.font;
let cw = inner.char_width;
let ch = inner.char_height;
let bytes_per_line = (font.width() as usize).div_ceil(8);
let mut iter = state.buffer.flush_rows();
let old_cursor_col = inner.cursor_col;
let old_cursor_row = inner.cursor_row;
// New cursor
let cursor_col = state.cursor_col;
let cursor_row = state.cursor_row;
framebuffer.fill_rect(
old_cursor_col * cw,
old_cursor_row * ch,
cw,
ch,
state.bg_color.as_rgba(false),
);
while let Some((row_idx, row)) = iter.next_dirty() {
if row_idx >= inner.height {
break;
}
for (col_idx, &chr) in row.iter().take(inner.width as _).enumerate() {
let glyph = chr.character();
let (fg, bg, attr) = chr.attributes();
let mut fg = fg.as_rgba(attr.contains(Attributes::BOLD));
let mut bg = bg.as_rgba(false);
if row_idx == cursor_row && col_idx == cursor_col as usize {
core::mem::swap(&mut fg, &mut bg);
}
let glyph = DrawGlyph {
sx: (col_idx as u32) * cw,
sy: row_idx * ch,
c: glyph,
fg,
bg,
bytes_per_line,
};
draw_glyph(&mut framebuffer, font, glyph);
}
}
// Place cursor
framebuffer.fill_rect(
cursor_col * cw,
cursor_row * ch,
cw,
ch,
state.fg_color.as_rgba(false),
);
inner.display.synchronize().ok();
inner.cursor_col = cursor_col;
inner.cursor_row = cursor_row;
}
}
impl FramebufferConsole {
/// Constructs an instance of console from its framebuffer reference.
pub fn from_framebuffer(
display: Arc<dyn DisplayDevice>,
font: Option<PcScreenFont<'static>>,
boot: bool,
) -> Result<Self, Error> {
let mode = display
.active_display_mode()
.ok_or(Error::InvalidOperation)?;
let font = font.unwrap_or_default();
let char_width = font.width();
let char_height = font.height();
let buffer = ConsoleBuffer::new(mode.height / char_height)?;
let inner = Inner {
display,
font,
width: mode.width / char_width,
height: mode.height / char_height,
char_width,
char_height,
cursor_row: 0,
cursor_col: 0,
boot_framebuffer: boot,
};
Ok(Self {
inner: IrqSafeSpinlock::new(inner),
state: IrqSafeSpinlock::new(ConsoleState::new(buffer)),
})
}
/// Changes the display used for this console
pub fn set_display(&self, display: Arc<dyn DisplayDevice>) -> Result<(), Error> {
let mode = display
.active_display_mode()
.ok_or(Error::InvalidOperation)?;
{
let mut old_inner = self.inner.lock();
let mut old_state = self.state.lock();
let char_width = old_inner.char_width;
let char_height = old_inner.char_height;
let buffer = ConsoleBuffer::new(mode.height / char_height)?;
let inner = Inner {
display: display.clone(),
font: old_inner.font,
width: mode.width / char_width,
height: mode.height / char_height,
char_width,
char_height,
cursor_row: 0,
cursor_col: 0,
boot_framebuffer: false,
};
*old_inner = inner;
*old_state = ConsoleState::new(buffer);
}
// Clear the display
if let Ok(mut framebuffer) = KernelFramebufferAccess::acquire(display) {
framebuffer.fill_rect(
0,
0,
mode.width,
mode.height,
console::DEFAULT_BG_COLOR.as_rgba(false),
);
}
Ok(())
}
/// Returns `true` if the console is using a boot-time framebuffer
pub fn uses_boot_framebuffer(&self) -> bool {
self.inner.lock().boot_framebuffer
}
}
fn draw_glyph(
framebuffer: &mut KernelFramebufferAccess,
font: PcScreenFont<'static>,
g: DrawGlyph,
) {
let mut c = g.c as u32;
if c >= font.len() {
c = b'?' as u32;
}
let f = framebuffer.format();
let fg = g.fg.to_pixel_u32(f);
let bg = g.bg.to_pixel_u32(f);
let mut glyph = font.raw_glyph_data(c);
let mut y = 0;
while y < font.height() {
let mut mask = 1 << (font.width() - 1);
let mut x = 0;
while x < font.width() {
let v = if glyph[0] & mask != 0 { fg } else { bg };
framebuffer.set_pixel(g.sx + x, g.sy + y, v);
mask >>= 1;
x += 1;
}
glyph = &glyph[g.bytes_per_line..];
y += 1;
}
}