summaryrefslogtreecommitdiffstats
path: root/rsnix/src/gop.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rsnix/src/gop.rs')
-rw-r--r--rsnix/src/gop.rs71
1 files changed, 64 insertions, 7 deletions
diff --git a/rsnix/src/gop.rs b/rsnix/src/gop.rs
index 9cb0d7d..fa5378e 100644
--- a/rsnix/src/gop.rs
+++ b/rsnix/src/gop.rs
@@ -5,6 +5,9 @@
5/// linear framebuffer. 5/// linear framebuffer.
6use crate::font; 6use crate::font;
7use crate::uefi::{GraphicsOutputProtocol, PIXEL_BGR, PIXEL_RGB}; 7use crate::uefi::{GraphicsOutputProtocol, PIXEL_BGR, PIXEL_RGB};
8use alloc::boxed::Box;
9use alloc::vec;
10use alloc::vec::Vec;
8use core::fmt; 11use core::fmt;
9use core::ptr; 12use core::ptr;
10 13
@@ -22,6 +25,18 @@ pub struct Framebuffer {
22 25
23static mut FB: Option<Framebuffer> = None; 26static mut FB: Option<Framebuffer> = None;
24 27
28/// Software backbuffer: a RAM copy of the whole framebuffer (same pitch
29/// layout, u32 pixels).
30///
31/// Real-hardware GOP framebuffers are typically mapped uncacheable or
32/// write-combining (firmware MTRR/PAT), so *reading* them costs a bus
33/// round-trip per cache line — a full-screen scroll shift reads ~4-8 MB
34/// and crawls. Rasterisation therefore writes every pixel to the shadow
35/// too, and scrolling is a fast cached memcpy inside the shadow followed
36/// by a write-only push to the display: the display is never read.
37static mut SHADOW: *mut u32 = core::ptr::null_mut();
38static mut SHADOW_WORDS: usize = 0;
39
25/// Initialise the framebuffer from GOP. Call *before* ExitBootServices. 40/// Initialise the framebuffer from GOP. Call *before* ExitBootServices.
26pub fn init(gop: &mut GraphicsOutputProtocol) { 41pub fn init(gop: &mut GraphicsOutputProtocol) {
27 let mode = unsafe { &*gop.mode }; 42 let mode = unsafe { &*gop.mode };
@@ -51,6 +66,17 @@ pub fn init(gop: &mut GraphicsOutputProtocol) {
51 bgr, 66 bgr,
52 }; 67 };
53 68
69 // Allocate the RAM backbuffer before first paint so every write goes
70 // to both the display and the shadow.
71 let words = (pitch * height) / 4;
72 unsafe {
73 if words > 0 {
74 let v: Vec<u32> = vec![0; words];
75 SHADOW = Box::into_raw(v.into_boxed_slice()) as *mut u32;
76 SHADOW_WORDS = words;
77 }
78 }
79
54 fb.clear(); 80 fb.clear();
55 81
56 unsafe { 82 unsafe {
@@ -105,7 +131,7 @@ pub fn rgb(r: u8, g: u8, b: u8) -> u32 {
105impl Framebuffer { 131impl Framebuffer {
106 fn clear(&self) { 132 fn clear(&self) {
107 let size = self.pitch * self.height; 133 let size = self.pitch * self.height;
108 // Fill with background colour (32-bit writes) 134 // Fill with background colour (32-bit writes), display + shadow.
109 let mut p = self.base; 135 let mut p = self.base;
110 let end = unsafe { p.add(size) }; 136 let end = unsafe { p.add(size) };
111 unsafe { 137 unsafe {
@@ -114,22 +140,49 @@ impl Framebuffer {
114 p = p.add(4); 140 p = p.add(4);
115 } 141 }
116 } 142 }
143 if !unsafe { SHADOW.is_null() } {
144 for i in 0..unsafe { SHADOW_WORDS } {
145 unsafe {
146 *SHADOW.add(i) = self.bg;
147 }
148 }
149 }
117 } 150 }
118 151
119 /// Shift the whole framebuffer up one row (16 pixels) and clear the 152 /// Shift the whole framebuffer up one row (16 pixels) and clear the
120 /// exposed bottom row. Called by `vt::scroll_up`; the vt then re-blits 153 /// exposed bottom row. Called by `vt::scroll_up`; the vt then re-blits
121 /// only the new bottom row. 154 /// only the new bottom row.
155 ///
156 /// 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 /// (reads are uncached on real hardware and dominate the old path).
122 pub(crate) fn shift_up(&self) { 159 pub(crate) fn shift_up(&self) {
123 // Move everything up one row (16 pixels)
124 let row_bytes = self.pitch * 16; 160 let row_bytes = self.pitch * 16;
125 let total = self.pitch * self.height; 161 let total = self.pitch * self.height;
162 let row_words = row_bytes / 4;
163 let words = total / 4;
126 unsafe { 164 unsafe {
127 ptr::copy(self.base.add(row_bytes), self.base, total - row_bytes); 165 if SHADOW.is_null() {
128 // Clear last row 166 // No shadow (allocation failed): fall back to shifting the
129 let last_row = self.base.add(total - row_bytes); 167 // display directly.
130 for i in 0..(self.pitch * 16 / 4) { 168 ptr::copy(self.base.add(row_bytes), self.base, total - row_bytes);
131 ptr::write_volatile(last_row.add(i * 4) as *mut u32, self.bg); 169 let last_row = self.base.add(total - row_bytes);
170 for i in 0..row_words {
171 ptr::write_volatile(last_row.add(i * 4) as *mut u32, self.bg);
172 }
173 return;
174 }
175 // Shift the shadow up one row (cached RAM — fast).
176 ptr::copy(SHADOW.add(row_words), SHADOW, words - row_words);
177 // Clear the exposed bottom row in the shadow.
178 for i in (words - row_words)..words {
179 *SHADOW.add(i) = self.bg;
132 } 180 }
181 // Push the shadow to the display: write-only, no reads.
182 // A bulk copy (rep movsb) writes in 64-byte bursts that
183 // coalesce well on WC framebuffers — far fewer bus
184 // transactions than per-pixel stores.
185 ptr::copy_nonoverlapping(SHADOW, self.base as *mut u32, words);
133 } 186 }
134 } 187 }
135} 188}
@@ -308,7 +361,11 @@ pub(crate) fn blit_glyph(col: usize, row: usize, ch: u16, fg: (u8, u8, u8), bg:
308 let offset = (y + cy) * fb.pitch + (x + cx) * fb.bytes_per_pixel; 361 let offset = (y + cy) * fb.pitch + (x + cx) * fb.bytes_per_pixel;
309 unsafe { 362 unsafe {
310 let px = if row_bits & (0x80 >> cx) != 0 { fg } else { bg }; 363 let px = if row_bits & (0x80 >> cx) != 0 { fg } else { bg };
364 // Display (volatile; the fb is MMIO-ish) + RAM shadow.
311 ptr::write_volatile(fb.base.add(offset) as *mut u32, px); 365 ptr::write_volatile(fb.base.add(offset) as *mut u32, px);
366 if !SHADOW.is_null() {
367 *SHADOW.add(offset / 4) = px;
368 }
312 } 369 }
313 } 370 }
314 } 371 }