summaryrefslogtreecommitdiffstats
path: root/rsnix/src/gop.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-07-31 18:08:45 +0200
committersalmonik <salmonik@tuta.io>2026-07-31 18:08:45 +0200
commit075c87d52a4d90c0f00f8ba7ba849677771a9be3 (patch)
tree2d89bc491a3ac1d2d018f1f22c48274e77bd7a08 /rsnix/src/gop.rs
parentgop: RAM backbuffer — scrolling never reads the framebuffer (diff)
downloadrs-dos-075c87d52a4d90c0f00f8ba7ba849677771a9be3.tar.gz
rs-dos-075c87d52a4d90c0f00f8ba7ba849677771a9be3.zip
vt: defer scrolls and batch framebuffer pushes
Streaming output scrolled one framebuffer push per line (each a ~3-8 MB write-only copy). The vt now scrolls the logical grid instantly in RAM and defers the display shift: N pending scrolls are pushed together as one shift_up_n — one push regardless of batch size. - vt: PENDING_SCROLLS counter; scroll_up shifts CELLS only; flush renders nothing while pending (the diff would otherwise re-rasterise the whole screen per line); sync_scroll() does one gop::shift_up_n + RENDERED mirror + normal diff flush (which re-blits just the newly exposed rows and the cursor). - Sync triggers: batch of 16 rows, ~40 ms of stream pause (100 Hz TICKS), any CSI sequence (full-screen apps like the editor render immediately), clear_all/set_cursor_visible, interactive input (tty read_line/read_byte), and the shell before each prompt. - gop: shift_up → shift_up_n(n) (one shadow memcpy + one push). Verified in QEMU: mmap/hexdump streaming renders in coherent batches with the prompt syncing at the end; clear wipes clean; the editor scrolls and redraws immediately (45-line file, gutter + status bar intact, cursor correct); save/exit/cat round-trip unchanged. Release and dev builds pass.
Diffstat (limited to 'rsnix/src/gop.rs')
-rw-r--r--rsnix/src/gop.rs31
1 files changed, 17 insertions, 14 deletions
diff --git a/rsnix/src/gop.rs b/rsnix/src/gop.rs
index fa5378e..fa14eae 100644
--- a/rsnix/src/gop.rs
+++ b/rsnix/src/gop.rs
@@ -149,14 +149,16 @@ impl Framebuffer {
149 } 149 }
150 } 150 }
151 151
152 /// Shift the whole framebuffer up one row (16 pixels) and clear the 152 /// Shift the framebuffer content up by `n` text rows (16 pixels each)
153 /// exposed bottom row. Called by `vt::scroll_up`; the vt then re-blits 153 /// and clear the exposed bottom rows. Called by `vt` when deferred
154 /// only the new bottom row. 154 /// scrolls are pushed to the display; `n` can batch many scrolls into
155 /// one write-only push.
155 /// 156 ///
156 /// With the RAM backbuffer, this is a cached shadow memcpy + a 157 /// With the RAM backbuffer, this is a cached shadow memcpy + a
157 /// write-only push to the display — the display itself is never read 158 /// write-only push to the display — the display itself is never read
158 /// (reads are uncached on real hardware and dominate the old path). 159 /// (reads are uncached on real hardware and dominate the old path).
159 pub(crate) fn shift_up(&self) { 160 pub(crate) fn shift_up_n(&self, n: usize) {
161 let n = n.max(1).min(self.height / 16); // sanity
160 let row_bytes = self.pitch * 16; 162 let row_bytes = self.pitch * 16;
161 let total = self.pitch * self.height; 163 let total = self.pitch * self.height;
162 let row_words = row_bytes / 4; 164 let row_words = row_bytes / 4;
@@ -165,17 +167,17 @@ impl Framebuffer {
165 if SHADOW.is_null() { 167 if SHADOW.is_null() {
166 // No shadow (allocation failed): fall back to shifting the 168 // No shadow (allocation failed): fall back to shifting the
167 // display directly. 169 // display directly.
168 ptr::copy(self.base.add(row_bytes), self.base, total - row_bytes); 170 ptr::copy(self.base.add(n * row_bytes), self.base, total - n * row_bytes);
169 let last_row = self.base.add(total - row_bytes); 171 let last_row = self.base.add(total - n * row_bytes);
170 for i in 0..row_words { 172 for i in 0..(n * row_words) {
171 ptr::write_volatile(last_row.add(i * 4) as *mut u32, self.bg); 173 ptr::write_volatile(last_row.add(i * 4) as *mut u32, self.bg);
172 } 174 }
173 return; 175 return;
174 } 176 }
175 // Shift the shadow up one row (cached RAM — fast). 177 // Shift the shadow up n rows (cached RAM — fast).
176 ptr::copy(SHADOW.add(row_words), SHADOW, words - row_words); 178 ptr::copy(SHADOW.add(n * row_words), SHADOW, words - n * row_words);
177 // Clear the exposed bottom row in the shadow. 179 // Clear the exposed bottom rows in the shadow.
178 for i in (words - row_words)..words { 180 for i in (words - n * row_words)..words {
179 *SHADOW.add(i) = self.bg; 181 *SHADOW.add(i) = self.bg;
180 } 182 }
181 // Push the shadow to the display: write-only, no reads. 183 // Push the shadow to the display: write-only, no reads.
@@ -372,10 +374,11 @@ pub(crate) fn blit_glyph(col: usize, row: usize, ch: u16, fg: (u8, u8, u8), bg:
372 } 374 }
373} 375}
374 376
375/// Shift the framebuffer up one row and clear the bottom row. 377/// Shift the framebuffer content up `n` text rows and clear the exposed
376pub(crate) fn shift_up() { 378/// bottom rows. `n` batches deferred scrolls into one push.
379pub(crate) fn shift_up_n(n: usize) {
377 if let Some(fb) = unsafe { FB.as_ref() } { 380 if let Some(fb) = unsafe { FB.as_ref() } {
378 fb.shift_up(); 381 fb.shift_up_n(n);
379 } 382 }
380} 383}
381 384