gioser

tawasuyu/llimphi

Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.

Rust 660Markdown 302TOML 157RON 312382 Sutichaykuna

⎇ 168cd187519c0ad575d3ce63baee2f164c38d9d1 llimphi-cpu/src/escena.rs Rust · 377 siq'ikuna · 13.0 KB Ñawpa willay Pi imata llamkarqan ch'uya

chore: refresco desde el monorepo — vello 0.9 / wgpu 29, 120 miembros d251eb32b · Sergio ·

1// =============================================================================
2//  escena :: la grabadora de órdenes de dibujo
3// -----------------------------------------------------------------------------
4//  `vello::Scene` es un BUFFER CODIFICADO: cada `fill`/`stroke` no dibuja nada,
5//  apila bytes que después la GPU consume de una. `vello_cpu::RenderContext`, en
6//  cambio, es IMPERATIVO y con estado: se le fija transform/paint/fill-rule y se
7//  le pide `fill_path` ya mismo. Esa es toda la diferencia entre los dos mundos,
8//  y es el trabajo que el plan (W1) señalaba: «vello_cpu cubre fill/stroke/clip/
9//  Pixmap casi 1:1; NO tiene `Scene` — ahí está el trabajo».
10//
11//  `Escena` cierra esa brecha grabando. Expone los OCHO métodos que
12//  `llimphi-compositor` usa de `vello::Scene`, con la misma firma, y guarda cada
13//  llamada como una `Orden`. Después `crate::pintor` las reproduce contra un
14//  `RenderContext`. Grabar en vez de dibujar al vuelo no es un rodeo: es lo que
15//  hace que `append` —componer una escena hija dentro de la madre, que el
16//  compositor usa para reciclar subárboles cacheados— sea posible del todo.
17//
18//  Las órdenes hablan en tipos de `peniko`/`kurbo`, que son LOS MISMOS que usa
19//  vello (ambos resuelven a peniko 0.6). No hay conversión ni copia semántica en
20//  el medio: `Color`, `Affine`, `BezPath` cruzan tal cual.
21// =============================================================================
22
23use alloc::vec::Vec;
24
25use vello_cpu::kurbo::{Affine, BezPath, Rect, RoundedRect, Shape, Stroke};
26use vello_cpu::peniko::{BlendMode, Brush, BrushRef, Color, Fill};
27
28/// Tolerancia de aplanado al convertir una forma arbitraria en `BezPath`.
29///
30/// Es la misma que vello codifica de fábrica para sus formas (0,1 px). Más fino
31/// no se nota en pantalla y cuesta segmentos; más grueso se ve en los radios.
32const TOLERANCIA: f64 = 0.1;
33
34/// Una forma grabada.
35///
36/// Se conservan los casos rápidos en su tipo original en vez de aplanar todo a
37/// `BezPath`: `vello_cpu` tiene `fill_rect`/`stroke_rect` dedicados, y el
38/// compositor de Llimphi dibuja rectángulos y rectángulos redondeados casi
39/// siempre (cajas, bordes, recortes). Aplanar eso a curvas sería pagar
40/// segmentos por nada en el 90% de los nodos.
41#[derive(Clone, Debug)]
42pub enum Forma {
43    /// Rectángulo recto — el camino barato.
44    Rect(Rect),
45    /// Rectángulo redondeado — se aplana al reproducir, pero se graba compacto.
46    Redondeado(RoundedRect),
47    /// Cualquier otra cosa, ya aplanada a curvas de Bézier.
48    Camino(BezPath),
49}
50
51impl Forma {
52    /// Graba una forma cualquiera, quedándose con la representación más barata.
53    ///
54    /// `kurbo::Shape` ofrece `as_rect`/`as_rounded_rect` justamente para esto:
55    /// preguntar sin castear ni conocer el tipo concreto.
56    pub fn de(forma: &impl Shape) -> Self {
57        if let Some(r) = forma.as_rect() {
58            Self::Rect(r)
59        } else if let Some(rr) = forma.as_rounded_rect() {
60            Self::Redondeado(rr)
61        } else {
62            Self::Camino(forma.into_path(TOLERANCIA))
63        }
64    }
65
66    /// La forma como camino de Bézier. Para el recorte de capa, donde
67    /// `vello_cpu` sólo acepta `BezPath`.
68    pub fn a_camino(&self) -> BezPath {
69        match self {
70            Self::Rect(r) => r.into_path(TOLERANCIA),
71            Self::Redondeado(rr) => rr.into_path(TOLERANCIA),
72            Self::Camino(c) => c.clone(),
73        }
74    }
75}
76
77/// Una orden de dibujo grabada, con todo lo que hace falta para reproducirla.
78///
79/// El pincel viaja **en propiedad** (`Brush`, no `BrushRef`): la escena
80/// sobrevive a quien la grabó —es justamente el punto de `append`— así que no
81/// puede quedarse con préstamos.
82#[derive(Clone, Debug)]
83pub enum Orden {
84    /// `Scene::fill`.
85    Rellenar {
86        regla: Fill,
87        xf: Affine,
88        pincel: Brush,
89        xf_pincel: Option<Affine>,
90        forma: Forma,
91    },
92    /// `Scene::stroke`.
93    Trazar {
94        estilo: Stroke,
95        xf: Affine,
96        pincel: Brush,
97        xf_pincel: Option<Affine>,
98        forma: Forma,
99    },
100    /// `Scene::push_layer`.
101    AbrirCapa {
102        regla: Fill,
103        mezcla: BlendMode,
104        alfa: f32,
105        xf: Affine,
106        recorte: Forma,
107    },
108    /// `Scene::push_luminance_mask_layer`.
109    AbrirMascaraLuminancia {
110        regla: Fill,
111        alfa: f32,
112        xf: Affine,
113        recorte: Forma,
114    },
115    /// `Scene::pop_layer`.
116    CerrarCapa,
117    /// `Scene::draw_image`.
118    Imagen {
119        imagen: vello_cpu::peniko::ImageBrush,
120        xf: Affine,
121    },
122    /// `Scene::draw_blurred_rounded_rect`.
123    RectDifuso {
124        xf: Affine,
125        rect: Rect,
126        color: Color,
127        radio: f64,
128        desvio: f64,
129    },
130    /// Una corrida de glifos ya shapeados. El equivalente de `Scene::draw_glyphs`.
131    Glifos {
132        fuente: vello_cpu::peniko::FontData,
133        tamano: f32,
134        xf: Affine,
135        pincel: Brush,
136        /// Ajustar los contornos a la grilla de píxeles. En tamaños de UI
137        /// (12-14 px) es la diferencia entre texto nítido y texto borroso.
138        hint: bool,
139        glifos: Vec<Glifo>,
140    },
141}
142
143/// Un glifo posicionado: qué dibujar y dónde, en coordenadas de la corrida.
144///
145/// Es un tipo propio y no el de vello ni el de `glifo` a propósito: la escena es
146/// el punto donde los dos mundos se tocan, y hacerla hablar el vocabulario de
147/// uno de los dos la ataría a él. Los tres campos son los mismos en ambos, así
148/// que la traducción es un `map` en el borde.
149#[derive(Clone, Copy, Debug, PartialEq)]
150pub struct Glifo {
151    /// Índice del glifo **dentro de la fuente**. No es un code point Unicode:
152    /// el shaping ya resolvió ligaduras, bidi y selección de cara.
153    pub id: u32,
154    /// Desplazamiento horizontal dentro de la corrida.
155    pub x: f32,
156    /// Desplazamiento vertical dentro de la corrida (línea base).
157    pub y: f32,
158}
159
160/// La escena grabada: una lista plana de órdenes, en orden de emisión.
161///
162/// Deliberadamente plana y `Clone`: reproducir es recorrerla, y `append` es
163/// concatenar. No hay árbol que recorrer ni punteros que arreglar.
164#[derive(Clone, Debug, Default)]
165pub struct Escena {
166    ordenes: Vec<Orden>,
167}
168
169impl Escena {
170    /// Una escena vacía.
171    pub fn new() -> Self {
172        Self::default()
173    }
174
175    /// Descarta todo lo grabado conservando la capacidad ya reservada.
176    ///
177    /// Es lo que se llama entre fotogramas: el bucle de Llimphi vuelve a pintar
178    /// la vista entera cada vez, y realocar el vector en cada frame sería
179    /// regalar trabajo al asignador —que en wawa es un `linked_list_allocator`
180    /// de kernel, no un malloc con arenas—.
181    pub fn reset(&mut self) {
182        self.ordenes.clear();
183    }
184
185    /// Las órdenes grabadas, en orden. Lo que consume `crate::pintor`.
186    pub fn ordenes(&self) -> &[Orden] {
187        &self.ordenes
188    }
189
190    /// Cuántas órdenes tiene. Útil para tests y para medir escenas.
191    pub fn len(&self) -> usize {
192        self.ordenes.len()
193    }
194
195    /// ¿No se grabó nada?
196    pub fn is_empty(&self) -> bool {
197        self.ordenes.is_empty()
198    }
199
200    // -------------------------------------------------------------------------
201    //  Los ocho métodos que `llimphi-compositor` usa de `vello::Scene`.
202    //  Las firmas son las de vello 0.7, a propósito: el objetivo es que el
203    //  compositor no note con cuál de los dos está hablando.
204    // -------------------------------------------------------------------------
205
206    /// Rellena una forma. Espeja `vello::Scene::fill`.
207    pub fn fill<'b>(
208        &mut self,
209        style: Fill,
210        transform: Affine,
211        brush: impl Into<BrushRef<'b>>,
212        brush_transform: Option<Affine>,
213        shape: &impl Shape,
214    ) {
215        self.ordenes.push(Orden::Rellenar {
216            regla: style,
217            xf: transform,
218            pincel: brush.into().to_owned(),
219            xf_pincel: brush_transform,
220            forma: Forma::de(shape),
221        });
222    }
223
224    /// Traza el contorno de una forma. Espeja `vello::Scene::stroke`.
225    pub fn stroke<'b>(
226        &mut self,
227        style: &Stroke,
228        transform: Affine,
229        brush: impl Into<BrushRef<'b>>,
230        brush_transform: Option<Affine>,
231        shape: &impl Shape,
232    ) {
233        self.ordenes.push(Orden::Trazar {
234            estilo: style.clone(),
235            xf: transform,
236            pincel: brush.into().to_owned(),
237            xf_pincel: brush_transform,
238            forma: Forma::de(shape),
239        });
240    }
241
242    /// Abre una capa recortada. Espeja `vello::Scene::push_layer`.
243    pub fn push_layer(
244        &mut self,
245        clip_style: Fill,
246        blend: impl Into<BlendMode>,
247        alpha: f32,
248        transform: Affine,
249        clip: &impl Shape,
250    ) {
251        self.ordenes.push(Orden::AbrirCapa {
252            regla: clip_style,
253            mezcla: blend.into(),
254            alfa: alpha.clamp(0.0, 1.0),
255            xf: transform,
256            recorte: Forma::de(clip),
257        });
258    }
259
260    /// Abre una capa que enmascara por luminancia.
261    /// Espeja `vello::Scene::push_luminance_mask_layer`.
262    pub fn push_luminance_mask_layer(
263        &mut self,
264        clip_style: Fill,
265        alpha: f32,
266        transform: Affine,
267        clip: &impl Shape,
268    ) {
269        self.ordenes.push(Orden::AbrirMascaraLuminancia {
270            regla: clip_style,
271            alfa: alpha.clamp(0.0, 1.0),
272            xf: transform,
273            recorte: Forma::de(clip),
274        });
275    }
276
277    /// Cierra la última capa abierta. Espeja `vello::Scene::pop_layer`.
278    pub fn pop_layer(&mut self) {
279        self.ordenes.push(Orden::CerrarCapa);
280    }
281
282    /// Dibuja una imagen. Espeja `vello::Scene::draw_image`.
283    pub fn draw_image(&mut self, image: &vello_cpu::peniko::ImageBrush, transform: Affine) {
284        self.ordenes.push(Orden::Imagen {
285            imagen: image.clone(),
286            xf: transform,
287        });
288    }
289
290    /// Dibuja un rectángulo redondeado con desenfoque gaussiano — la sombra.
291    /// Espeja `vello::Scene::draw_blurred_rounded_rect`.
292    pub fn draw_blurred_rounded_rect(
293        &mut self,
294        transform: Affine,
295        rect: Rect,
296        brush: Color,
297        radius: f64,
298        std_dev: f64,
299    ) {
300        self.ordenes.push(Orden::RectDifuso {
301            xf: transform,
302            rect,
303            color: brush,
304            radio: radius,
305            desvio: std_dev,
306        });
307    }
308
309    /// Dibuja una corrida de glifos ya shapeados.
310    ///
311    /// **No espeja la firma de `vello::Scene::draw_glyphs`, y es a propósito.**
312    /// La de vello devuelve un *builder* (`DrawGlyphs`) que se va configurando y
313    /// se cierra con `.draw(style, glyphs)`. Reproducir ese builder acá sería
314    /// maquinaria para nadie: el único que llama a este método es código propio
315    /// —el puente de `llimphi-text` hacia el backend CPU—, no el DSL de widgets.
316    /// Los ocho métodos que sí espejan firma lo hacen porque `llimphi-compositor`
317    /// los invoca tal cual; éste no está en ese conjunto.
318    pub fn draw_glyphs<'b>(
319        &mut self,
320        fuente: &vello_cpu::peniko::FontData,
321        tamano: f32,
322        brush: impl Into<BrushRef<'b>>,
323        transform: Affine,
324        hint: bool,
325        glifos: impl IntoIterator<Item = Glifo>,
326    ) {
327        let glifos: Vec<Glifo> = glifos.into_iter().collect();
328        if glifos.is_empty() {
329            return;
330        }
331        self.ordenes.push(Orden::Glifos {
332            fuente: fuente.clone(),
333            tamano,
334            xf: transform,
335            pincel: brush.into().to_owned(),
336            hint,
337            glifos,
338        });
339    }
340
341    /// Incorpora una escena hija, opcionalmente transformada.
342    /// Espeja `vello::Scene::append`.
343    ///
344    /// Es la razón de ser de la grabación. El compositor cachea el dibujo de un
345    /// subárbol que no cambió y lo vuelve a pegar trasladado al origen nuevo;
346    /// sin escena grabada eso exigiría re-ejecutar el subárbol entero.
347    pub fn append(&mut self, other: &Self, transform: Option<Affine>) {
348        match transform {
349            None => self.ordenes.extend_from_slice(&other.ordenes),
350            Some(t) => self
351                .ordenes
352                .extend(other.ordenes.iter().map(|o| trasladar(o, t))),
353        }
354    }
355}
356
357/// Aplica una transformación a una orden ya grabada.
358///
359/// La transformación se compone POR IZQUIERDA (`t * xf`), igual que hace vello
360/// al concatenar encodings: la de la madre se aplica DESPUÉS de la de la hija,
361/// que es lo que uno espera al meter un dibujo dentro de otro ya posicionado.
362/// La transformación del pincel se deja como está: es relativa a la de la
363/// forma, no al mundo, y componerla dos veces desplazaría los gradientes.
364fn trasladar(orden: &Orden, t: Affine) -> Orden {
365    let mut o = orden.clone();
366    match &mut o {
367        Orden::Rellenar { xf, .. }
368        | Orden::Trazar { xf, .. }
369        | Orden::AbrirCapa { xf, .. }
370        | Orden::AbrirMascaraLuminancia { xf, .. }
371        | Orden::Imagen { xf, .. }
372        | Orden::RectDifuso { xf, .. }
373        | Orden::Glifos { xf, .. } => *xf = t * *xf,
374        Orden::CerrarCapa => {}
375    }
376    o
377}