// ============================================================================= // lienzo :: del `Pixmap` al framebuffer de verdad // ----------------------------------------------------------------------------- // El último tramo, y el más tonto de todos — pero el que decide si se ve algo. // `vello_cpu` entrega un `Pixmap` de `PremulRgba8` COMPACTO (ancho*alto, sin // relleno). Un framebuffer real casi nunca es así: tiene un `stride` mayor que // el ancho visible (la GPU alinea cada fila) y el orden de canales lo dicta el // firmware, no nosotros — el GOP de UEFI reporta `Bgr` tan seguido como `Rgb`. // // Por eso el volcado no es un `copy_from_slice`: es fila por fila, con // conversión de canal. Se hace en el orden de memoria del destino para que el // acceso sea secuencial; en un framebuffer sin caché de escritura combinada // (el WC que el kernel programa en el PAT) saltar de acá para allá se paga muy // caro. // ============================================================================= use vello_cpu::Pixmap; /// Cómo espera los canales el framebuffer de destino. /// /// Sólo los dos ordenamientos de 32 bits que aparecen en la práctica: es lo que /// reporta el GOP de UEFI (`PixelFormat::{Rgb, Bgr}`) y lo que ofrece virtio-gpu. /// Los modos de 24 bits y los paletizados no se contemplan — ningún camino de /// wawa los produce. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum FormatoPixel { /// Byte 0 = azul, 1 = verde, 2 = rojo, 3 = ignorado. Lo más común en x86. Bgra8, /// Byte 0 = rojo, 1 = verde, 2 = azul, 3 = ignorado. Rgba8, } /// Vuelca un `Pixmap` sobre un framebuffer crudo. /// /// - `destino`: los bytes del framebuffer, tal como los mapeó el kernel. /// - `stride_bytes`: cuántos bytes hay de una fila a la siguiente. **No** es /// `ancho * 4`: el firmware suele pedir más. Pasarlo mal produce la imagen /// inclinada en diagonal, que es el síntoma clásico de confundir stride con /// ancho. /// - `formato`: el orden de canales del destino. /// /// Recorta a lo que entre: si el pixmap es más grande que el framebuffer, se /// copia la intersección en vez de entrar en pánico. Un kernel no debe morir /// porque una ventana quedó más ancha que la pantalla. /// /// **PISA, no compone.** El alfa del pixmap se descarta y el destino sale /// siempre opaco, así que un píxel que la escena dejó transparente se escribe /// como NEGRO. La consecuencia es la regla que el llamador tiene que respetar: /// *un pixmap no puede ser más grande que la escena que pintó adentro* — el /// sobrante no queda «como estaba», queda negro. Pasó de verdad: el cromo de /// arranque de wawa hacía una cáscara del ancho de la pantalla para un fixture /// de 320 px y dejaba un rectángulo negro de 960x72 sobre el escritorio, toda /// la sesión, sin que ninguna estadística lo dijera (los píxeles con `a == 0` /// no entran en ningún conteo del kernel). Lo certifica /// `el_volcado_pisa_con_negro_lo_que_la_escena_no_cubrio`. /// /// Devuelve cuántas filas se copiaron de verdad. pub fn volcar( pixmap: &Pixmap, destino: &mut [u8], stride_bytes: usize, formato: FormatoPixel, ) -> usize { let ancho = pixmap.width() as usize; let alto = pixmap.height() as usize; if ancho == 0 || alto == 0 || stride_bytes < 4 { return 0; } let origen = pixmap.data(); // Cuántos píxeles de la fila caben en el destino, y cuántas filas hay. let px_por_fila = ancho.min(stride_bytes / 4); let filas = alto.min(destino.len() / stride_bytes); for y in 0..filas { let fila_orig = &origen[y * ancho..y * ancho + px_por_fila]; let base = y * stride_bytes; let fila_dest = &mut destino[base..base + px_por_fila * 4]; for (px, dest) in fila_orig.iter().zip(fila_dest.chunks_exact_mut(4)) { // El pixmap ya viene premultiplicado y el framebuffer es opaco, // así que el alfa se descarta. OJO: eso vale mientras el pixmap // CUBRA lo que vuelca; donde no cubrió, esto escribe negro opaco // sobre lo que hubiera debajo (ver el doc de la función). match formato { FormatoPixel::Bgra8 => { dest[0] = px.b; dest[1] = px.g; dest[2] = px.r; } FormatoPixel::Rgba8 => { dest[0] = px.r; dest[1] = px.g; dest[2] = px.b; } } dest[3] = 255; } } filas }