248 lines
6.4 KiB
Rust
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;
|
|
}
|
|
}
|