diff options
Diffstat (limited to 'rsnix/src/gop.rs')
| -rw-r--r-- | rsnix/src/gop.rs | 71 |
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. |
| 6 | use crate::font; | 6 | use crate::font; |
| 7 | use crate::uefi::{GraphicsOutputProtocol, PIXEL_BGR, PIXEL_RGB}; | 7 | use crate::uefi::{GraphicsOutputProtocol, PIXEL_BGR, PIXEL_RGB}; |
| 8 | use alloc::boxed::Box; | ||
| 9 | use alloc::vec; | ||
| 10 | use alloc::vec::Vec; | ||
| 8 | use core::fmt; | 11 | use core::fmt; |
| 9 | use core::ptr; | 12 | use core::ptr; |
| 10 | 13 | ||
| @@ -22,6 +25,18 @@ pub struct Framebuffer { | |||
| 22 | 25 | ||
| 23 | static mut FB: Option<Framebuffer> = None; | 26 | static 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. | ||
| 37 | static mut SHADOW: *mut u32 = core::ptr::null_mut(); | ||
| 38 | static mut SHADOW_WORDS: usize = 0; | ||
| 39 | |||
| 25 | /// Initialise the framebuffer from GOP. Call *before* ExitBootServices. | 40 | /// Initialise the framebuffer from GOP. Call *before* ExitBootServices. |
| 26 | pub fn init(gop: &mut GraphicsOutputProtocol) { | 41 | pub 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 { | |||
| 105 | impl Framebuffer { | 131 | impl 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 | } |
