summaryrefslogtreecommitdiffstats
path: root/rsnix/src/vt.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--rsnix/src/vt.rs71
1 files changed, 66 insertions, 5 deletions
diff --git a/rsnix/src/vt.rs b/rsnix/src/vt.rs
index 4db08fc..88129f2 100644
--- a/rsnix/src/vt.rs
+++ b/rsnix/src/vt.rs
@@ -119,6 +119,7 @@ pub fn size() -> (usize, usize) {
119} 119}
120 120
121pub fn set_cursor_visible(v: bool) { 121pub fn set_cursor_visible(v: bool) {
122 sync_scroll();
122 unsafe { 123 unsafe {
123 CURSOR_VISIBLE = v; 124 CURSOR_VISIBLE = v;
124 } 125 }
@@ -261,6 +262,12 @@ fn write_char(c: char) {
261} 262}
262 263
263fn csi_final(b: u8) { 264fn csi_final(b: u8) {
265 // A CSI sequence means a full-screen app is actively rendering —
266 // push any deferred scrolls so it draws over a current framebuffer
267 // (streaming shell output never emits CSI, so it stays batched).
268 if unsafe { PENDING_SCROLLS } > 0 {
269 sync_scroll();
270 }
264 match b { 271 match b {
265 // Cursor position (1-based; 0 means 1) 272 // Cursor position (1-based; 0 means 1)
266 b'H' | b'f' => { 273 b'H' | b'f' => {
@@ -400,18 +407,62 @@ fn newline() {
400 } 407 }
401} 408}
402 409
403/// Shift the whole screen up one row: cell grids in memory, pixels on the 410/// Deferred scrolls: the logical grid scrolls instantly (RAM), but the
404/// framebuffer. Only the exposed bottom row gets re-rasterised by `flush`. 411/// framebuffer shift+push is batched. N pending scrolls cost one push,
412/// so a burst of streaming output no longer writes the whole framebuffer
413/// once per line. `flush` renders nothing while scrolls are pending;
414/// `sync_scroll` pushes the whole batch and lets the normal diff re-blit
415/// only the newly-exposed rows.
416const SCROLL_BATCH: usize = 16;
417static mut PENDING_SCROLLS: usize = 0;
418static mut LAST_SYNC_TICK: u16 = 0;
419
420/// Shift the logical grid up one row; defer the framebuffer shift.
405fn scroll_up() { 421fn scroll_up() {
406 let n = unsafe { ROWS * COLS }; 422 let n = unsafe { ROWS * COLS };
407 let cols = unsafe { COLS }; 423 let cols = unsafe { COLS };
408 cells().copy_within(cols..n, 0); 424 cells().copy_within(cols..n, 0);
409 rendered_cells().copy_within(cols..n, 0);
410 for c in 0..cols { 425 for c in 0..cols {
411 cells()[n - cols + c] = BLANK; 426 cells()[n - cols + c] = BLANK;
412 rendered_cells()[n - cols + c] = BLANK;
413 } 427 }
414 gop::shift_up(); 428 unsafe {
429 PENDING_SCROLLS += 1;
430 // Batch: sync when enough scrolls piled up, or when the stream
431 // pauses (~40 ms at the 100 Hz timer) so interactive output still
432 // appears promptly.
433 const TICK_LIMIT: u16 = 4;
434 let now = crate::interrupts::TICKS.load(core::sync::atomic::Ordering::Relaxed);
435 if PENDING_SCROLLS >= SCROLL_BATCH
436 || now.wrapping_sub(LAST_SYNC_TICK) > TICK_LIMIT
437 {
438 sync_scroll();
439 }
440 }
441}
442
443/// Push deferred scrolls to the display in one batch: shift the shadow
444/// (and the rendered grid, which mirrors it) up by the pending count and
445/// do a single write-only push, then let the normal flush diff re-blit
446/// the newly-exposed rows and redraw the cursor.
447pub fn sync_scroll() {
448 unsafe {
449 let n = PENDING_SCROLLS;
450 if n == 0 {
451 return;
452 }
453 let rows = ROWS;
454 let cols = COLS;
455 gop::shift_up_n(n);
456 // Mirror the pushed content in RENDERED (it describes the fb).
457 let total = rows * cols;
458 rendered_cells().copy_within(n * cols..total, 0);
459 for i in (total - n * cols)..total {
460 rendered_cells()[i] = BLANK;
461 }
462 PENDING_SCROLLS = 0;
463 LAST_SYNC_TICK = crate::interrupts::TICKS.load(core::sync::atomic::Ordering::Relaxed);
464 flush();
465 }
415} 466}
416 467
417// --------------------------------------------------------------------------- 468// ---------------------------------------------------------------------------
@@ -473,6 +524,7 @@ pub fn clear_all() {
473 unsafe { 524 unsafe {
474 SGR_FG = 7; 525 SGR_FG = 7;
475 SGR_BG = 0; 526 SGR_BG = 0;
527 sync_scroll();
476 erase_all(); 528 erase_all();
477 CURSOR_R = 0; 529 CURSOR_R = 0;
478 CURSOR_C = 0; 530 CURSOR_C = 0;
@@ -490,6 +542,15 @@ pub fn clear_all() {
490/// (scrolling) or full clears can never leave a stale inverse block that 542/// (scrolling) or full clears can never leave a stale inverse block that
491/// the diff can't see (blank-vs-blank). 543/// the diff can't see (blank-vs-blank).
492fn flush() { 544fn flush() {
545 unsafe {
546 // Deferred scrolls pending: the framebuffer lags the logical grid.
547 // Render nothing until `sync_scroll` pushes the batch — the diff
548 // would otherwise see every cell as changed and re-rasterise the
549 // whole screen once per line.
550 if PENDING_SCROLLS > 0 {
551 return;
552 }
553 }
493 let rows = unsafe { ROWS }; 554 let rows = unsafe { ROWS };
494 let cols = unsafe { COLS }; 555 let cols = unsafe { COLS };
495 let n = rows * cols; 556 let n = rows * cols;