⎇ 4bcdc88c838739c9bdac50c6d6d9a906c1d2f729
renaser/kernel/src/grafico.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
feat(renaser): Fases 8b y 8c — el escritorio interactivo
1// =============================================================================
2// renaser :: kernel/src/grafico.rs — el sustrato grafico del sistema
3// -----------------------------------------------------------------------------
4// En renaser el texto es un caso particular del dibujo, y el dibujo descansa
5// sobre este modulo: el color, el framebuffer fisico (`Pantalla`) y el lienzo
6// intermedio en RAM (`Lienzo`) que sostiene la tecnica de doble bufer.
7// =============================================================================
8
9use core::cell::UnsafeCell;
10use core::ptr;
11use core::sync::atomic::{AtomicBool, Ordering};
12
13use bootloader_api::info::{FrameBuffer, FrameBufferInfo, PixelFormat};
14use embedded_graphics::draw_target::DrawTarget;
15use embedded_graphics::geometry::{OriginDimensions, Size};
16use embedded_graphics::pixelcolor::{Rgb888, RgbColor};
17use embedded_graphics::Pixel;
18
19/// Ancho maximo de lienzo soportado (Full HD).
20pub(crate) const ANCHO_MAX: usize = 1920;
21/// Alto maximo de lienzo soportado (Full HD).
22pub(crate) const ALTO_MAX: usize = 1080;
23/// Capacidad del lienzo intermedio, en pixeles de 32 bits.
24const PIXELES_MAX: usize = ANCHO_MAX * ALTO_MAX;
25
26// =============================================================================
27// COLOR — la unidad indivisible del lenguaje visual de renaser
28// =============================================================================
29
30/// Color de 24 bits, independiente del formato fisico del framebuffer.
31#[derive(Clone, Copy)]
32pub(crate) struct Color {
33 pub(crate) r: u8,
34 pub(crate) g: u8,
35 pub(crate) b: u8,
36}
37
38impl Color {
39 /// Reposo del lienzo: un indigo casi negro, sereno y persistente.
40 pub(crate) const LIENZO_EN_REPOSO: Color = Color {
41 r: 0x12,
42 g: 0x16,
43 b: 0x20,
44 };
45
46 /// Panel del compositor (Fase 8): un slate apenas mas claro que el reposo.
47 /// Tiñe cada marco teselado, de modo que el teselado se vea —como una
48 /// rejilla de paneles— aunque sus apps aun no hayan pintado nada.
49 pub(crate) const PANEL: Color = Color {
50 r: 0x1B,
51 g: 0x21,
52 b: 0x30,
53 };
54
55 /// Borde de la ventana ENFOCADA (Fase 8c): un indigo brillante. Delata, de
56 /// un vistazo, a quien recibe el teclado.
57 pub(crate) const FOCO: Color = Color {
58 r: 0x4B,
59 g: 0x00,
60 b: 0x82,
61 };
62
63 /// Borde de una ventana sin foco (Fase 8c): un gris mate, discreto — marca
64 /// el marco sin reclamar la atencion.
65 pub(crate) const SIN_FOCO: Color = Color {
66 r: 0x3A,
67 g: 0x40,
68 b: 0x4E,
69 };
70
71 /// Alerta de colapso: un rojo saturado, imposible de ignorar.
72 pub(crate) const ALERTA: Color = Color {
73 r: 0xD4,
74 g: 0x1E,
75 b: 0x2C,
76 };
77
78 /// Agotamiento de memoria (OOM): un naranja de advertencia.
79 pub(crate) const OOM: Color = Color {
80 r: 0xFF,
81 g: 0xA5,
82 b: 0x00,
83 };
84
85 /// Tinta del texto: un blanco suave, legible sobre el indigo.
86 pub(crate) const TEXTO: Color = Color {
87 r: 0xE8,
88 g: 0xEC,
89 b: 0xF4,
90 };
91
92 /// Desalojo de una aplicacion: un purpura inequivoco. Distinto del rojo de
93 /// colapso del kernel y del naranja de OOM — porque esto NO es un colapso:
94 /// es el kernel conteniendo a un inquilino discolo y siguiendo vivo.
95 pub(crate) const DESALOJO: Color = Color {
96 r: 0x8B,
97 g: 0x5C,
98 b: 0xF6,
99 };
100
101 /// Desalojo por desbordo de memoria: un amarillo palido. Distingue al
102 /// inquilino que revento su techo ESPACIAL del que agoto su tiempo (purpura).
103 pub(crate) const DESALOJO_MEMORIA: Color = Color {
104 r: 0xFF,
105 g: 0xFF,
106 b: 0xE0,
107 };
108}
109
110// =============================================================================
111// REGION — la sub-superficie que el kernel asigna a cada aplicacion
112// =============================================================================
113
114/// Una sub-region rectangular de la pantalla, en pixeles. El kernel asigna una
115/// a cada aplicacion del userspace: es, a la vez, su ventana al mundo y su
116/// confinamiento — una app jamas pinta un pixel fuera de la suya.
117#[derive(Clone, Copy)]
118pub(crate) struct RegionPantalla {
119 /// Desplazamiento horizontal de la esquina superior izquierda.
120 pub(crate) x: usize,
121 /// Desplazamiento vertical de la esquina superior izquierda.
122 pub(crate) y: usize,
123 /// Ancho de la region, en pixeles.
124 pub(crate) ancho: usize,
125 /// Alto de la region, en pixeles.
126 pub(crate) alto: usize,
127}
128
129/// Traduce un [`Color`] logico al valor nativo de 32 bits que el framebuffer
130/// espera, respetando el orden de canales que reporta el firmware UEFI.
131pub(crate) fn codificar(formato: PixelFormat, color: Color) -> u32 {
132 let (r, g, b) = (color.r as u32, color.g as u32, color.b as u32);
133 match formato {
134 PixelFormat::Rgb => r | (g << 8) | (b << 16),
135 PixelFormat::Bgr => b | (g << 8) | (r << 16),
136 PixelFormat::U8 => (r * 54 + g * 183 + b * 19) >> 8,
137 PixelFormat::Unknown {
138 red_position,
139 green_position,
140 blue_position,
141 } => (r << red_position) | (g << green_position) | (b << blue_position),
142 _ => r | (g << 8) | (b << 16),
143 }
144}
145
146// =============================================================================
147// ESCRITURA VOLATIL — la unica celula que toca memoria de video real
148// =============================================================================
149
150/// Deposita un pixel ya codificado en una direccion del framebuffer fisico.
151/// Las escrituras son **volatiles**: el optimizador no puede elidirlas.
152///
153/// # Seguridad
154///
155/// `destino` debe apuntar a memoria de video valida y escribible para
156/// `bytes_por_pixel` bytes, correctamente alineada para el ancho de escritura.
157#[inline]
158pub(crate) unsafe fn escribir_pixel_volatil(destino: *mut u8, valor: u32, bytes_por_pixel: usize) {
159 match bytes_por_pixel {
160 4 => unsafe { ptr::write_volatile(destino.cast::<u32>(), valor) },
161 3 => unsafe {
162 ptr::write_volatile(destino, valor as u8);
163 ptr::write_volatile(destino.add(1), (valor >> 8) as u8);
164 ptr::write_volatile(destino.add(2), (valor >> 16) as u8);
165 },
166 2 => unsafe { ptr::write_volatile(destino.cast::<u16>(), valor as u16) },
167 _ => unsafe { ptr::write_volatile(destino, valor as u8) },
168 }
169}
170
171// =============================================================================
172// LIENZO INTERMEDIO — el corazon del doble bufer
173// =============================================================================
174
175/// Respaldo estatico del lienzo, alineado a pagina. Vive en `.bss`.
176#[repr(align(4096))]
177struct LienzoEstatico(UnsafeCell<[u32; PIXELES_MAX]>);
178
179// SEGURIDAD: el acceso se serializa mediante `LIENZO_ENTREGADO`, que garantiza
180// un unico prestamo mutable durante toda la vida del sistema.
181unsafe impl Sync for LienzoEstatico {}
182
183/// Memoria de respaldo del lienzo intermedio.
184static MEMORIA_LIENZO: LienzoEstatico = LienzoEstatico(UnsafeCell::new([0u32; PIXELES_MAX]));
185
186/// Centinela de entrega unica del lienzo.
187static LIENZO_ENTREGADO: AtomicBool = AtomicBool::new(false);
188
189/// Entrega — exactamente una vez — el prestamo mutable de la memoria del lienzo.
190pub(crate) fn reclamar_memoria_lienzo() -> Option<&'static mut [u32]> {
191 if LIENZO_ENTREGADO.swap(true, Ordering::AcqRel) {
192 return None;
193 }
194 // SEGURIDAD: el `swap` anterior garantiza que este es el unico prestamo
195 // mutable de `MEMORIA_LIENZO` durante toda la ejecucion.
196 let arreglo: &'static mut [u32; PIXELES_MAX] = unsafe { &mut *MEMORIA_LIENZO.0.get() };
197 Some(arreglo.as_mut_slice())
198}
199
200/// Superficie de dibujo en RAM. Cada pixel se almacena ya codificado.
201pub(crate) struct Lienzo {
202 pub(crate) pixeles: &'static mut [u32],
203 pub(crate) ancho: usize,
204 pub(crate) alto: usize,
205 pub(crate) formato: PixelFormat,
206}
207
208impl Lienzo {
209 /// Construye un lienzo sobre la memoria de respaldo reclamada.
210 pub(crate) fn nuevo(
211 memoria: &'static mut [u32],
212 ancho: usize,
213 alto: usize,
214 formato: PixelFormat,
215 ) -> Lienzo {
216 Lienzo {
217 pixeles: memoria,
218 ancho,
219 alto,
220 formato,
221 }
222 }
223
224 /// Pinta un unico pixel. Las coordenadas fuera del lienzo se ignoran.
225 pub(crate) fn pintar_pixel(&mut self, x: usize, y: usize, color: Color) {
226 if x < self.ancho && y < self.alto {
227 self.pixeles[y * self.ancho + x] = codificar(self.formato, color);
228 }
229 }
230
231 /// Rellena un rectangulo, recortado con firmeza a los limites del lienzo.
232 pub(crate) fn rellenar_rect(
233 &mut self,
234 x0: usize,
235 y0: usize,
236 ancho: usize,
237 alto: usize,
238 color: Color,
239 ) {
240 let valor = codificar(self.formato, color);
241 let x_ini = x0.min(self.ancho);
242 let y_ini = y0.min(self.alto);
243 let x_fin = (x0 + ancho).min(self.ancho);
244 let y_fin = (y0 + alto).min(self.alto);
245
246 let mut y = y_ini;
247 while y < y_fin {
248 let base = y * self.ancho;
249 let mut x = x_ini;
250 while x < x_fin {
251 self.pixeles[base + x] = valor;
252 x += 1;
253 }
254 y += 1;
255 }
256 }
257
258 /// Inunda el lienzo entero con un color plano.
259 pub(crate) fn limpiar(&mut self, color: Color) {
260 self.rellenar_rect(0, 0, self.ancho, self.alto, color);
261 }
262}
263
264// `embedded-graphics` ve el lienzo como un `DrawTarget`: sus primitivas
265// vectoriales pueden dibujar directamente sobre el.
266impl DrawTarget for Lienzo {
267 type Color = Rgb888;
268 type Error = core::convert::Infallible;
269
270 fn draw_iter<I>(&mut self, pixeles: I) -> Result<(), Self::Error>
271 where
272 I: IntoIterator<Item = Pixel<Self::Color>>,
273 {
274 for Pixel(punto, color) in pixeles {
275 if let (Ok(x), Ok(y)) = (usize::try_from(punto.x), usize::try_from(punto.y)) {
276 self.pintar_pixel(
277 x,
278 y,
279 Color {
280 r: color.r(),
281 g: color.g(),
282 b: color.b(),
283 },
284 );
285 }
286 }
287 Ok(())
288 }
289}
290
291impl OriginDimensions for Lienzo {
292 fn size(&self) -> Size {
293 Size::new(self.ancho as u32, self.alto as u32)
294 }
295}
296
297// =============================================================================
298// PANTALLA — el framebuffer fisico GOP, envuelto en seguridad
299// =============================================================================
300
301/// Vista segura del framebuffer lineal entregado por el firmware UEFI.
302pub(crate) struct Pantalla {
303 pub(crate) base: *mut u8,
304 pub(crate) ancho: usize,
305 pub(crate) alto: usize,
306 pub(crate) paso_bytes: usize,
307 pub(crate) bytes_por_pixel: usize,
308}
309
310impl Pantalla {
311 /// Adopta el framebuffer descrito por `info`. La memoria de video es
312 /// permanente, asi que conservar su puntero crudo es legitimo.
313 pub(crate) fn adoptar(framebuffer: &mut FrameBuffer, info: FrameBufferInfo) -> Pantalla {
314 let base = framebuffer.buffer_mut().as_mut_ptr();
315 Pantalla {
316 base,
317 ancho: info.width,
318 alto: info.height,
319 paso_bytes: info.stride * info.bytes_per_pixel,
320 bytes_por_pixel: info.bytes_per_pixel,
321 }
322 }
323
324 /// Vuelca el lienzo intermedio sobre la pantalla fisica de un solo gesto.
325 pub(crate) fn presentar(&mut self, lienzo: &Lienzo) {
326 let ancho = self.ancho.min(lienzo.ancho);
327 let alto = self.alto.min(lienzo.alto);
328
329 for y in 0..alto {
330 let fila_fisica = y * self.paso_bytes;
331 let fila_lienzo = y * lienzo.ancho;
332 for x in 0..ancho {
333 let pixel = lienzo.pixeles[fila_lienzo + x];
334 // SEGURIDAD: x e y estan acotados por las dimensiones reales
335 // del framebuffer; el desplazamiento cae siempre dentro de el.
336 unsafe {
337 let destino = self.base.add(fila_fisica + x * self.bytes_por_pixel);
338 escribir_pixel_volatil(destino, pixel, self.bytes_por_pixel);
339 }
340 }
341 }
342 }
343}