tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
chore: refresco desde el monorepo — vello 0.9 / wgpu 29, 120 miembros
1// =============================================================================
2// lienzo :: del `Pixmap` al framebuffer de verdad
3// -----------------------------------------------------------------------------
4// El último tramo, y el más tonto de todos — pero el que decide si se ve algo.
5// `vello_cpu` entrega un `Pixmap` de `PremulRgba8` COMPACTO (ancho*alto, sin
6// relleno). Un framebuffer real casi nunca es así: tiene un `stride` mayor que
7// el ancho visible (la GPU alinea cada fila) y el orden de canales lo dicta el
8// firmware, no nosotros — el GOP de UEFI reporta `Bgr` tan seguido como `Rgb`.
9//
10// Por eso el volcado no es un `copy_from_slice`: es fila por fila, con
11// conversión de canal. Se hace en el orden de memoria del destino para que el
12// acceso sea secuencial; en un framebuffer sin caché de escritura combinada
13// (el WC que el kernel programa en el PAT) saltar de acá para allá se paga muy
14// caro.
15// =============================================================================
16
17use vello_cpu::Pixmap;
18
19/// Cómo espera los canales el framebuffer de destino.
20///
21/// Sólo los dos ordenamientos de 32 bits que aparecen en la práctica: es lo que
22/// reporta el GOP de UEFI (`PixelFormat::{Rgb, Bgr}`) y lo que ofrece virtio-gpu.
23/// Los modos de 24 bits y los paletizados no se contemplan — ningún camino de
24/// wawa los produce.
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub enum FormatoPixel {
27 /// Byte 0 = azul, 1 = verde, 2 = rojo, 3 = ignorado. Lo más común en x86.
28 Bgra8,
29 /// Byte 0 = rojo, 1 = verde, 2 = azul, 3 = ignorado.
30 Rgba8,
31}
32
33/// Vuelca un `Pixmap` sobre un framebuffer crudo.
34///
35/// - `destino`: los bytes del framebuffer, tal como los mapeó el kernel.
36/// - `stride_bytes`: cuántos bytes hay de una fila a la siguiente. **No** es
37/// `ancho * 4`: el firmware suele pedir más. Pasarlo mal produce la imagen
38/// inclinada en diagonal, que es el síntoma clásico de confundir stride con
39/// ancho.
40/// - `formato`: el orden de canales del destino.
41///
42/// Recorta a lo que entre: si el pixmap es más grande que el framebuffer, se
43/// copia la intersección en vez de entrar en pánico. Un kernel no debe morir
44/// porque una ventana quedó más ancha que la pantalla.
45///
46/// Devuelve cuántas filas se copiaron de verdad.
47pub fn volcar(
48 pixmap: &Pixmap,
49 destino: &mut [u8],
50 stride_bytes: usize,
51 formato: FormatoPixel,
52) -> usize {
53 let ancho = pixmap.width() as usize;
54 let alto = pixmap.height() as usize;
55 if ancho == 0 || alto == 0 || stride_bytes < 4 {
56 return 0;
57 }
58
59 let origen = pixmap.data();
60 // Cuántos píxeles de la fila caben en el destino, y cuántas filas hay.
61 let px_por_fila = ancho.min(stride_bytes / 4);
62 let filas = alto.min(destino.len() / stride_bytes);
63
64 for y in 0..filas {
65 let fila_orig = &origen[y * ancho..y * ancho + px_por_fila];
66 let base = y * stride_bytes;
67 let fila_dest = &mut destino[base..base + px_por_fila * 4];
68 for (px, dest) in fila_orig.iter().zip(fila_dest.chunks_exact_mut(4)) {
69 // El pixmap ya viene premultiplicado y el framebuffer es opaco, así
70 // que el alfa se descarta: no hay nada debajo con qué componer.
71 match formato {
72 FormatoPixel::Bgra8 => {
73 dest[0] = px.b;
74 dest[1] = px.g;
75 dest[2] = px.r;
76 }
77 FormatoPixel::Rgba8 => {
78 dest[0] = px.r;
79 dest[1] = px.g;
80 dest[2] = px.b;
81 }
82 }
83 dest[3] = 255;
84 }
85 }
86 filas
87}