diff options
| author | salmonik <salmonik@tuta.io> | 2026-07-31 18:08:45 +0200 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-07-31 18:08:45 +0200 |
| commit | 075c87d52a4d90c0f00f8ba7ba849677771a9be3 (patch) | |
| tree | 2d89bc491a3ac1d2d018f1f22c48274e77bd7a08 /rsnix/src/gop.rs | |
| parent | gop: RAM backbuffer — scrolling never reads the framebuffer (diff) | |
| download | rs-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 '')
| -rw-r--r-- | rsnix/src/gop.rs | 31 |
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 |
| 376 | pub(crate) fn shift_up() { | 378 | /// bottom rows. `n` batches deferred scrolls into one push. |
| 379 | pub(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 | ||
