// ============================================================================= // renaser :: kernel/src/grafico.rs — el sustrato grafico del sistema // ----------------------------------------------------------------------------- // En renaser el texto es un caso particular del dibujo, y el dibujo descansa // sobre este modulo: el color, el framebuffer fisico (`Pantalla`) y el lienzo // intermedio en RAM (`Lienzo`) que sostiene la tecnica de doble bufer. // ============================================================================= use core::cell::UnsafeCell; use core::ptr; use core::sync::atomic::{AtomicBool, Ordering}; use bootloader_api::info::{FrameBuffer, FrameBufferInfo, PixelFormat}; use embedded_graphics::draw_target::DrawTarget; use embedded_graphics::geometry::{OriginDimensions, Size}; use embedded_graphics::pixelcolor::{Rgb888, RgbColor}; use embedded_graphics::Pixel; /// Ancho maximo de lienzo soportado (Full HD). pub(crate) const ANCHO_MAX: usize = 1920; /// Alto maximo de lienzo soportado (Full HD). pub(crate) const ALTO_MAX: usize = 1080; /// Capacidad del lienzo intermedio, en pixeles de 32 bits. const PIXELES_MAX: usize = ANCHO_MAX * ALTO_MAX; // ============================================================================= // COLOR — la unidad indivisible del lenguaje visual de renaser // ============================================================================= /// Color de 24 bits, independiente del formato fisico del framebuffer. #[derive(Clone, Copy)] pub(crate) struct Color { pub(crate) r: u8, pub(crate) g: u8, pub(crate) b: u8, } impl Color { /// Reposo del lienzo: un indigo casi negro, sereno y persistente. pub(crate) const LIENZO_EN_REPOSO: Color = Color { r: 0x12, g: 0x16, b: 0x20, }; /// Panel del compositor (Fase 8): un slate apenas mas claro que el reposo. /// Tiñe cada marco teselado, de modo que el teselado se vea —como una /// rejilla de paneles— aunque sus apps aun no hayan pintado nada. pub(crate) const PANEL: Color = Color { r: 0x1B, g: 0x21, b: 0x30, }; /// Borde de la ventana ENFOCADA (Fase 8c): un indigo brillante. Delata, de /// un vistazo, a quien recibe el teclado. pub(crate) const FOCO: Color = Color { r: 0x4B, g: 0x00, b: 0x82, }; /// Borde de una ventana sin foco (Fase 8c): un gris mate, discreto — marca /// el marco sin reclamar la atencion. pub(crate) const SIN_FOCO: Color = Color { r: 0x3A, g: 0x40, b: 0x4E, }; /// Alerta de colapso: un rojo saturado, imposible de ignorar. pub(crate) const ALERTA: Color = Color { r: 0xD4, g: 0x1E, b: 0x2C, }; /// Agotamiento de memoria (OOM): un naranja de advertencia. pub(crate) const OOM: Color = Color { r: 0xFF, g: 0xA5, b: 0x00, }; /// Tinta del texto: un blanco suave, legible sobre el indigo. pub(crate) const TEXTO: Color = Color { r: 0xE8, g: 0xEC, b: 0xF4, }; /// Desalojo de una aplicacion: un purpura inequivoco. Distinto del rojo de /// colapso del kernel y del naranja de OOM — porque esto NO es un colapso: /// es el kernel conteniendo a un inquilino discolo y siguiendo vivo. pub(crate) const DESALOJO: Color = Color { r: 0x8B, g: 0x5C, b: 0xF6, }; /// Desalojo por desbordo de memoria: un amarillo palido. Distingue al /// inquilino que revento su techo ESPACIAL del que agoto su tiempo (purpura). pub(crate) const DESALOJO_MEMORIA: Color = Color { r: 0xFF, g: 0xFF, b: 0xE0, }; } // ============================================================================= // REGION — la sub-superficie que el kernel asigna a cada aplicacion // ============================================================================= /// Una sub-region rectangular de la pantalla, en pixeles. El kernel asigna una /// a cada aplicacion del userspace: es, a la vez, su ventana al mundo y su /// confinamiento — una app jamas pinta un pixel fuera de la suya. #[derive(Clone, Copy)] pub(crate) struct RegionPantalla { /// Desplazamiento horizontal de la esquina superior izquierda. pub(crate) x: usize, /// Desplazamiento vertical de la esquina superior izquierda. pub(crate) y: usize, /// Ancho de la region, en pixeles. pub(crate) ancho: usize, /// Alto de la region, en pixeles. pub(crate) alto: usize, } /// Traduce un [`Color`] logico al valor nativo de 32 bits que el framebuffer /// espera, respetando el orden de canales que reporta el firmware UEFI. pub(crate) fn codificar(formato: PixelFormat, color: Color) -> u32 { let (r, g, b) = (color.r as u32, color.g as u32, color.b as u32); match formato { PixelFormat::Rgb => r | (g << 8) | (b << 16), PixelFormat::Bgr => b | (g << 8) | (r << 16), PixelFormat::U8 => (r * 54 + g * 183 + b * 19) >> 8, PixelFormat::Unknown { red_position, green_position, blue_position, } => (r << red_position) | (g << green_position) | (b << blue_position), _ => r | (g << 8) | (b << 16), } } // ============================================================================= // ESCRITURA VOLATIL — la unica celula que toca memoria de video real // ============================================================================= /// Deposita un pixel ya codificado en una direccion del framebuffer fisico. /// Las escrituras son **volatiles**: el optimizador no puede elidirlas. /// /// # Seguridad /// /// `destino` debe apuntar a memoria de video valida y escribible para /// `bytes_por_pixel` bytes, correctamente alineada para el ancho de escritura. #[inline] pub(crate) unsafe fn escribir_pixel_volatil(destino: *mut u8, valor: u32, bytes_por_pixel: usize) { match bytes_por_pixel { 4 => unsafe { ptr::write_volatile(destino.cast::(), valor) }, 3 => unsafe { ptr::write_volatile(destino, valor as u8); ptr::write_volatile(destino.add(1), (valor >> 8) as u8); ptr::write_volatile(destino.add(2), (valor >> 16) as u8); }, 2 => unsafe { ptr::write_volatile(destino.cast::(), valor as u16) }, _ => unsafe { ptr::write_volatile(destino, valor as u8) }, } } // ============================================================================= // LIENZO INTERMEDIO — el corazon del doble bufer // ============================================================================= /// Respaldo estatico del lienzo, alineado a pagina. Vive en `.bss`. #[repr(align(4096))] struct LienzoEstatico(UnsafeCell<[u32; PIXELES_MAX]>); // SEGURIDAD: el acceso se serializa mediante `LIENZO_ENTREGADO`, que garantiza // un unico prestamo mutable durante toda la vida del sistema. unsafe impl Sync for LienzoEstatico {} /// Memoria de respaldo del lienzo intermedio. static MEMORIA_LIENZO: LienzoEstatico = LienzoEstatico(UnsafeCell::new([0u32; PIXELES_MAX])); /// Centinela de entrega unica del lienzo. static LIENZO_ENTREGADO: AtomicBool = AtomicBool::new(false); /// Entrega — exactamente una vez — el prestamo mutable de la memoria del lienzo. pub(crate) fn reclamar_memoria_lienzo() -> Option<&'static mut [u32]> { if LIENZO_ENTREGADO.swap(true, Ordering::AcqRel) { return None; } // SEGURIDAD: el `swap` anterior garantiza que este es el unico prestamo // mutable de `MEMORIA_LIENZO` durante toda la ejecucion. let arreglo: &'static mut [u32; PIXELES_MAX] = unsafe { &mut *MEMORIA_LIENZO.0.get() }; Some(arreglo.as_mut_slice()) } /// Superficie de dibujo en RAM. Cada pixel se almacena ya codificado. pub(crate) struct Lienzo { pub(crate) pixeles: &'static mut [u32], pub(crate) ancho: usize, pub(crate) alto: usize, pub(crate) formato: PixelFormat, } impl Lienzo { /// Construye un lienzo sobre la memoria de respaldo reclamada. pub(crate) fn nuevo( memoria: &'static mut [u32], ancho: usize, alto: usize, formato: PixelFormat, ) -> Lienzo { Lienzo { pixeles: memoria, ancho, alto, formato, } } /// Pinta un unico pixel. Las coordenadas fuera del lienzo se ignoran. pub(crate) fn pintar_pixel(&mut self, x: usize, y: usize, color: Color) { if x < self.ancho && y < self.alto { self.pixeles[y * self.ancho + x] = codificar(self.formato, color); } } /// Rellena un rectangulo, recortado con firmeza a los limites del lienzo. pub(crate) fn rellenar_rect( &mut self, x0: usize, y0: usize, ancho: usize, alto: usize, color: Color, ) { let valor = codificar(self.formato, color); let x_ini = x0.min(self.ancho); let y_ini = y0.min(self.alto); let x_fin = (x0 + ancho).min(self.ancho); let y_fin = (y0 + alto).min(self.alto); let mut y = y_ini; while y < y_fin { let base = y * self.ancho; let mut x = x_ini; while x < x_fin { self.pixeles[base + x] = valor; x += 1; } y += 1; } } /// Inunda el lienzo entero con un color plano. pub(crate) fn limpiar(&mut self, color: Color) { self.rellenar_rect(0, 0, self.ancho, self.alto, color); } } // `embedded-graphics` ve el lienzo como un `DrawTarget`: sus primitivas // vectoriales pueden dibujar directamente sobre el. impl DrawTarget for Lienzo { type Color = Rgb888; type Error = core::convert::Infallible; fn draw_iter(&mut self, pixeles: I) -> Result<(), Self::Error> where I: IntoIterator>, { for Pixel(punto, color) in pixeles { if let (Ok(x), Ok(y)) = (usize::try_from(punto.x), usize::try_from(punto.y)) { self.pintar_pixel( x, y, Color { r: color.r(), g: color.g(), b: color.b(), }, ); } } Ok(()) } } impl OriginDimensions for Lienzo { fn size(&self) -> Size { Size::new(self.ancho as u32, self.alto as u32) } } // ============================================================================= // PANTALLA — el framebuffer fisico GOP, envuelto en seguridad // ============================================================================= /// Vista segura del framebuffer lineal entregado por el firmware UEFI. pub(crate) struct Pantalla { pub(crate) base: *mut u8, pub(crate) ancho: usize, pub(crate) alto: usize, pub(crate) paso_bytes: usize, pub(crate) bytes_por_pixel: usize, /// Formato de pixel — necesario para estampar capas que se pintan /// DIRECTAMENTE sobre el framebuffer, no sobre el lienzo (Fase 13). pub(crate) formato: PixelFormat, } impl Pantalla { /// Adopta el framebuffer descrito por `info`. La memoria de video es /// permanente, asi que conservar su puntero crudo es legitimo. pub(crate) fn adoptar(framebuffer: &mut FrameBuffer, info: FrameBufferInfo) -> Pantalla { let base = framebuffer.buffer_mut().as_mut_ptr(); Pantalla { base, ancho: info.width, alto: info.height, paso_bytes: info.stride * info.bytes_per_pixel, bytes_por_pixel: info.bytes_per_pixel, formato: info.pixel_format, } } /// Vuelca el lienzo intermedio sobre la pantalla fisica de un solo gesto. pub(crate) fn presentar(&mut self, lienzo: &Lienzo) { let ancho = self.ancho.min(lienzo.ancho); let alto = self.alto.min(lienzo.alto); for y in 0..alto { let fila_fisica = y * self.paso_bytes; let fila_lienzo = y * lienzo.ancho; for x in 0..ancho { let pixel = lienzo.pixeles[fila_lienzo + x]; // SEGURIDAD: x e y estan acotados por las dimensiones reales // del framebuffer; el desplazamiento cae siempre dentro de el. unsafe { let destino = self.base.add(fila_fisica + x * self.bytes_por_pixel); escribir_pixel_volatil(destino, pixel, self.bytes_por_pixel); } } } } } // ============================================================================= // EL PUNTERO DEL RATON — un sprite estampado sobre el framebuffer (Fase 13) // ----------------------------------------------------------------------------- // El puntero NO vive en el lienzo: el lienzo es el escritorio limpio, y se // recompone con frecuencia. El puntero es una capa de PRESENTACION que cada // volcado vuelve a sellar sobre el framebuffer, despues de copiar el lienzo. // Asi no hay save-under que mantener: el lienzo HACE de save-under, y el // framebuffer recibe el puntero como ultimo gesto. // ============================================================================= /// Ancho del sprite del puntero, en pixeles. const PUNTERO_ANCHO: usize = 12; /// El sprite del puntero — una flecha noroeste. `#` es el borde oscuro, `*` el /// relleno claro, `.` transparente. const PUNTERO: [&[u8; PUNTERO_ANCHO]; 18] = [ b"#...........", b"##..........", b"#*#.........", b"#**#........", b"#***#.......", b"#****#......", b"#*****#.....", b"#******#....", b"#*******#...", b"#********#..", b"#*********#.", b"#*****#####.", b"#**#**#.....", b"#*#.#**#....", b"##..#**#....", b"#....#**#...", b".....#**#...", b"......###...", ]; impl Pantalla { /// Estampa el sprite del puntero del raton sobre el framebuffer, con su /// vertice en (x, y). El sprite se recorta con firmeza a los limites de la /// pantalla. NO altera el lienzo: la proxima recomposicion lo deja intacto; /// el siguiente volcado lo vuelve a estampar (Fase 13). pub(crate) fn estampar_puntero(&mut self, x: usize, y: usize) { let borde = codificar( self.formato, Color { r: 0x10, g: 0x12, b: 0x18, }, ); let relleno = codificar( self.formato, Color { r: 0xF0, g: 0xF2, b: 0xF8, }, ); for (fila, linea) in PUNTERO.iter().enumerate() { let py = y + fila; if py >= self.alto { break; } for (col, &celda) in linea.iter().enumerate() { let px = x + col; if px >= self.ancho { continue; } let valor = match celda { b'#' => borde, b'*' => relleno, _ => continue, }; // SEGURIDAD: (px, py) acotado a las dimensiones reales del // framebuffer; el desplazamiento cae dentro de la memoria de // video que el firmware nos entrego. unsafe { let destino = self .base .add(py * self.paso_bytes + px * self.bytes_por_pixel); escribir_pixel_volatil(destino, valor, self.bytes_por_pixel); } } } } }