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

⎇ dd126bad603bdf8af9019ca1eca4c05da0100da4 SDD.md Markdown · 366 siq'ikuna · 22.4 KB Ñawpa willay ch'uya

feat: llimphi standalone — framework UI soberano extraído del monorepo e65e9cc62 · Sergio ·

1# Llimphi — motor gráfico soberano
2
3> Llimphi (quechua: *color / brillo / pigmento*, en el sentido de "pintar la pantalla"). Tipo: **NATIVE GPU rendering suite**.
4
5> **Regla dura para apps:** nada de cómputo pesado síncrono en `App::update`/`init`/handlers — congela la UI ("Not Responding"). Ver [COMPUTO-FUERA-DEL-HILO-UI.md](COMPUTO-FUERA-DEL-HILO-UI.md) (patrón worker + checklist por app, prioridad urgente).
6
7> **¿Buscás cómo *usar* Llimphi?** Este SDD es el *porqué* (diseño, fases, roadmap). La referencia de *uso* — bucle Elm, DSL `View<Msg>`, catálogo de widgets/módulos, GPU directo — está en [MANUAL.md](MANUAL.md), verificada contra el código.
8
9## Tesis
10
11Soberanía total sobre el píxel. Renderizar las geometrías exactas del simulador cósmico (`cosmos`), el compositor (`mirada`), las apps de escritorio (`nahual`) y el visor (`pluma`) sin cajas negras de Apple/Google/navegadores. Reemplazo total de **GPUI** en la pila gioser.
12
13## Anatomía — 4 capas estrictas (S₀ → S₂)
14
15Cada capa hace **una sola cosa** con precisión matemática.
16
17```
18[ CUADRANTE III · 0x02 RUWAY ]
19
204. llimphi-ui      — Lógica de Interfaz (Árbol Monádico / DAG UI)
21   │                 (manejo de estado, eventos de teclado/ratón)
22   ▼
233. llimphi-layout  — Motor de Layout (Cálculo Espacial)
24   │                 (cajas, dimensiones, restricciones flex/grid)
25   ▼
262. llimphi-raster  — Rasterizador Vectorial (La Brocha Fina)
27   │                 (primitivas matemáticas → píxeles via Compute Shaders)
28   ▼
291. llimphi-hal     — Abstracción de Hardware (Puente al Silicio)
30   │                 (GPU o Framebuffer, sin importar el OS)
31   ▼
32[ HARDWARE · GPU / Pantalla ]
33```
34
35## Fases de forja
36
37### Fase 1 — Puente al Silicio (`llimphi-hal`)
38
39Aislar el motor del sistema operativo. Llimphi debe pintar tanto en una ventana Wayland controlada por `mirada` como en el framebuffer directo al arrancar `wawa`.
40
41- **Abstractor:** `wgpu` (impl Rust de WebGPU sobre Vulkan nativo). Control de memoria seguro, bajísima sobrecarga.
42- **Ventana:** `winit` para desarrollo en Linux. La arquitectura define un **trait `Surface`** abstracto: el día de mañana se desenchufa `winit` y se le pasa el puntero de memoria bruto del kernel `wawa`.
43- **Hito:** Compilar, iniciar Vulkan por debajo, limpiar la pantalla pintándola de un solo color gris plomo a 144 Hz.
44
45### Fase 2 — Brocha Matemática (`llimphi-raster`)
46
47Pintar curvas y grafos orbitales con precisión Δ < 10⁻⁹ rad sin destrozar la CPU. En lugar de rasterizar píxel por píxel, **delegar todo el cálculo vectorial a los Compute Shaders de la GPU**.
48
49- **Motor:** `vello`.
50- **Integración:** Conectar la textura de salida de `wgpu` como lienzo destino de `vello`.
51- **Ejecución:** Construir una `Scene` en `vello`. Pasarle primitivas geométricas puras (líneas, curvas de Bézier, texto).
52- **Hito:** Renderizar en pantalla el grafo de un nodo estático con anti-aliasing perfecto calculado íntegramente por la GPU.
53
54### Fase 3 — Física del Espacio (`llimphi-layout`)
55
56Posicionar dinámicamente paneles, texto y ventanas requiere resolver ecuaciones de restricciones espaciales. No escribir un sistema propio de márgenes/padding: es un sumidero infinito.
57
58- **Motor:** `taffy` (de la gente de Dioxus). Algoritmos Flexbox + CSS Grid en Rust puro.
59- **Flujo:** Antes de decirle a `llimphi-raster` dónde pintar, pasar el árbol de nodos a `taffy` para calcular las coordenadas `(x, y, width, height)` absolutas de toda la interfaz.
60- **Hito:** Paneles laterales y cajas que se redimensionan automáticamente, calculados en < 1 ms por frame.
61
62### Fase 4 — Árbol de Estado Monádico (`llimphi-ui`)
63
64El mayor problema de las interfaces (y por qué falló el paradigma OOP en esto) es el manejo del estado. Aquí se inyecta la cosmovisión estructural.
65
66- **Arquitectura:** Nada de mutabilidad compartida (`Rc<RefCell<...>>` disperso). Unidireccional estilo Elm o **DAG (Grafo Acíclico Dirigido)**: el estado de la aplicación es **inmutable** y cada evento (click, tecla) genera una **nueva versión** del estado.
67- **Bucle:**
68  1. El usuario hace click (Input).
69  2. El evento actualiza el Estado Global.
70  3. El Estado Global reconstruye el Árbol UI.
71  4. El Árbol pasa por `llimphi-layout` (Layout).
72  5. Las coordenadas resultantes generan primitivas para `llimphi-raster` (Scene).
73  6. `llimphi-hal` renderiza y hace el swap de la pantalla.
74
75## Veredicto arquitectónico
76
77No es una biblioteca genérica. Es un **motor de combate**. `wgpu + vello + taffy + DAG monádico` da un frontend capaz de competir en rendimiento con los mejores editores del mundo, diseñado como **traje a medida** para las topologías de gioser. Sin abstracciones de navegadores, sin cajas negras de Apple/Google.
78
79## Pila exacta (sin negociación)
80
81| Capa | Crate raíz | Deps externas |
82|---|---|---|
83| HAL | `llimphi-hal` | `wgpu`, `winit`, `raw-window-handle` |
84| Raster | `llimphi-raster` | `vello`, `vello_encoding`, `peniko` |
85| Text | `llimphi-text` | `parley` (shaping + fontique + swash, hereda vello via raster) |
86| Layout | `llimphi-layout` | `taffy` |
87| UI | `llimphi-ui` | `llimphi-{hal,raster,layout,text}` |
88
89## Migración GPUI → Llimphi
90
91Apps actualmente en GPUI que deben portarse:
92
93- `02_ruway/nahual/*` (todas las apps GPUI: shell, file-explorer, database-explorer, image-viewer, text-viewer + 8 libs + 12 widgets)
94- `02_ruway/mirada/mirada-launcher`, `mirada-portal`, `mirada-greeter`
95- `00_unanchay/pluma/pluma-editor-gpui`
96- `01_yachay/dominium/dominium-canvas-gpui`
97- `01_yachay/cosmos/cosmos-app` (canvas + panels GPUI)
98
99**Estrategia:** Las apps mantienen su lógica de dominio en sus `*-core` agnósticos. Solo se reemplaza la capa de presentación: en lugar de `use gpui::*`, pasan a usar `use llimphi_ui::*`.
100
101## Estado (2026-05-31)
102
103### Hecho
104- Las 5 capas del framework en producción: `llimphi-hal` (wgpu+winit), `llimphi-raster` (vello), `llimphi-text` (parley, ahora con vello directo y texto multicolor en una pasada), `llimphi-layout` (taffy, con `LayoutTree::clear()` para reuso entre frames), `llimphi-ui` (bucle Elm + runtime winit).
105- Split compositor/runtime: `llimphi-compositor` (winit-free: View tree, mount, paint/paint_gpu, hit-test) separado de `llimphi-ui` (runtime winit) → habilita un futuro runtime sobre el framebuffer de `wawa` sin winit.
106- GPUI extinto (2026-05-26): toda app gráfica de la suite corre sobre Llimphi.
107- Backend GPU directo (sin vello) completo y validado en hardware real (Iris Xe): `GpuPipelines` + `GpuBatch` + `View::gpu_paint_with`; ~11× vs vello a 1M puntos persistente, >140 fps.
108- Catálogo de ~44 widgets: incluye text-editor (split en `-core` agnóstico + `-lsp`), nodegraph, tiled/panes/splitter, tree, list, grid (virtualizada 2D), gallery, timeline (scrub clickeable), menubar/edit-menu/context-menu, clipboard del sistema, tabs, modal, toast, y la familia de controles (button/field/slider/switch/segmented/...).
109- 10 módulos compuestos: command-palette, diff-viewer, fif (find-in-files), file-picker, bookmarks, mini-map, shuma-term, symbol-outline, selector, plugin-host.
110- `llimphi-workspace` (chasis tipo tmux) + `llimphi-gallery` (showcase) + `llimphi-motion`/`llimphi-icons`/`llimphi-surface` auxiliares.
111
112### Pendiente
113- Runtime sobre framebuffer de `wawa` (`WawaFramebufferSurface`) reusando el compositor winit-free — habilitado por el split pero aún no escrito.
114- Backend GPU directo: sin MSAA/AA fino, sin texto, una sola `line_width` por flush; falta primer caller real denso (cosmos starfield) que mida una falla concreta antes de extender shaders.
115- Widgets `llimphi-widget-{transport, waveform}` aún por extraer (la nota de media los deja como futuro no bloqueante).
116- Investigación abierta: cuelgue/deadlock de apps Llimphi tras click/scroll (hipótesis `get_current_texture` Wayland FIFO) — pendiente reproducir+backtrace.
117
118## Estado — bitácora histórica
119
120- **2026-05-25:** SDD escrito. Esqueletos de los 4 crates creados.
121- **2026-05-25 (tarde):** Las 4 fases en código y compilando. Examples:
122  - `cargo run -p llimphi-hal --example clear_screen --release` — ventana gris plomo a refresh del display ✅ (verificado en hardware).
123  - `cargo run -p llimphi-raster --example render_node --release` — nodo con AA perfecto vía vello/wgpu.
124  - `cargo run -p llimphi-layout --example layout_panels --release` — sidebar + header/body/footer flex que se reorganiza al resize.
125  - `cargo run -p llimphi-ui --example counter --release` — bucle Elm completo: click hit-test → update → view → layout → raster → present.
126- **2026-05-25 (noche):** quinto crate `llimphi-text` (skrifa + vello). Bug de `max_storage_buffers_per_shader_stage` corregido (`Limits::default()` en vez de `downlevel`). `View::text()` permite poner texto centrado en cualquier nodo. Examples:
127  - `cargo run -p llimphi-text --example hello_text --release` — "Llimphi" + tagline sobre fondo negro.
128  - `counter` ahora muestra el número real (no barras) y los botones llevan label.
129- **2026-05-25 (cierre):** dos fixes de hardware + parley.
130  - **Storage write fix:** swapchain de muchos adapters Linux/Vulkan no acepta storage writes en Rgba8Unorm. Patrón nuevo: textura intermedia con `STORAGE_BINDING | TEXTURE_BINDING` donde pinta vello + `TextureBlitter` que la copia al swapchain en `Surface::present(frame, &hal)`. Cambio de API: `frame.present()` → `surface.present(frame, &hal)`.
131  - **Paint-order fix:** `mount_recursive` registraba en post-orden y el background del root tapaba a los hijos. Ahora pre-orden depth-first.
132  - **Parley:** llimphi-text reescrito sobre parley. API nueva: `Typesetter` (cachea FontContext + LayoutContext), `TextBlock { text, size_px, color, origin, max_width, alignment, line_height }`, `Alignment { Start, Center, End, Justify }`, `measure(&mut ts, &block)`. Bidi + ligatures + fallback CJK/emoji vía fontique. `hello_text` muestra título + párrafo justificado con script mixto Latin/Arabic/CJK.
133- **2026-05-25 (cierre+1):** teclado en `llimphi-ui`. `App` gana `fn on_key(model, &KeyEvent) -> Option<Msg>` con default `None`. Re-export `Key` y `NamedKey` de winit. Runtime mantiene `Modifiers` state vía `ModifiersChanged`. `TextSpec` gana `alignment` (default `Center`, los labels de botón siguen igual) + `View::text_aligned(...)`. Example nuevo `editor`: text field con char insertion, backspace, enter, tab→4-spaces, ctrl+L limpia.
134- **2026-05-26:** migración GPUI → Llimphi **completada**. GPUI queda extinto: toda app gráfica de la suite (pluma, mirada, cosmos, dominium, nahual, iniy, khipu, chasqui…) corre sobre Llimphi. No se agrega código nuevo sobre GPUI (ver regla dura §3 de `CLAUDE.md`).
135- **2026-05-31:** split de `llimphi-widget-text-editor` (4328 LOC) → núcleo agnóstico `llimphi-widget-text-editor-core` (buffer/cursor/ops/undo/bracket/find/diagnostics/clipboard/highlight, sin render: sólo `peniko::Color`) + widget Llimphi (state + view) que lo re-exporta. Núcleo reutilizable en TUI/web/headless. `LayoutTree::clear()` para reusar el árbol taffy entre frames (`llimphi-layout`).
136- **2026-05-31 (texto multicolor):** syntax highlighting en una sola pasada de shaping. `llimphi-text` gana `RunBrush` + `Typesetter::layout_runs` (color por rango de bytes vía `parley::RangedBuilder`/`StyleProperty::Brush`) + `draw_layout_runs`; `View::text_runs` lo expone. El editor pasó de un nodo (+ layout parley) por token a uno por línea.
137- **2026-05-31 (split compositor/runtime):** `llimphi-ui` (1943 LOC) partido para separar la composición declarativa del runtime winit:
138  - **`llimphi-compositor`** (nuevo, **winit-free**): el árbol `View<Msg>`, `mount` sobre taffy, `paint`/`paint_gpu` a `vello::Scene` y el hit-test. Depende sólo de `llimphi-layout` + `llimphi-text` + `vello` + `wgpu` (este último sólo por la firma de `GpuPaintFn`; `wgpu` no es windowing). **No depende de `llimphi-hal`.**
139  - **`llimphi-ui`**: queda como el runtime winit (`App`/`Handle`/`run`/event loop/`KeyEvent`) y re-exporta el compositor entero → los consumidores siguen usando `llimphi_ui::View` etc. sin cambios.
140  - Prerrequisito habilitado: `llimphi-text` ahora depende de `vello` directo (no de `llimphi-raster`), así que la pila de render (`compositor`→`text`/`vello`) es winit-free. Eso abre la puerta a un runtime sobre el framebuffer del kernel `wawa` (`WawaFramebufferSurface`) que reuse el mismo compositor sin arrastrar winit. `Renderer` (lo único que necesita `llimphi-hal`) se queda en `llimphi-raster`, consumido por `llimphi-ui`.
141
142## Roadmap — GPU directo wgpu (sin vello)
143
144### Por qué
145
146`llimphi-raster` traduce hoy todo a `vello::Scene` (BezPath / kurbo /
147peniko) y vello rasteriza vía compute shaders. Para 99 % de la suite
148sobra: pluma editor, shuma shell, mirada compositor, nahual, iniy, khipu,
149chasqui explorer, etc. pintan decenas a centenas de primitivos por frame.
150
151El techo aparece cuando una app necesita rendir **>1 M primitivos por
152frame**. En ese régimen el overhead de construir `BezPath`, ensamblar
153buffers para los shaders internos de vello y hacer una pasada compute
154por cada batch domina sobre el tiempo de raster real. Casos concretos
155en gioser:
156
157| App | Carga potencial | Trigger probable |
158|---|---|---|
159| **cosmos** | Catálogo Gaia DR3, mapas de cielo enteros | Starfield denso o sky-survey overlay |
160| **tinkuy** | Particle engine N→∞ por diseño | Sim con > 10⁵ partículas |
161| **nakui** | 100 K filas × 26 cols = 2.6 M celdas potencialmente visibles | Viewport con dataset grande |
162| **dominium** | Mean-field con N agentes | Cuando se pase de 10³ a 10⁵ |
163| **pineal** | Sus painters ya producen `Vec<f32>` interleaved (principio P1) — son los primeros listos para consumir el backend | Cualquiera de los anteriores que use pineal-* |
164
165El techo es **horizontal**. Resolverlo en cualquier app individual sería
166duplicación; el lugar es el motor.
167
168### Qué es
169
170Un backend alternativo en `llimphi-raster` que **salta vello** y sube
171los slices de coordenadas directamente a vertex buffers `wgpu`, dispara
172shaders WGSL chiquitos y emite una draw call por batch.
173
174```
175hoy:      painter → vello::Scene → BezPath → vello → wgpu → GPU
176con esto: painter → GpuBatch     → vertex buffer    → wgpu → GPU
177```
178
179El trait que ven las apps (`Canvas` para pineal, `View::paint_with` para
180llimphi-ui) **no cambia**. Cambia el implementador por debajo cuando se
181elige "modo GPU directo".
182
183### Trade-offs vs vello
184
185| | Vello (hoy) | GPU directo |
186|---|---|---|
187| AA | Analítico, perfecto | MSAA hardware o supersample en shader |
188| Curvas suaves | Bezier nativo | Hay que teselar primero |
189| Texto | Sí, vello + parley | No — usar vello para text aunque coexista |
190| Throughput primitivos | Bueno hasta ~100 K | Apto para 1–10 M |
191| Costo de mantener | Cero (vello lo mantiene Linebender) | Shaders WGSL + pipelines propias |
192
193Decisión: los dos backends **coexisten**. La app elige por hint
194(`View::gpu_paint_with` para denso, `paint_with` para todo lo demás).
195
196### Plan de tareas
197
198**Fase 0 — Spike de medición (½ día). ✓ HECHO (2026-05-28).**
199Benchmark sintético: pintar 100 K, 500 K y 1 M puntos con `SceneCanvas`
200actual vs un mock GPU-directo (vertex buffer + shader trivial). Si el
201factor no es ≥ 5× en el rango de 500 K, abortar — vello ya es
202suficiente y no vale el costo de mantenimiento. Métrica de éxito: 60 fps
203con 1 M puntos en GPU mid (Radeon 5500M, Intel Iris Xe).
204
205Implementado en `llimphi-raster/examples/spike_gpu_directo.rs`. Cubre
206ambos backends contra una textura `Rgba8Unorm` 1024×1024 headless,
207warmup 5 + 15 frames medidos, bloquea hasta GPU idle (`Maintain::Wait`)
208para que los `ms` reportados sean tiempo real CPU+GPU.
209
210El binario `llimphi-gpu-bench` (en su propio crate) reporta info del
211adapter wgpu + corre dos escenarios distintos: **rebuild por frame**
212(LCG + `write_buffer` de 12-160 MB por frame, peor caso) y
213**persistente** (buffer/Scene preparados UNA vez, bucle medido sólo
214emite la draw call — caso real de cosmos/tinkuy/nakui).
215
216**Resultados — Intel Iris Xe (TGL GT2), Mesa 26.1.1, Vulkan, 2026-05-28:**
217
218Rebuild por frame:
219
220| N | vello ms | directo ms | factor |
221|---:|---:|---:|---:|
222| 25K  | 7.3  | 1.2  | **6.05×** |
223| 50K  | 12.9 | 1.4  | **8.94×** |
224| 100K | 21.7 | 3.2  | **6.67×** |
225| 200K | 26.1 | 6.1  | 4.30× |
226| 500K | 94.4 | 18.0 | **5.25×** |
227| 1M   | 202.4 | 49.0 | 4.13× |
228
229Persistente (datos fijos, sólo redraw):
230
231| N | vello ms | directo ms | factor | fps directo |
232|---:|---:|---:|---:|---:|
233| 100K | 18.6  | 0.8  | **22.55×** | 1210 |
234| 500K | 34.1  | 3.4  | **9.97×**  | 293 |
235| 1M   | 83.1  | 7.1  | **11.76×** | 141 |
236| 2M   | 101.7 | 16.0 | **6.37×**  | 63 |
237| 5M   | crash | 41.8 | —          | 24 |
238| 10M  | crash | 79.7 | —          | 13 |
239
240Veredictos contra el criterio del SDD:
241
242- **Factor ≥5× a 500K**: ✓ PASA. Rebuild 5.25×, persistente 9.97×.
243- **≥60 fps @ 1M**: ✓ PASA en persistente (141 fps); falla en rebuild
244  (22 fps) — pero rebuild no es el use case real.
245- **Techo de vello**: ~2 M paths en GPU mid. Más alto que mi hipótesis
246  inicial (que era 200–300 K, contaminada por llvmpipe), pero existe.
247  El path directo escala lineal a >10 M sin crashes.
248
249Conclusión: el GPU directo cumple su propósito. La diferencia entre
250rebuild y persistente (5–20×) confirma que el patrón correcto es
251"datos cambian → vello, datos estáticos → GPU directo persistente".
252
253**Fase 1 — Hook en `llimphi-ui` (1–2 días).**
254Hoy `View::paint_with(F)` da
255`F: Fn(&mut vello::Scene, &mut Typesetter, PaintRect)`. Agregar:
256
257```rust
258View::gpu_paint_with(F)
259  where F: Fn(&wgpu::Device, &wgpu::Queue,
260              &mut wgpu::CommandEncoder,
261              &wgpu::TextureView, PaintRect)
262```
263
264El runtime de llimphi-ui ya tiene `Device`/`Queue` para vello; sólo hay
265que exponer el `CommandEncoder` y `TextureView` del frame durante el
266mount/paint. Compatibilidad: ambos hooks coexisten en el mismo View
267tree; el orden de pintura sigue siendo pre-orden DFS.
268
269**Fase 2 — Pipelines y shaders en `llimphi-raster` (3–5 días).**
270Tres pipelines WGSL precompiladas y cacheadas:
271
272- `lines_pipeline` — line list, anchura uniforme (expandida a tris en
273  vertex shader como hace pineal-export::png).
274- `tris_pipeline` — triangle list con per-vertex color.
275- `rects_pipeline` — instanced quad con per-instance `[x, y, w, h, color]`.
276
277Vertex format común: `[x: f32, y: f32, rgba: u32]`. Sin texturas; eso
278queda para una fase posterior si aparece demanda.
279
280**Fase 3 — `GpuBatch` accumulator (2–3 días).**
281Estructura que las apps usan dentro del callback:
282
283```rust
284let mut batch = GpuBatch::new(device);
285batch.add_lines(&coords, color);
286batch.add_tris(&coords, &colors);
287batch.add_rect(rect, color);
288batch.flush(encoder, view);  // 1 draw call por pipeline usada
289```
290
291Grow strategy: vertex buffer dobla capacidad cada vez que se queda
292chico. Sin copy back — vive del frame, se reusa el siguiente.
293
294**Fase 4 — `GpuSceneCanvas` en pineal-render (1 día).**
295Wrapper que implementa el trait `Canvas` de pineal usando `GpuBatch`
296por debajo. Cero cambios en los painters. Permite usar el catálogo
297entero de pineal en modo denso simplemente eligiendo el otro
298constructor de Canvas dentro del `gpu_paint_with`.
299
300**Fase 5 — Primer caller real (cosmos starfield, 2–3 días).**
301Adaptar `cosmos-canvas-llimphi` para subir todas las estrellas del
302viewport en una draw call usando `gpu_paint_with`. Métrica: dataset
303HYG (~120 K estrellas brillantes) renderizadas a 144 fps en GPU mid.
304
305**Fase 6 — Tests + demo + SDD (1 día). ✓ HECHO (2026-05-28).**
306- `llimphi-raster/examples/gpu_million_points.rs`: usa `GpuPipelines` +
307  `GpuBatch` puros (sin app, sin runtime Elm) para pintar N rects
308  sintéticos. Validación headless del HAL + bench de referencia
309  post-implementación. Smoke en `tests/gpu_batch_smoke.rs`.
310- Tabla "cuándo elegir" → abajo.
311- Pineal SDD §4 actualizado con `GpuSceneCanvas` en producción.
312
313### ¿Cuándo elegir vello vs GPU directo?
314
315| Pregunta | Vello (`paint_with`) | GPU directo (`gpu_paint_with`) |
316|---|---|---|
317| ¿Cuántos primitivos por frame? | < ~500 K (rebuild) o < ~2 M (Scene reusada) | 100 K – 10 M+ |
318| ¿Los datos cambian cada frame? | Sí — vello rebuild es barato hasta 500 K | Posible pero con coste de `write_buffer`; ideal estático |
319| ¿Curvas Bezier nativas? | Sí | No (teselar antes) |
320| ¿Texto? | Sí | No — usar vello hermano u overlay |
321| ¿AA fino requerido? | Sí (analítico) | No (sin MSAA todavía) |
322| ¿Múltiples grosores de stroke? | Sí | Una sola `line_width` por flush |
323| ¿Anti-fluctuación de pixel? | Sí | Subpixel jitter visible |
324| Ejemplos de uso | pluma editor, shuma shell, mirada, nahual, iniy, khipu, chasqui explorer, dominium UI | cosmos starfield denso, tinkuy particles, nakui viewport, pineal denso |
325
326Default razonable: **`paint_with`** salvo que el caller ya midió que el
327volumen lo justifica. El costo de mantener un pipeline + WGSL propios
328es alto comparado con seguir usando vello.
329
330Patrón "buffer persistente": para el use case denso real (catálogo
331fijo, particles iniciales, dataset estático), construir el
332`wgpu::Buffer` y `BindGroup` UNA vez con `GpuPipelines::{rects, tris,
333lines, bind_layout}` expuestos y emitir el draw call manualmente
334desde el `gpu_paint_with` reusando esos recursos. Eso da factores
335~11× vs vello a 1M en GPU mid (medido Iris Xe), y >140 fps.
336`GpuBatch` queda para datos transitorios (UI dinámica densa).
337
338Convivencia: una misma `View` puede registrar AMBOS hooks. El runtime
339pinta vello primero (toda la Scene), luego ejecuta los GPU painters
340en orden DFS. Para texto encima de un render GPU denso, se usa
341`App::view_overlay` (segunda Scene vello sobre el main).
342
343**Estimado total: 10–15 días de trabajo concentrado.**
344**Trabajo real (1 día, 2026-05-28):** todas las fases completas, sólo
345falta validar el criterio formal (≥5× a 500K, 60 fps @ 1M) en GPU mid
346real — el bench corrió en llvmpipe.
347
348### Trigger
349
350No empezar hasta tener un caller real que mida una falla concreta.
351El candidato natural es cosmos (starfield Gaia o sky-survey overlay).
352Hasta entonces, el item queda acá en este SDD como decisión arquitectónica
353tomada — todas las apps saben que el techo existe y que la salida
354está diseñada.
355
356### No-objetivos explícitos
357
358- **No** reemplazar vello. Coexisten — vello para vector/text/AA fino,
359  GPU directo para volumen.
360- **No** hacer un layer de abstracción tipo Skia. El trait `Canvas` de
361  pineal y el `paint_with` de llimphi son la abstracción; no se agrega
362  más arriba.
363- **No** soportar texto en el backend GPU directo. Texto siempre por
364  vello+parley; si una vista mezcla millones de puntos + labels, hace
365  `gpu_paint_with` para los puntos y un `paint_with` superpuesto para
366  los labels.

SDD.md

Llimphi — motor gráfico soberano

Llimphi (quechua: color / brillo / pigmento, en el sentido de "pintar la pantalla"). Tipo: NATIVE GPU rendering suite.

Regla dura para apps: nada de cómputo pesado síncrono en App::update/init/handlers — congela la UI ("Not Responding"). Ver COMPUTO-FUERA-DEL-HILO-UI.md (patrón worker + checklist por app, prioridad urgente).

¿Buscás cómo usar Llimphi? Este SDD es el porqué (diseño, fases, roadmap). La referencia de uso — bucle Elm, DSL View<Msg>, catálogo de widgets/módulos, GPU directo — está en MANUAL.md, verificada contra el código.

Tesis

Soberanía total sobre el píxel. Renderizar las geometrías exactas del simulador cósmico (cosmos), el compositor (mirada), las apps de escritorio (nahual) y el visor (pluma) sin cajas negras de Apple/Google/navegadores. Reemplazo total de GPUI en la pila gioser.

Anatomía — 4 capas estrictas (S₀ → S₂)

Cada capa hace una sola cosa con precisión matemática.

[ CUADRANTE III · 0x02 RUWAY ]

4. llimphi-ui      — Lógica de Interfaz (Árbol Monádico / DAG UI)
   │                 (manejo de estado, eventos de teclado/ratón)
   ▼
3. llimphi-layout  — Motor de Layout (Cálculo Espacial)
   │                 (cajas, dimensiones, restricciones flex/grid)
   ▼
2. llimphi-raster  — Rasterizador Vectorial (La Brocha Fina)
   │                 (primitivas matemáticas → píxeles via Compute Shaders)
   ▼
1. llimphi-hal     — Abstracción de Hardware (Puente al Silicio)
   │                 (GPU o Framebuffer, sin importar el OS)
   ▼
[ HARDWARE · GPU / Pantalla ]

Fases de forja

Fase 1 — Puente al Silicio (llimphi-hal)

Aislar el motor del sistema operativo. Llimphi debe pintar tanto en una ventana Wayland controlada por mirada como en el framebuffer directo al arrancar wawa.

Fase 2 — Brocha Matemática (llimphi-raster)

Pintar curvas y grafos orbitales con precisión Δ < 10⁻⁹ rad sin destrozar la CPU. En lugar de rasterizar píxel por píxel, delegar todo el cálculo vectorial a los Compute Shaders de la GPU.

Fase 3 — Física del Espacio (llimphi-layout)

Posicionar dinámicamente paneles, texto y ventanas requiere resolver ecuaciones de restricciones espaciales. No escribir un sistema propio de márgenes/padding: es un sumidero infinito.

Fase 4 — Árbol de Estado Monádico (llimphi-ui)

El mayor problema de las interfaces (y por qué falló el paradigma OOP en esto) es el manejo del estado. Aquí se inyecta la cosmovisión estructural.

Veredicto arquitectónico

No es una biblioteca genérica. Es un motor de combate. wgpu + vello + taffy + DAG monádico da un frontend capaz de competir en rendimiento con los mejores editores del mundo, diseñado como traje a medida para las topologías de gioser. Sin abstracciones de navegadores, sin cajas negras de Apple/Google.

Pila exacta (sin negociación)

CapaCrate raízDeps externas
HALllimphi-halwgpu, winit, raw-window-handle
Rasterllimphi-rastervello, vello_encoding, peniko
Textllimphi-textparley (shaping + fontique + swash, hereda vello via raster)
Layoutllimphi-layouttaffy
UIllimphi-uillimphi-{hal,raster,layout,text}

Migración GPUI → Llimphi

Apps actualmente en GPUI que deben portarse:

Estrategia: Las apps mantienen su lógica de dominio en sus *-core agnósticos. Solo se reemplaza la capa de presentación: en lugar de use gpui::*, pasan a usar use llimphi_ui::*.

Estado (2026-05-31)

Hecho

Pendiente

Estado — bitácora histórica

Roadmap — GPU directo wgpu (sin vello)

Por qué

llimphi-raster traduce hoy todo a vello::Scene (BezPath / kurbo / peniko) y vello rasteriza vía compute shaders. Para 99 % de la suite sobra: pluma editor, shuma shell, mirada compositor, nahual, iniy, khipu, chasqui explorer, etc. pintan decenas a centenas de primitivos por frame.

El techo aparece cuando una app necesita rendir >1 M primitivos por frame. En ese régimen el overhead de construir BezPath, ensamblar buffers para los shaders internos de vello y hacer una pasada compute por cada batch domina sobre el tiempo de raster real. Casos concretos en gioser:

AppCarga potencialTrigger probable
cosmosCatálogo Gaia DR3, mapas de cielo enterosStarfield denso o sky-survey overlay
tinkuyParticle engine N→∞ por diseñoSim con > 10⁵ partículas
nakui100 K filas × 26 cols = 2.6 M celdas potencialmente visiblesViewport con dataset grande
dominiumMean-field con N agentesCuando se pase de 10³ a 10⁵
pinealSus painters ya producen Vec<f32> interleaved (principio P1) — son los primeros listos para consumir el backendCualquiera de los anteriores que use pineal-*

El techo es horizontal. Resolverlo en cualquier app individual sería duplicación; el lugar es el motor.

Qué es

Un backend alternativo en llimphi-raster que salta vello y sube los slices de coordenadas directamente a vertex buffers wgpu, dispara shaders WGSL chiquitos y emite una draw call por batch.

hoy:      painter → vello::Scene → BezPath → vello → wgpu → GPU
con esto: painter → GpuBatch     → vertex buffer    → wgpu → GPU

El trait que ven las apps (Canvas para pineal, View::paint_with para llimphi-ui) no cambia. Cambia el implementador por debajo cuando se elige "modo GPU directo".

Trade-offs vs vello

Vello (hoy)GPU directo
AAAnalítico, perfectoMSAA hardware o supersample en shader
Curvas suavesBezier nativoHay que teselar primero
TextoSí, vello + parleyNo — usar vello para text aunque coexista
Throughput primitivosBueno hasta ~100 KApto para 1–10 M
Costo de mantenerCero (vello lo mantiene Linebender)Shaders WGSL + pipelines propias

Decisión: los dos backends coexisten. La app elige por hint (View::gpu_paint_with para denso, paint_with para todo lo demás).

Plan de tareas

Fase 0 — Spike de medición (½ día). ✓ HECHO (2026-05-28). Benchmark sintético: pintar 100 K, 500 K y 1 M puntos con SceneCanvas actual vs un mock GPU-directo (vertex buffer + shader trivial). Si el factor no es ≥ 5× en el rango de 500 K, abortar — vello ya es suficiente y no vale el costo de mantenimiento. Métrica de éxito: 60 fps con 1 M puntos en GPU mid (Radeon 5500M, Intel Iris Xe).

Implementado en llimphi-raster/examples/spike_gpu_directo.rs. Cubre ambos backends contra una textura Rgba8Unorm 1024×1024 headless, warmup 5 + 15 frames medidos, bloquea hasta GPU idle (Maintain::Wait) para que los ms reportados sean tiempo real CPU+GPU.

El binario llimphi-gpu-bench (en su propio crate) reporta info del adapter wgpu + corre dos escenarios distintos: rebuild por frame (LCG + write_buffer de 12-160 MB por frame, peor caso) y persistente (buffer/Scene preparados UNA vez, bucle medido sólo emite la draw call — caso real de cosmos/tinkuy/nakui).

Resultados — Intel Iris Xe (TGL GT2), Mesa 26.1.1, Vulkan, 2026-05-28:

Rebuild por frame:

Nvello msdirecto msfactor
25K7.31.26.05×
50K12.91.48.94×
100K21.73.26.67×
200K26.16.14.30×
500K94.418.05.25×
1M202.449.04.13×

Persistente (datos fijos, sólo redraw):

Nvello msdirecto msfactorfps directo
100K18.60.822.55×1210
500K34.13.49.97×293
1M83.17.111.76×141
2M101.716.06.37×63
5Mcrash41.8—24
10Mcrash79.7—13

Veredictos contra el criterio del SDD:

Conclusión: el GPU directo cumple su propósito. La diferencia entre rebuild y persistente (5–20×) confirma que el patrón correcto es "datos cambian → vello, datos estáticos → GPU directo persistente".

Fase 1 — Hook en llimphi-ui (1–2 días). Hoy View::paint_with(F) da F: Fn(&mut vello::Scene, &mut Typesetter, PaintRect). Agregar:

View::gpu_paint_with(F)
  where F: Fn(&wgpu::Device, &wgpu::Queue,
              &mut wgpu::CommandEncoder,
              &wgpu::TextureView, PaintRect)

El runtime de llimphi-ui ya tiene Device/Queue para vello; sólo hay que exponer el CommandEncoder y TextureView del frame durante el mount/paint. Compatibilidad: ambos hooks coexisten en el mismo View tree; el orden de pintura sigue siendo pre-orden DFS.

Fase 2 — Pipelines y shaders en llimphi-raster (3–5 días). Tres pipelines WGSL precompiladas y cacheadas:

Vertex format común: [x: f32, y: f32, rgba: u32]. Sin texturas; eso queda para una fase posterior si aparece demanda.

Fase 3 — GpuBatch accumulator (2–3 días). Estructura que las apps usan dentro del callback:

let mut batch = GpuBatch::new(device);
batch.add_lines(&coords, color);
batch.add_tris(&coords, &colors);
batch.add_rect(rect, color);
batch.flush(encoder, view);  // 1 draw call por pipeline usada

Grow strategy: vertex buffer dobla capacidad cada vez que se queda chico. Sin copy back — vive del frame, se reusa el siguiente.

Fase 4 — GpuSceneCanvas en pineal-render (1 día). Wrapper que implementa el trait Canvas de pineal usando GpuBatch por debajo. Cero cambios en los painters. Permite usar el catálogo entero de pineal en modo denso simplemente eligiendo el otro constructor de Canvas dentro del gpu_paint_with.

Fase 5 — Primer caller real (cosmos starfield, 2–3 días). Adaptar cosmos-canvas-llimphi para subir todas las estrellas del viewport en una draw call usando gpu_paint_with. Métrica: dataset HYG (~120 K estrellas brillantes) renderizadas a 144 fps en GPU mid.

Fase 6 — Tests + demo + SDD (1 día). ✓ HECHO (2026-05-28).

¿Cuándo elegir vello vs GPU directo?

PreguntaVello (paint_with)GPU directo (gpu_paint_with)
¿Cuántos primitivos por frame?< ~500 K (rebuild) o < ~2 M (Scene reusada)100 K – 10 M+
¿Los datos cambian cada frame?Sí — vello rebuild es barato hasta 500 KPosible pero con coste de write_buffer; ideal estático
¿Curvas Bezier nativas?SíNo (teselar antes)
¿Texto?SíNo — usar vello hermano u overlay
¿AA fino requerido?Sí (analítico)No (sin MSAA todavía)
¿Múltiples grosores de stroke?SíUna sola line_width por flush
¿Anti-fluctuación de pixel?SíSubpixel jitter visible
Ejemplos de usopluma editor, shuma shell, mirada, nahual, iniy, khipu, chasqui explorer, dominium UIcosmos starfield denso, tinkuy particles, nakui viewport, pineal denso

Default razonable: paint_with salvo que el caller ya midió que el volumen lo justifica. El costo de mantener un pipeline + WGSL propios es alto comparado con seguir usando vello.

Patrón "buffer persistente": para el use case denso real (catálogo fijo, particles iniciales, dataset estático), construir el wgpu::Buffer y BindGroup UNA vez con GpuPipelines::{rects, tris, lines, bind_layout} expuestos y emitir el draw call manualmente desde el gpu_paint_with reusando esos recursos. Eso da factores ~11× vs vello a 1M en GPU mid (medido Iris Xe), y >140 fps. GpuBatch queda para datos transitorios (UI dinámica densa).

Convivencia: una misma View puede registrar AMBOS hooks. El runtime pinta vello primero (toda la Scene), luego ejecuta los GPU painters en orden DFS. Para texto encima de un render GPU denso, se usa App::view_overlay (segunda Scene vello sobre el main).

Estimado total: 10–15 días de trabajo concentrado. Trabajo real (1 día, 2026-05-28): todas las fases completas, sólo falta validar el criterio formal (≥5× a 500K, 60 fps @ 1M) en GPU mid real — el bench corrió en llvmpipe.

Trigger

No empezar hasta tener un caller real que mida una falla concreta. El candidato natural es cosmos (starfield Gaia o sky-survey overlay). Hasta entonces, el item queda acá en este SDD como decisión arquitectónica tomada — todas las apps saben que el techo existe y que la salida está diseñada.

No-objetivos explícitos