tawasuyu

tawasuyu/brahman

Rust 518TOML 202Markdown 57JSON 197408 Definiciones

⎇ main renaser/kernel/src/grafico.rs Rust · 433 líneas · 15.3 KB Historia Quién tocó qué crudo

feat(renaser): Fase 13 — ratón, puntero y arrastre de flotantes 8fc1d99dd · sergio ·

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    /// Formato de pixel — necesario para estampar capas que se pintan
309    /// DIRECTAMENTE sobre el framebuffer, no sobre el lienzo (Fase 13).
310    pub(crate) formato: PixelFormat,
311}
312
313impl Pantalla {
314    /// Adopta el framebuffer descrito por `info`. La memoria de video es
315    /// permanente, asi que conservar su puntero crudo es legitimo.
316    pub(crate) fn adoptar(framebuffer: &mut FrameBuffer, info: FrameBufferInfo) -> Pantalla {
317        let base = framebuffer.buffer_mut().as_mut_ptr();
318        Pantalla {
319            base,
320            ancho: info.width,
321            alto: info.height,
322            paso_bytes: info.stride * info.bytes_per_pixel,
323            bytes_por_pixel: info.bytes_per_pixel,
324            formato: info.pixel_format,
325        }
326    }
327
328    /// Vuelca el lienzo intermedio sobre la pantalla fisica de un solo gesto.
329    pub(crate) fn presentar(&mut self, lienzo: &Lienzo) {
330        let ancho = self.ancho.min(lienzo.ancho);
331        let alto = self.alto.min(lienzo.alto);
332
333        for y in 0..alto {
334            let fila_fisica = y * self.paso_bytes;
335            let fila_lienzo = y * lienzo.ancho;
336            for x in 0..ancho {
337                let pixel = lienzo.pixeles[fila_lienzo + x];
338                // SEGURIDAD: x e y estan acotados por las dimensiones reales
339                // del framebuffer; el desplazamiento cae siempre dentro de el.
340                unsafe {
341                    let destino = self.base.add(fila_fisica + x * self.bytes_por_pixel);
342                    escribir_pixel_volatil(destino, pixel, self.bytes_por_pixel);
343                }
344            }
345        }
346    }
347}
348
349// =============================================================================
350//  EL PUNTERO DEL RATON — un sprite estampado sobre el framebuffer (Fase 13)
351// -----------------------------------------------------------------------------
352//  El puntero NO vive en el lienzo: el lienzo es el escritorio limpio, y se
353//  recompone con frecuencia. El puntero es una capa de PRESENTACION que cada
354//  volcado vuelve a sellar sobre el framebuffer, despues de copiar el lienzo.
355//  Asi no hay save-under que mantener: el lienzo HACE de save-under, y el
356//  framebuffer recibe el puntero como ultimo gesto.
357// =============================================================================
358
359/// Ancho del sprite del puntero, en pixeles.
360const PUNTERO_ANCHO: usize = 12;
361/// El sprite del puntero — una flecha noroeste. `#` es el borde oscuro, `*` el
362/// relleno claro, `.` transparente.
363const PUNTERO: [&[u8; PUNTERO_ANCHO]; 18] = [
364    b"#...........",
365    b"##..........",
366    b"#*#.........",
367    b"#**#........",
368    b"#***#.......",
369    b"#****#......",
370    b"#*****#.....",
371    b"#******#....",
372    b"#*******#...",
373    b"#********#..",
374    b"#*********#.",
375    b"#*****#####.",
376    b"#**#**#.....",
377    b"#*#.#**#....",
378    b"##..#**#....",
379    b"#....#**#...",
380    b".....#**#...",
381    b"......###...",
382];
383
384impl Pantalla {
385    /// Estampa el sprite del puntero del raton sobre el framebuffer, con su
386    /// vertice en (x, y). El sprite se recorta con firmeza a los limites de la
387    /// pantalla. NO altera el lienzo: la proxima recomposicion lo deja intacto;
388    /// el siguiente volcado lo vuelve a estampar (Fase 13).
389    pub(crate) fn estampar_puntero(&mut self, x: usize, y: usize) {
390        let borde = codificar(
391            self.formato,
392            Color {
393                r: 0x10,
394                g: 0x12,
395                b: 0x18,
396            },
397        );
398        let relleno = codificar(
399            self.formato,
400            Color {
401                r: 0xF0,
402                g: 0xF2,
403                b: 0xF8,
404            },
405        );
406        for (fila, linea) in PUNTERO.iter().enumerate() {
407            let py = y + fila;
408            if py >= self.alto {
409                break;
410            }
411            for (col, &celda) in linea.iter().enumerate() {
412                let px = x + col;
413                if px >= self.ancho {
414                    continue;
415                }
416                let valor = match celda {
417                    b'#' => borde,
418                    b'*' => relleno,
419                    _ => continue,
420                };
421                // SEGURIDAD: (px, py) acotado a las dimensiones reales del
422                // framebuffer; el desplazamiento cae dentro de la memoria de
423                // video que el firmware nos entrego.
424                unsafe {
425                    let destino = self
426                        .base
427                        .add(py * self.paso_bytes + px * self.bytes_por_pixel);
428                    escribir_pixel_volatil(destino, valor, self.bytes_por_pixel);
429                }
430            }
431        }
432    }
433}