summaryrefslogtreecommitdiffstats
path: root/rsnix/src/gop.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-17 20:28:48 +0200
committersalmonik <salmonik@tuta.io>2026-08-17 20:28:48 +0200
commit7bd3459f4f8bae69509202e5aa0dbd04a42c656f (patch)
tree20ce28490635941d3dd88df5732300e3a0689d51 /rsnix/src/gop.rs
parentTerminal-app design (ADR 0005) + disk-write controller (diff)
downloadrs-dos-7bd3459f4f8bae69509202e5aa0dbd04a42c656f.tar.gz
rs-dos-7bd3459f4f8bae69509202e5aa0dbd04a42c656f.zip
vt → per-instance Terminal rendering into a pixel buffer
- 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
Diffstat (limited to '')
-rw-r--r--rsnix/src/gop.rs86
1 files changed, 67 insertions, 19 deletions
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 {
104 QR_LOCKED.load(Ordering::Relaxed) 104 QR_LOCKED.load(Ordering::Relaxed)
105} 105}
106 106
107/// Hook invoked after the console scrolls, so the window manager can
108/// re-composite GUI windows (which sit above the console layer in the
109/// shadow and must not scroll with it).
110fn scroll_hook_noop() {}
111
112static mut SCROLL_HOOK: fn() = scroll_hook_noop;
113
114pub fn set_scroll_hook(f: fn()) {
115 unsafe {
116 SCROLL_HOOK = f;
117 }
118}
119
120pub(crate) fn call_scroll_hook() {
121 unsafe {
122 SCROLL_HOOK();
123 }
124}
125
126#[inline] 107#[inline]
127fn stall_display() { 108fn stall_display() {
128 let n = DISPLAY_STALL.load(Ordering::Relaxed); 109 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:
630 } 611 }
631} 612}
632 613
614/// Render one 8×16 glyph into a caller-owned pixel buffer (row-major, `w`
615/// pixels per row). No push — the caller composites the buffer.
616pub(crate) fn buffer_glyph_px(
617 buf: *mut u32,
618 w: usize,
619 h: usize,
620 x: usize,
621 y: usize,
622 ch: u16,
623 fg: (u8, u8, u8),
624 bg: (u8, u8, u8),
625) {
626 if buf.is_null() {
627 return;
628 }
629 let glyph = font::GLYPH_8X16;
630 let data = if ch < 256 {
631 match glyph.get(ch as usize) {
632 Some(d) => *d,
633 None => return,
634 }
635 } else if let Some(bits) = unicode_glyph_bits(ch) {
636 bits
637 } else {
638 match glyph.get(32) {
639 Some(d) => *d,
640 None => return,
641 }
642 };
643 let fgc = rgb(fg.0, fg.1, fg.2);
644 let bgc = rgb(bg.0, bg.1, bg.2);
645 unsafe {
646 for cy in 0..16 {
647 let py = y + cy;
648 if py >= h {
649 break;
650 }
651 let row_bits = data[cy];
652 for cx in 0..8 {
653 let px = x + cx;
654 if px >= w {
655 break;
656 }
657 let on = row_bits & (0x80 >> cx) != 0;
658 *buf.add(py * w + px) = if on { fgc } else { bgc };
659 }
660 }
661 }
662}
663
664/// Shift a caller-owned pixel buffer up by `px` rows, clearing the exposed
665/// bottom. No push — the caller composites the buffer.
666pub(crate) fn buffer_shift_up(buf: *mut u32, w: usize, h: usize, px: usize) {
667 if buf.is_null() || px == 0 || px >= h {
668 return;
669 }
670 unsafe {
671 let n = (h - px) * w;
672 core::ptr::copy(buf.add(px * w), buf, n); // memmove (overlap ok)
673 for i in n..(h * w) {
674 *buf.add(i) = 0;
675 }
676 }
677}
678
633/// Print crash/fault lines directly to the framebuffer, bypassing the vt 679/// Print crash/fault lines directly to the framebuffer, bypassing the vt
634/// entirely (the vt state may be corrupt when an exception fires). 680/// entirely (the vt state may be corrupt when an exception fires).
635/// 681///
@@ -958,6 +1004,7 @@ impl Framebuffer {
958 /// With the RAM backbuffer, this is a cached shadow memcpy + a 1004 /// With the RAM backbuffer, this is a cached shadow memcpy + a
959 /// write-only push to the display — the display itself is never read 1005 /// write-only push to the display — the display itself is never read
960 /// (reads are uncached on real hardware and dominate the old path). 1006 /// (reads are uncached on real hardware and dominate the old path).
1007 #[allow(dead_code)]
961 pub(crate) fn shift_up_n(&self, n: usize) { 1008 pub(crate) fn shift_up_n(&self, n: usize) {
962 let n = n.max(1).min(self.height / 16); // sanity 1009 let n = n.max(1).min(self.height / 16); // sanity
963 let row_bytes = self.pitch * 16; 1010 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:
1225 1272
1226/// Shift the framebuffer content up `n` text rows and clear the exposed 1273/// Shift the framebuffer content up `n` text rows and clear the exposed
1227/// bottom rows. `n` batches deferred scrolls into one push. 1274/// bottom rows. `n` batches deferred scrolls into one push.
1275#[allow(dead_code)]
1228pub(crate) fn shift_up_n(n: usize) { 1276pub(crate) fn shift_up_n(n: usize) {
1229 if let Some(fb) = fb().as_ref() { 1277 if let Some(fb) = fb().as_ref() {
1230 fb.shift_up_n(n); 1278 fb.shift_up_n(n);