perf(display): present copies window rows as-is (scanout R8G8B8X8 = window byte order, Host hands out row bytes, Framebuffer::write_rgba_row); Window copy_from/copy_to one fenced memcpy; full present ~65ms -> ~3ms

This commit is contained in:
2026-10-28 14:47:06 +01:00
parent d26b265d38
commit 815a2f9fe2
7 changed files with 88 additions and 55 deletions

View File

@@ -3,13 +3,10 @@
//! the display's memory has a fixed ceiling whatever apps do.
use libkos::syscall;
use libkos::window::Window;
use types::address::{ByteOffset, VirtualAddress};
use types::address::VirtualAddress;
use types::badge::Badge;
use types::bounded::{CapSlot, PageCount};
use types::display::Color;
use types::display::window::PIXEL_BYTES;
use types::mapping::PagePerms;
use types::permission::{Permission, PermissionSet};
use crate::server::Host;
@@ -26,12 +23,13 @@ pub struct KernelHost {
/// from.
ep: CapSlot,
input: Option<CapSlot>,
buffers: [Option<(CapSlot, Window)>; MAX_WINDOWS],
/// Each slot's region and its pixels, mapped here as plain memory.
buffers: [Option<(CapSlot, &'static mut [u8])>; MAX_WINDOWS],
}
impl KernelHost {
pub fn new(ep: CapSlot, input: Option<CapSlot>) -> Self {
Self { ep, input, buffers: [None; MAX_WINDOWS] }
Self { ep, input, buffers: [const { None }; MAX_WINDOWS] }
}
}
@@ -43,8 +41,8 @@ impl Host for KernelHost {
let pages = PageCount::new(u16::try_from(bytes.div_ceil(PAGE)).ok()?)?;
let region = syscall::create_region(pages).ok()?;
let va = VirtualAddress::from_raw(BUFFERS_VA + index * BUFFER_SPAN);
match libkos::window::map(region, va, PagePerms::ReadWrite) {
Ok(window) => *slot = Some((region, window)),
match libkos::window::map_buffer(region, va) {
Ok(pixels) => *slot = Some((region, pixels)),
Err(_) => {
syscall::drop_cap(region);
return None;
@@ -55,20 +53,16 @@ impl Host for KernelHost {
}
fn blank(&mut self, index: usize) {
if let Some((_, window)) = self.buffers.get(index).and_then(Option::as_ref) {
window.fill_u32(ByteOffset::ZERO, window.len() / PIXEL_BYTES, 0);
if let Some((_, pixels)) = self.buffers.get_mut(index).and_then(Option::as_mut) {
pixels.fill(0);
}
}
fn read(&self, index: usize, from: usize, out: &mut [Color]) {
let Some((_, window)) = self.buffers.get(index).and_then(Option::as_ref) else { return };
for (i, px) in out.iter_mut().enumerate() {
let at = (from + i) * PIXEL_BYTES;
if at + PIXEL_BYTES > window.len() {
return;
}
*px = Color::from_rgba(window.read::<u32>(ByteOffset::new(at)).to_le_bytes());
}
fn pixels(&self, index: usize, from: usize, count: usize) -> &[u8] {
let Some((_, pixels)) = self.buffers.get(index).and_then(Option::as_ref) else { return &[] };
let start = (from * PIXEL_BYTES).min(pixels.len());
let end = from.saturating_add(count).saturating_mul(PIXEL_BYTES).min(pixels.len());
&pixels[start..end]
}
fn reclaim(&mut self, index: usize) {

View File

@@ -20,8 +20,6 @@ use crate::windows::{MAX_WINDOWS, Windows};
/// The OS bar: one row of text.
pub const BAR_HEIGHT: u16 = font::HEIGHT;
/// Pixels copied per row at a time.
const RUN: usize = 256;
/// What the display needs from the kernel.
pub trait Host {
@@ -31,8 +29,10 @@ pub trait Host {
/// Blanks slot `index`'s buffer, so a new window shows nothing of the
/// last one's.
fn blank(&mut self, index: usize);
/// Pixels of slot `index`'s buffer from pixel `from` on, into `out`.
fn read(&self, index: usize, from: usize, out: &mut [Color]);
/// Up to `count` pixels of slot `index`'s buffer from pixel `from` on,
/// as its bytes (R, G, B, A each); fewer past its end. The app may be
/// drawing into it meanwhile: a torn frame, no worse.
fn pixels(&self, index: usize, from: usize, count: usize) -> &[u8];
/// Takes back every copy of slot `index`'s buffer handed out.
fn reclaim(&mut self, index: usize);
/// A copy of the display's endpoint carrying `badge`.
@@ -141,15 +141,9 @@ impl<F: Framebuffer, H: Host> DisplayServer<F, H> {
let (ww, wh) = (self.window_size().0.raw(), self.window_size().1.raw());
let (x0, y0) = (x.raw().min(ww), y.raw().min(wh));
let (x1, y1) = (x0.saturating_add(width.raw()).min(ww), y0.saturating_add(height.raw()).min(wh));
let mut run = [Color::from_raw(0); RUN];
for row in y0..y1 {
let mut col = x0;
while col < x1 {
let n = usize::from(x1 - col).min(RUN);
self.host.read(index, row as usize * ww as usize + col as usize, &mut run[..n]);
self.fb.write_row(ScreenCoord::new(col), ScreenCoord::new(row + BAR_HEIGHT), &run[..n]);
col += n as u16;
}
let bytes = self.host.pixels(index, row as usize * ww as usize + x0 as usize, usize::from(x1 - x0));
self.fb.write_rgba_row(ScreenCoord::new(x0), ScreenCoord::new(row + BAR_HEIGHT), bytes);
}
self.fb.flush();
}

View File

@@ -15,7 +15,8 @@ const H: usize = 16 + 8;
/// Buffers in memory; a capability is its slot number plus 40.
#[derive(Default)]
struct FakeHost {
buffers: Vec<Option<Vec<Color>>>,
/// Each slot's pixels, the bytes R, G, B, A.
buffers: Vec<Option<Vec<u8>>>,
minted: Vec<usize>,
revoked: Vec<CapSlot>,
reclaimed: Vec<usize>,
@@ -28,17 +29,16 @@ impl Host for FakeHost {
if self.buffers.len() <= index {
self.buffers.resize(index + 1, None);
}
self.buffers[index].get_or_insert_with(|| vec![Color::from_raw(0); pixels]);
self.buffers[index].get_or_insert_with(|| vec![0; pixels * 4]);
CapSlot::new(40 + index as u8)
}
fn blank(&mut self, index: usize) {
if let Some(b) = self.buffers[index].as_mut() {
b.fill(Color::from_raw(0));
b.fill(0);
}
}
fn read(&self, index: usize, from: usize, out: &mut [Color]) {
let b = self.buffers[index].as_ref().unwrap();
out.copy_from_slice(&b[from..from + out.len()]);
fn pixels(&self, index: usize, from: usize, count: usize) -> &[u8] {
&self.buffers[index].as_ref().unwrap()[from * 4..(from + count) * 4]
}
fn reclaim(&mut self, index: usize) {
self.reclaimed.push(index);
@@ -58,6 +58,13 @@ impl Host for FakeHost {
}
}
/// A window buffer all one colour, as an app would draw it.
fn fill(buf: &mut [u8], c: Color) {
for px in buf.chunks_exact_mut(4) {
px.copy_from_slice(&c.to_rgba());
}
}
type Srv = DisplayServer<GridFramebuffer<W, H>, FakeHost>;
fn server() -> Srv {
@@ -76,7 +83,7 @@ fn open(s: &mut Srv, opener: usize, name: &[u8]) -> usize {
/// Paints window `slot`'s buffer one colour and presents all of it.
fn paint(s: &mut Srv, badge: usize, slot: usize, color: Color) -> [usize; 4] {
// The app writes its buffer directly; here, through the fake.
s.host_mut_for_test().buffers[slot].as_mut().unwrap().fill(color);
fill(s.host_mut_for_test().buffers[slot].as_mut().unwrap(), color);
let rect = w::pack_rect(
types::display::ScreenCoord::new(0),
types::display::ScreenCoord::new(0),
@@ -156,9 +163,9 @@ fn closing_takes_the_window_and_its_buffer_back() {
assert_eq!(shown(&s), Some(BLUE), "the terminal is in front again");
assert_eq!(s.handle_call(snake, [w::POLL, 0, 0, 0], None).0[0], MALFORMED, "closed");
// Its buffer is reused, blank, for the next window.
s.host_mut_for_test().buffers[1].as_mut().unwrap().fill(RED);
fill(s.host_mut_for_test().buffers[1].as_mut().unwrap(), RED);
open(&mut s, 2, b"Snake");
assert!(s.host().buffers[1].as_ref().unwrap().iter().all(|c| *c == Color::from_raw(0)));
assert!(s.host().buffers[1].as_ref().unwrap().iter().all(|b| *b == 0));
}
#[test]
@@ -176,7 +183,7 @@ fn a_launcher_gone_takes_its_windows_with_it() {
fn a_present_outside_the_window_is_clipped() {
let mut s = server();
let t = open(&mut s, 1, b"Terminal");
s.host_mut_for_test().buffers[0].as_mut().unwrap().fill(RED);
fill(s.host_mut_for_test().buffers[0].as_mut().unwrap(), RED);
let rect = w::pack_rect(
types::display::ScreenCoord::new(30),
types::display::ScreenCoord::new(5),