From 7bd3459f4f8bae69509202e5aa0dbd04a42c656f Mon Sep 17 00:00:00 2001 From: salmonik Date: Mon, 17 Aug 2026 20:28:48 +0200 Subject: vt → per-instance Terminal rendering into a pixel buffer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Refactor vt.rs into a per-instance Terminal struct (owned cell/rendered grids, cursor, ANSI parser, scroll state), rendering glyphs into a caller-owned pixel buffer with dirty-region blitting - Add gop buffer primitives (buffer_glyph_px, buffer_shift_up) and keep the legacy vt:: API as thin wrappers around the home terminal - Remove the now-obsolete scroll hook and shadow scroll path - Home terminal renders into its own buffer; window_id stays None until the WM integration lands --- rsnix/src/gop.rs | 86 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 67 insertions(+), 19 deletions(-) (limited to 'rsnix/src/gop.rs') diff --git a/rsnix/src/gop.rs b/rsnix/src/gop.rs index cddad22..61cc78d 100644 --- a/rsnix/src/gop.rs +++ b/rsnix/src/gop.rs @@ -104,25 +104,6 @@ pub fn qr_locked() -> bool { QR_LOCKED.load(Ordering::Relaxed) } -/// Hook invoked after the console scrolls, so the window manager can -/// re-composite GUI windows (which sit above the console layer in the -/// shadow and must not scroll with it). -fn scroll_hook_noop() {} - -static mut SCROLL_HOOK: fn() = scroll_hook_noop; - -pub fn set_scroll_hook(f: fn()) { - unsafe { - SCROLL_HOOK = f; - } -} - -pub(crate) fn call_scroll_hook() { - unsafe { - SCROLL_HOOK(); - } -} - #[inline] fn stall_display() { let n = DISPLAY_STALL.load(Ordering::Relaxed); @@ -630,6 +611,71 @@ pub(crate) fn shadow_glyph_px(x: usize, y: usize, ch: u16, fg: (u8, u8, u8), bg: } } +/// Render one 8×16 glyph into a caller-owned pixel buffer (row-major, `w` +/// pixels per row). No push — the caller composites the buffer. +pub(crate) fn buffer_glyph_px( + buf: *mut u32, + w: usize, + h: usize, + x: usize, + y: usize, + ch: u16, + fg: (u8, u8, u8), + bg: (u8, u8, u8), +) { + if buf.is_null() { + return; + } + let glyph = font::GLYPH_8X16; + let data = if ch < 256 { + match glyph.get(ch as usize) { + Some(d) => *d, + None => return, + } + } else if let Some(bits) = unicode_glyph_bits(ch) { + bits + } else { + match glyph.get(32) { + Some(d) => *d, + None => return, + } + }; + let fgc = rgb(fg.0, fg.1, fg.2); + let bgc = rgb(bg.0, bg.1, bg.2); + unsafe { + for cy in 0..16 { + let py = y + cy; + if py >= h { + break; + } + let row_bits = data[cy]; + for cx in 0..8 { + let px = x + cx; + if px >= w { + break; + } + let on = row_bits & (0x80 >> cx) != 0; + *buf.add(py * w + px) = if on { fgc } else { bgc }; + } + } + } +} + +/// Shift a caller-owned pixel buffer up by `px` rows, clearing the exposed +/// bottom. No push — the caller composites the buffer. +pub(crate) fn buffer_shift_up(buf: *mut u32, w: usize, h: usize, px: usize) { + if buf.is_null() || px == 0 || px >= h { + return; + } + unsafe { + let n = (h - px) * w; + core::ptr::copy(buf.add(px * w), buf, n); // memmove (overlap ok) + for i in n..(h * w) { + *buf.add(i) = 0; + } + } +} + /// Print crash/fault lines directly to the framebuffer, bypassing the vt /// entirely (the vt state may be corrupt when an exception fires). /// @@ -958,6 +1004,7 @@ impl Framebuffer { /// With the RAM backbuffer, this is a cached shadow memcpy + a /// write-only push to the display — the display itself is never read /// (reads are uncached on real hardware and dominate the old path). + #[allow(dead_code)] pub(crate) fn shift_up_n(&self, n: usize) { let n = n.max(1).min(self.height / 16); // sanity let row_bytes = self.pitch * 16; @@ -1225,6 +1272,7 @@ pub(crate) fn blit_glyph(col: usize, row: usize, ch: u16, fg: (u8, u8, u8), bg: /// Shift the framebuffer content up `n` text rows and clear the exposed /// bottom rows. `n` batches deferred scrolls into one push. +#[allow(dead_code)] pub(crate) fn shift_up_n(n: usize) { if let Some(fb) = fb().as_ref() { fb.shift_up_n(n); -- cgit v1.2.3