feat(renaser): Fase 8a — el compositor teselante

El kernel deja de colocar las ventanas a mano: las tesela. El motor es
mirada-layout — el mismo nucleo no_std que ordena el compositor Wayland
de brahman, enlazado por path cruzando la frontera de workspace. Es el
primer consumo REAL del nucleo compartido brahman <-> renaser.

- kernel/compositor.rs: enlaza mirada-layout y calcula un marco por app
  con el algoritmo MasterStack, dentro del area de pantalla.
- consola::volcar_marco centra el fotograma natural de la app dentro de
  su marco teselado (antes lo depositaba en region.x/y fijos).
- ContextoCapacidades lleva marco + natural_ancho/alto; sys_render_frame
  valida el fotograma contra el tamaño natural.
- cargar_userspace tesela con el compositor y pinta el escenario antes
  de encender las apps. Las apps NO cambian: el compositor reordena la
  pantalla sin que ninguna toque una instruccion.

Verificado en QEMU (screendump): las cinco apps de genesis teseladas en
MasterStack — hola como ventana maestra, el resto apiladas a la derecha,
cada lienzo centrado en su panel.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-22 18:56:40 +00:00
parent 8fc26b0c0c
commit 65c88ccf25
11 changed files with 393 additions and 77 deletions
+71 -17
View File
@@ -119,21 +119,42 @@ impl Consola {
}
/// Compone un fotograma crudo del userspace WASM —pixeles `0x00RRGGBB`, con
/// sus limites ya verificados por el host— sobre la SUB-REGION asignada a su
/// aplicacion. Cada pixel se recodifica al formato nativo del framebuffer y
/// se deposita desplazado por `(region.x, region.y)`: una app jamas escribe
/// fuera de su ventana, y varias cohabitan el lienzo sin pisarse.
fn volcar_marco(&mut self, region: RegionPantalla, datos: &[u8]) {
/// sus limites ya verificados por el host— dentro del MARCO que el
/// compositor (Fase 8) asigno a su aplicacion. El fotograma mide el tamaño
/// NATURAL de la app (`nat_ancho × nat_alto`); se CENTRA en el marco —que el
/// teselado pudo hacer mayor o menor que ese natural— y se recorta con
/// firmeza a sus bordes. Una app jamas pinta un pixel fuera de su marco.
fn volcar_marco(
&mut self,
marco: RegionPantalla,
nat_ancho: usize,
nat_alto: usize,
datos: &[u8],
) {
if nat_ancho == 0 || nat_alto == 0 {
return;
}
// Centrar el fotograma natural dentro del marco. Si el natural excede
// al marco, el desplazamiento queda en cero y el sobrante se recorta.
let off_x = marco.x + marco.ancho.saturating_sub(nat_ancho) / 2;
let off_y = marco.y + marco.alto.saturating_sub(nat_alto) / 2;
let marco_x_fin = marco.x + marco.ancho;
let marco_y_fin = marco.y + marco.alto;
for (indice, trozo) in datos.chunks_exact(4).enumerate() {
let columna = indice % region.ancho;
let fila = indice / region.ancho;
if fila >= region.alto {
break; // el fotograma excede el alto de la region: se ignora el resto
let columna = indice % nat_ancho;
let fila = indice / nat_ancho;
if fila >= nat_alto {
break; // el fotograma excede su alto natural: se ignora el resto
}
let x = off_x + columna;
let y = off_y + fila;
// Recorte firme: al marco —el confinamiento de la app— y al lienzo.
if x >= marco_x_fin || y >= marco_y_fin {
continue;
}
let x = region.x + columna;
let y = region.y + fila;
if x >= self.lienzo.ancho || y >= self.lienzo.alto {
continue; // recorte firme: nada se pinta fuera del lienzo
continue;
}
let p = u32::from_le_bytes([trozo[0], trozo[1], trozo[2], trozo[3]]);
let color = Color {
@@ -148,13 +169,32 @@ impl Consola {
}
/// Inunda una region entera con un color plano y la presenta. Es la baliza
/// de desalojo: cuando una aplicacion falla, su ventana se tatua de purpura.
/// de desalojo: cuando una aplicacion falla, su marco se tatua de purpura.
fn pintar_region(&mut self, region: RegionPantalla, color: Color) {
self.lienzo
.rellenar_rect(region.x, region.y, region.ancho, region.alto, color);
self.presentar();
}
/// Pinta el escenario del compositor (Fase 8): inunda el area de apps con
/// el reposo del lienzo —borrando cuanto hubiera debajo— y, sobre ella,
/// tiñe cada marco teselado con el color de panel. Asi el teselado se ve
/// como una rejilla de paneles aun antes de que sus apps pinten nada.
fn pintar_escenario(&mut self, area: RegionPantalla, marcos: &[RegionPantalla]) {
self.lienzo.rellenar_rect(
area.x,
area.y,
area.ancho,
area.alto,
Color::LIENZO_EN_REPOSO,
);
for marco in marcos {
self.lienzo
.rellenar_rect(marco.x, marco.y, marco.ancho, marco.alto, Color::PANEL);
}
self.presentar();
}
/// Vuelca el lienzo sobre la pantalla fisica.
pub(crate) fn presentar(&mut self) {
self.pantalla.presentar(&self.lienzo);
@@ -167,11 +207,25 @@ pub(crate) static CONSOLA: Once<Mutex<Consola>> = Once::new();
/// Puerta del kernel para la capacidad `sys_render_frame` del userspace WASM:
/// compone sobre la consola global un fotograma —cuyos limites el host ya
/// verifico matematicamente contra la memoria lineal del modulo— dentro de la
/// region de pantalla que el kernel asigno a esa aplicacion.
pub(crate) fn volcar_marco_wasm(region: RegionPantalla, datos: &[u8]) {
/// verifico matematicamente contra la memoria lineal del modulo— centrado en
/// el marco que el compositor asigno a esa aplicacion. `nat_ancho`/`nat_alto`
/// son el tamaño natural del lienzo de la app.
pub(crate) fn volcar_marco_wasm(
marco: RegionPantalla,
nat_ancho: usize,
nat_alto: usize,
datos: &[u8],
) {
if let Some(consola) = CONSOLA.get() {
consola.lock().volcar_marco(region, datos);
consola.lock().volcar_marco(marco, nat_ancho, nat_alto, datos);
}
}
/// Pinta el escenario del compositor (Fase 8): el area de apps y, sobre ella,
/// cada marco teselado. Se invoca una vez, en el arranque, tras teselar.
pub(crate) fn pintar_escenario(area: RegionPantalla, marcos: &[RegionPantalla]) {
if let Some(consola) = CONSOLA.get() {
consola.lock().pintar_escenario(area, marcos);
}
}