perf: fast display (row runs, bulk fills, pixel scroll), terminal draws changed cells only + scrolls pixels, print! batched (was IPC per fmt fragment), snake sleeps on timer

This commit is contained in:
2026-10-27 22:09:33 +01:00
parent 7b8258b231
commit c8056ddac6
17 changed files with 308 additions and 40 deletions

View File

@@ -47,6 +47,13 @@ pub fn draw_text(ep: CapSlot, x: ScreenCoord, y: ScreenCoord, fg: Color, bg: Col
}
}
/// Moves the rows from `top`, `height` tall, up by `by`, filling the rows
/// left at the bottom with `fill`.
pub fn scroll(ep: CapSlot, top: ScreenCoord, height: Dimension, by: Dimension, fill: Color) {
let band = (top.raw() as usize) | ((height.raw() as usize) << 16);
let _ = syscall::request(ep, [types::display::SCROLL, band, by.raw() as usize, fill.raw() as usize]);
}
/// The display server's character cell, in pixels.
pub const CELL_WIDTH: usize = 8;
pub const CELL_HEIGHT: usize = 16;

View File

@@ -1,10 +1,32 @@
use core::fmt;
struct Writer;
/// Holds what one `print!` formats, so it goes out in full messages:
/// formatting writes in fragments (padding a character at a time), and a
/// call per fragment is a call (and a redraw, on a terminal) per character.
struct Writer {
buf: [u8; BATCH],
len: usize,
}
/// A few whole text messages' worth.
const BATCH: usize = 5 * types::log::TEXT_BYTES;
impl Writer {
fn flush(&mut self) {
write_bytes(&self.buf[..self.len]);
self.len = 0;
}
}
impl fmt::Write for Writer {
fn write_str(&mut self, s: &str) -> fmt::Result {
write_bytes(s.as_bytes());
for &b in s.as_bytes() {
if self.len == BATCH {
self.flush();
}
self.buf[self.len] = b;
self.len += 1;
}
Ok(())
}
}
@@ -26,7 +48,9 @@ pub fn write_bytes(bytes: &[u8]) {
pub fn _print(args: fmt::Arguments) {
use fmt::Write;
Writer.write_fmt(args).ok();
let mut w = Writer { buf: [0; BATCH], len: 0 };
w.write_fmt(args).ok();
w.flush();
}
#[macro_export]

View File

@@ -118,15 +118,36 @@ impl Window {
}
}
/// `count` u32s from `offset` set to `value`.
/// `count` u32s from `offset` set to `value`: one check, then a run of
/// stores (drawing fills thousands at a time).
pub fn fill_u32(&self, offset: ByteOffset, count: usize, value: u32) {
let bytes = count.checked_mul(4).expect("window fill too large");
self.at(offset, bytes, 4);
let addr = self.at(offset, bytes, 4) as *mut u32;
for i in 0..count {
self.write(offset + i * 4, value);
// SAFETY: the whole run was checked in range and aligned.
unsafe { core::ptr::write_volatile(addr.add(i), value) }
}
}
/// `values` stored from `offset` on, in one checked run.
pub fn write_u32s(&self, offset: ByteOffset, values: &[u32]) {
let addr = self.at(offset, values.len() * 4, 4) as *mut u32;
for (i, v) in values.iter().enumerate() {
// SAFETY: the whole run was checked in range and aligned.
unsafe { core::ptr::write_volatile(addr.add(i), *v) }
}
}
/// Moves `len` bytes from `from` to `to` inside the window; the two may
/// overlap.
pub fn copy_within(&self, from: ByteOffset, to: ByteOffset, len: usize) {
let src = self.at(from, len, 1) as *const u8;
let dst = self.at(to, len, 1) as *mut u8;
// SAFETY: both ranges are inside the mapped window; `copy` allows
// overlap. Plain memory moves, not device registers.
unsafe { core::ptr::copy(src, dst, len) }
}
/// `count` words from `offset` as atomics, for memory shared with
/// threads or processes that only ever touch it atomically.
pub fn atomics(&self, offset: ByteOffset, count: usize) -> &'static [AtomicUsize] {