⎇ e94023d8af790ded497f740d7abe811ce0523be9
renaser/kernel/src/consola.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
feat(renaser): Fase 8a — el compositor teselante
1// =============================================================================
2// renaser :: kernel/src/consola.rs — la superficie de texto e imagen
3// -----------------------------------------------------------------------------
4// La consola une el lienzo intermedio, la pantalla fisica y una pluma de
5// escritura. Rasteriza cada glifo con `fontdue` al vuelo —el texto convertido,
6// por fin, en dibujo— y tambien sabe volcar fotogramas crudos del userspace
7// WASM. Es global y serializada tras un `Mutex`: las tareas escriben en ella.
8// =============================================================================
9
10use spin::{Mutex, Once};
11
12use crate::grafico::{codificar, Color, Lienzo, Pantalla, RegionPantalla};
13use crate::texto;
14
15/// Margen del texto respecto al borde del lienzo, en pixeles.
16const MARGEN: usize = 40;
17/// Tamaño de la tipografia, en pixeles.
18const TAM_FUENTE: f32 = 30.0;
19/// Altura de avance de una linea de texto, en pixeles.
20const ALTO_LINEA: usize = 40;
21/// Posicion vertical de la primera linea base.
22const BASE_INICIAL: usize = MARGEN + 30;
23
24/// Interpola dos colores segun una cobertura: `0` => `fondo`, `255` => `tinta`.
25fn mezclar(fondo: Color, tinta: Color, cobertura: u8) -> Color {
26 let canal = |a: u8, b: u8| -> u8 {
27 let c = cobertura as u16;
28 ((a as u16 * (255 - c) + b as u16 * c) / 255) as u8
29 };
30 Color {
31 r: canal(fondo.r, tinta.r),
32 g: canal(fondo.g, tinta.g),
33 b: canal(fondo.b, tinta.b),
34 }
35}
36
37/// La consola grafica de renaser: doble bufer, pantalla fisica y pluma.
38pub(crate) struct Consola {
39 lienzo: Lienzo,
40 pantalla: Pantalla,
41 /// Posicion horizontal de la pluma de escritura.
42 pluma_x: usize,
43 /// Linea base vertical de la pluma de escritura.
44 base_y: usize,
45}
46
47// SEGURIDAD: `Consola` encierra, via `Pantalla`, un puntero crudo al
48// framebuffer. Ese puntero es valido durante toda la vida del kernel y todo
49// acceso a la consola se serializa tras un `Mutex`. En un sistema de un solo
50// nucleo, esto la hace segura de compartir entre el hilo principal y las tareas.
51unsafe impl Send for Consola {}
52
53impl Consola {
54 /// Crea una consola con la pluma en la esquina superior izquierda.
55 pub(crate) fn nueva(lienzo: Lienzo, pantalla: Pantalla) -> Consola {
56 Consola {
57 lienzo,
58 pantalla,
59 pluma_x: MARGEN,
60 base_y: BASE_INICIAL,
61 }
62 }
63
64 /// Lleva la pluma al inicio de la siguiente linea. Al llegar al fondo,
65 /// limpia el lienzo: una pizarra nueva.
66 fn nueva_linea(&mut self) {
67 self.pluma_x = MARGEN;
68 self.base_y += ALTO_LINEA;
69 if self.base_y + ALTO_LINEA >= self.lienzo.alto {
70 self.lienzo.limpiar(Color::LIENZO_EN_REPOSO);
71 self.base_y = BASE_INICIAL;
72 }
73 }
74
75 /// Escribe un caracter: rasteriza su glifo y avanza la pluma.
76 fn escribir_char(&mut self, caracter: char) {
77 if caracter == '\n' {
78 self.nueva_linea();
79 return;
80 }
81 let (metricas, cobertura) = texto::rasterizar(caracter, TAM_FUENTE);
82 // Salto de linea automatico al alcanzar el margen derecho.
83 if self.pluma_x + metricas.advance_width as usize + MARGEN > self.lienzo.ancho {
84 self.nueva_linea();
85 }
86 self.dibujar_glifo(&metricas, &cobertura);
87 self.pluma_x += metricas.advance_width as usize;
88 }
89
90 /// Escribe una cadena completa, caracter a caracter.
91 pub(crate) fn escribir(&mut self, texto: &str) {
92 for caracter in texto.chars() {
93 self.escribir_char(caracter);
94 }
95 }
96
97 /// Funde un mapa de cobertura de `fontdue` sobre el lienzo, en la pluma.
98 fn dibujar_glifo(&mut self, metricas: &fontdue::Metrics, cobertura: &[u8]) {
99 // Origen del glifo: la pluma desplazada por las metricas. El mapa de
100 // `fontdue` se recorre de arriba a abajo desde la cima del glifo.
101 let inicio_x = self.pluma_x as isize + metricas.xmin as isize;
102 let inicio_y = self.base_y as isize - metricas.ymin as isize - metricas.height as isize;
103
104 for fila in 0..metricas.height {
105 for col in 0..metricas.width {
106 let opacidad = cobertura[fila * metricas.width + col];
107 if opacidad == 0 {
108 continue; // pixel transparente: no toca el fondo
109 }
110 let x = inicio_x + col as isize;
111 let y = inicio_y + fila as isize;
112 if x < 0 || y < 0 {
113 continue;
114 }
115 let color = mezclar(Color::LIENZO_EN_REPOSO, Color::TEXTO, opacidad);
116 self.lienzo.pintar_pixel(x as usize, y as usize, color);
117 }
118 }
119 }
120
121 /// Compone un fotograma crudo del userspace WASM —pixeles `0x00RRGGBB`, con
122 /// sus limites ya verificados por el host— dentro del MARCO que el
123 /// compositor (Fase 8) asigno a su aplicacion. El fotograma mide el tamaño
124 /// NATURAL de la app (`nat_ancho × nat_alto`); se CENTRA en el marco —que el
125 /// teselado pudo hacer mayor o menor que ese natural— y se recorta con
126 /// firmeza a sus bordes. Una app jamas pinta un pixel fuera de su marco.
127 fn volcar_marco(
128 &mut self,
129 marco: RegionPantalla,
130 nat_ancho: usize,
131 nat_alto: usize,
132 datos: &[u8],
133 ) {
134 if nat_ancho == 0 || nat_alto == 0 {
135 return;
136 }
137 // Centrar el fotograma natural dentro del marco. Si el natural excede
138 // al marco, el desplazamiento queda en cero y el sobrante se recorta.
139 let off_x = marco.x + marco.ancho.saturating_sub(nat_ancho) / 2;
140 let off_y = marco.y + marco.alto.saturating_sub(nat_alto) / 2;
141 let marco_x_fin = marco.x + marco.ancho;
142 let marco_y_fin = marco.y + marco.alto;
143
144 for (indice, trozo) in datos.chunks_exact(4).enumerate() {
145 let columna = indice % nat_ancho;
146 let fila = indice / nat_ancho;
147 if fila >= nat_alto {
148 break; // el fotograma excede su alto natural: se ignora el resto
149 }
150 let x = off_x + columna;
151 let y = off_y + fila;
152 // Recorte firme: al marco —el confinamiento de la app— y al lienzo.
153 if x >= marco_x_fin || y >= marco_y_fin {
154 continue;
155 }
156 if x >= self.lienzo.ancho || y >= self.lienzo.alto {
157 continue;
158 }
159 let p = u32::from_le_bytes([trozo[0], trozo[1], trozo[2], trozo[3]]);
160 let color = Color {
161 r: (p >> 16) as u8,
162 g: (p >> 8) as u8,
163 b: p as u8,
164 };
165 self.lienzo.pixeles[y * self.lienzo.ancho + x] =
166 codificar(self.lienzo.formato, color);
167 }
168 self.presentar();
169 }
170
171 /// Inunda una region entera con un color plano y la presenta. Es la baliza
172 /// de desalojo: cuando una aplicacion falla, su marco se tatua de purpura.
173 fn pintar_region(&mut self, region: RegionPantalla, color: Color) {
174 self.lienzo
175 .rellenar_rect(region.x, region.y, region.ancho, region.alto, color);
176 self.presentar();
177 }
178
179 /// Pinta el escenario del compositor (Fase 8): inunda el area de apps con
180 /// el reposo del lienzo —borrando cuanto hubiera debajo— y, sobre ella,
181 /// tiñe cada marco teselado con el color de panel. Asi el teselado se ve
182 /// como una rejilla de paneles aun antes de que sus apps pinten nada.
183 fn pintar_escenario(&mut self, area: RegionPantalla, marcos: &[RegionPantalla]) {
184 self.lienzo.rellenar_rect(
185 area.x,
186 area.y,
187 area.ancho,
188 area.alto,
189 Color::LIENZO_EN_REPOSO,
190 );
191 for marco in marcos {
192 self.lienzo
193 .rellenar_rect(marco.x, marco.y, marco.ancho, marco.alto, Color::PANEL);
194 }
195 self.presentar();
196 }
197
198 /// Vuelca el lienzo sobre la pantalla fisica.
199 pub(crate) fn presentar(&mut self) {
200 self.pantalla.presentar(&self.lienzo);
201 }
202}
203
204/// La consola global de renaser. Se funde en el arranque; despues, las tareas
205/// asincronas y las capacidades del userspace escriben en ella tras su `Mutex`.
206pub(crate) static CONSOLA: Once<Mutex<Consola>> = Once::new();
207
208/// Puerta del kernel para la capacidad `sys_render_frame` del userspace WASM:
209/// compone sobre la consola global un fotograma —cuyos limites el host ya
210/// verifico matematicamente contra la memoria lineal del modulo— centrado en
211/// el marco que el compositor asigno a esa aplicacion. `nat_ancho`/`nat_alto`
212/// son el tamaño natural del lienzo de la app.
213pub(crate) fn volcar_marco_wasm(
214 marco: RegionPantalla,
215 nat_ancho: usize,
216 nat_alto: usize,
217 datos: &[u8],
218) {
219 if let Some(consola) = CONSOLA.get() {
220 consola.lock().volcar_marco(marco, nat_ancho, nat_alto, datos);
221 }
222}
223
224/// Pinta el escenario del compositor (Fase 8): el area de apps y, sobre ella,
225/// cada marco teselado. Se invoca una vez, en el arranque, tras teselar.
226pub(crate) fn pintar_escenario(area: RegionPantalla, marcos: &[RegionPantalla]) {
227 if let Some(consola) = CONSOLA.get() {
228 consola.lock().pintar_escenario(area, marcos);
229 }
230}
231
232/// Tatua la baliza de desalojo sobre la region de una aplicacion que el kernel
233/// ha dado por terminada. El color delata la causa —purpura para una falla de
234/// ejecucion o de combustible, amarillo palido para un desbordo de memoria—. Es
235/// una advertencia NO fatal: la app muere, el kernel y sus vecinas siguen vivos.
236pub(crate) fn pintar_desalojo(region: RegionPantalla, color: Color) {
237 if let Some(consola) = CONSOLA.get() {
238 consola.lock().pintar_region(region, color);
239 }
240}