feat: llimphi standalone — framework UI soberano extraído del monorepo
Motor gráfico Llimphi como workspace independiente: bucle Elm (input→update→view→layout→raster→present) sobre wgpu+vello+taffy+parley. Núcleo (hal/raster/layout/text/ui/theme/surface/motion/icons) + ~40 widgets + módulos, sin dependencias al resto del monorepo. cargo check --workspace pasa (64 crates). Puerta de entrada: cargo run -p llimphi-ui --example counter. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,366 @@
|
||||
# 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](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](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`.
|
||||
|
||||
- **Abstractor:** `wgpu` (impl Rust de WebGPU sobre Vulkan nativo). Control de memoria seguro, bajísima sobrecarga.
|
||||
- **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`.
|
||||
- **Hito:** Compilar, iniciar Vulkan por debajo, limpiar la pantalla pintándola de un solo color gris plomo a 144 Hz.
|
||||
|
||||
### 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**.
|
||||
|
||||
- **Motor:** `vello`.
|
||||
- **Integración:** Conectar la textura de salida de `wgpu` como lienzo destino de `vello`.
|
||||
- **Ejecución:** Construir una `Scene` en `vello`. Pasarle primitivas geométricas puras (líneas, curvas de Bézier, texto).
|
||||
- **Hito:** Renderizar en pantalla el grafo de un nodo estático con anti-aliasing perfecto calculado íntegramente por 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.
|
||||
|
||||
- **Motor:** `taffy` (de la gente de Dioxus). Algoritmos Flexbox + CSS Grid en Rust puro.
|
||||
- **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.
|
||||
- **Hito:** Paneles laterales y cajas que se redimensionan automáticamente, calculados en < 1 ms por frame.
|
||||
|
||||
### 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.
|
||||
|
||||
- **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.
|
||||
- **Bucle:**
|
||||
1. El usuario hace click (Input).
|
||||
2. El evento actualiza el Estado Global.
|
||||
3. El Estado Global reconstruye el Árbol UI.
|
||||
4. El Árbol pasa por `llimphi-layout` (Layout).
|
||||
5. Las coordenadas resultantes generan primitivas para `llimphi-raster` (Scene).
|
||||
6. `llimphi-hal` renderiza y hace el swap de la pantalla.
|
||||
|
||||
## 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)
|
||||
|
||||
| Capa | Crate raíz | Deps externas |
|
||||
|---|---|---|
|
||||
| HAL | `llimphi-hal` | `wgpu`, `winit`, `raw-window-handle` |
|
||||
| Raster | `llimphi-raster` | `vello`, `vello_encoding`, `peniko` |
|
||||
| Text | `llimphi-text` | `parley` (shaping + fontique + swash, hereda vello via raster) |
|
||||
| Layout | `llimphi-layout` | `taffy` |
|
||||
| UI | `llimphi-ui` | `llimphi-{hal,raster,layout,text}` |
|
||||
|
||||
## Migración GPUI → Llimphi
|
||||
|
||||
Apps actualmente en GPUI que deben portarse:
|
||||
|
||||
- `02_ruway/nahual/*` (todas las apps GPUI: shell, file-explorer, database-explorer, image-viewer, text-viewer + 8 libs + 12 widgets)
|
||||
- `02_ruway/mirada/mirada-launcher`, `mirada-portal`, `mirada-greeter`
|
||||
- `00_unanchay/pluma/pluma-editor-gpui`
|
||||
- `01_yachay/dominium/dominium-canvas-gpui`
|
||||
- `01_yachay/cosmos/cosmos-app` (canvas + panels GPUI)
|
||||
|
||||
**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
|
||||
- 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).
|
||||
- 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.
|
||||
- GPUI extinto (2026-05-26): toda app gráfica de la suite corre sobre Llimphi.
|
||||
- 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.
|
||||
- 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/...).
|
||||
- 10 módulos compuestos: command-palette, diff-viewer, fif (find-in-files), file-picker, bookmarks, mini-map, shuma-term, symbol-outline, selector, plugin-host.
|
||||
- `llimphi-workspace` (chasis tipo tmux) + `llimphi-gallery` (showcase) + `llimphi-motion`/`llimphi-icons`/`llimphi-surface` auxiliares.
|
||||
|
||||
### Pendiente
|
||||
- Runtime sobre framebuffer de `wawa` (`WawaFramebufferSurface`) reusando el compositor winit-free — habilitado por el split pero aún no escrito.
|
||||
- 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.
|
||||
- Widgets `llimphi-widget-{transport, waveform}` aún por extraer (la nota de media los deja como futuro no bloqueante).
|
||||
- Investigación abierta: cuelgue/deadlock de apps Llimphi tras click/scroll (hipótesis `get_current_texture` Wayland FIFO) — pendiente reproducir+backtrace.
|
||||
|
||||
## Estado — bitácora histórica
|
||||
|
||||
- **2026-05-25:** SDD escrito. Esqueletos de los 4 crates creados.
|
||||
- **2026-05-25 (tarde):** Las 4 fases en código y compilando. Examples:
|
||||
- `cargo run -p llimphi-hal --example clear_screen --release` — ventana gris plomo a refresh del display ✅ (verificado en hardware).
|
||||
- `cargo run -p llimphi-raster --example render_node --release` — nodo con AA perfecto vía vello/wgpu.
|
||||
- `cargo run -p llimphi-layout --example layout_panels --release` — sidebar + header/body/footer flex que se reorganiza al resize.
|
||||
- `cargo run -p llimphi-ui --example counter --release` — bucle Elm completo: click hit-test → update → view → layout → raster → present.
|
||||
- **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:
|
||||
- `cargo run -p llimphi-text --example hello_text --release` — "Llimphi" + tagline sobre fondo negro.
|
||||
- `counter` ahora muestra el número real (no barras) y los botones llevan label.
|
||||
- **2026-05-25 (cierre):** dos fixes de hardware + parley.
|
||||
- **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)`.
|
||||
- **Paint-order fix:** `mount_recursive` registraba en post-orden y el background del root tapaba a los hijos. Ahora pre-orden depth-first.
|
||||
- **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.
|
||||
- **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.
|
||||
- **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`).
|
||||
- **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`).
|
||||
- **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.
|
||||
- **2026-05-31 (split compositor/runtime):** `llimphi-ui` (1943 LOC) partido para separar la composición declarativa del runtime winit:
|
||||
- **`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`.**
|
||||
- **`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.
|
||||
- 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`.
|
||||
|
||||
## 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:
|
||||
|
||||
| App | Carga potencial | Trigger probable |
|
||||
|---|---|---|
|
||||
| **cosmos** | Catálogo Gaia DR3, mapas de cielo enteros | Starfield denso o sky-survey overlay |
|
||||
| **tinkuy** | Particle engine N→∞ por diseño | Sim con > 10⁵ partículas |
|
||||
| **nakui** | 100 K filas × 26 cols = 2.6 M celdas potencialmente visibles | Viewport con dataset grande |
|
||||
| **dominium** | Mean-field con N agentes | Cuando se pase de 10³ a 10⁵ |
|
||||
| **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-* |
|
||||
|
||||
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 |
|
||||
|---|---|---|
|
||||
| AA | Analítico, perfecto | MSAA hardware o supersample en shader |
|
||||
| Curvas suaves | Bezier nativo | Hay que teselar primero |
|
||||
| Texto | Sí, vello + parley | No — usar vello para text aunque coexista |
|
||||
| Throughput primitivos | Bueno hasta ~100 K | Apto para 1–10 M |
|
||||
| Costo de mantener | Cero (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:
|
||||
|
||||
| N | vello ms | directo ms | factor |
|
||||
|---:|---:|---:|---:|
|
||||
| 25K | 7.3 | 1.2 | **6.05×** |
|
||||
| 50K | 12.9 | 1.4 | **8.94×** |
|
||||
| 100K | 21.7 | 3.2 | **6.67×** |
|
||||
| 200K | 26.1 | 6.1 | 4.30× |
|
||||
| 500K | 94.4 | 18.0 | **5.25×** |
|
||||
| 1M | 202.4 | 49.0 | 4.13× |
|
||||
|
||||
Persistente (datos fijos, sólo redraw):
|
||||
|
||||
| N | vello ms | directo ms | factor | fps directo |
|
||||
|---:|---:|---:|---:|---:|
|
||||
| 100K | 18.6 | 0.8 | **22.55×** | 1210 |
|
||||
| 500K | 34.1 | 3.4 | **9.97×** | 293 |
|
||||
| 1M | 83.1 | 7.1 | **11.76×** | 141 |
|
||||
| 2M | 101.7 | 16.0 | **6.37×** | 63 |
|
||||
| 5M | crash | 41.8 | — | 24 |
|
||||
| 10M | crash | 79.7 | — | 13 |
|
||||
|
||||
Veredictos contra el criterio del SDD:
|
||||
|
||||
- **Factor ≥5× a 500K**: ✓ PASA. Rebuild 5.25×, persistente 9.97×.
|
||||
- **≥60 fps @ 1M**: ✓ PASA en persistente (141 fps); falla en rebuild
|
||||
(22 fps) — pero rebuild no es el use case real.
|
||||
- **Techo de vello**: ~2 M paths en GPU mid. Más alto que mi hipótesis
|
||||
inicial (que era 200–300 K, contaminada por llvmpipe), pero existe.
|
||||
El path directo escala lineal a >10 M sin crashes.
|
||||
|
||||
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:
|
||||
|
||||
```rust
|
||||
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:
|
||||
|
||||
- `lines_pipeline` — line list, anchura uniforme (expandida a tris en
|
||||
vertex shader como hace pineal-export::png).
|
||||
- `tris_pipeline` — triangle list con per-vertex color.
|
||||
- `rects_pipeline` — instanced quad con per-instance `[x, y, w, h, color]`.
|
||||
|
||||
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:
|
||||
|
||||
```rust
|
||||
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).**
|
||||
- `llimphi-raster/examples/gpu_million_points.rs`: usa `GpuPipelines` +
|
||||
`GpuBatch` puros (sin app, sin runtime Elm) para pintar N rects
|
||||
sintéticos. Validación headless del HAL + bench de referencia
|
||||
post-implementación. Smoke en `tests/gpu_batch_smoke.rs`.
|
||||
- Tabla "cuándo elegir" → abajo.
|
||||
- Pineal SDD §4 actualizado con `GpuSceneCanvas` en producción.
|
||||
|
||||
### ¿Cuándo elegir vello vs GPU directo?
|
||||
|
||||
| Pregunta | Vello (`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 K | Posible 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 uso | pluma editor, shuma shell, mirada, nahual, iniy, khipu, chasqui explorer, dominium UI | cosmos 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
|
||||
|
||||
- **No** reemplazar vello. Coexisten — vello para vector/text/AA fino,
|
||||
GPU directo para volumen.
|
||||
- **No** hacer un layer de abstracción tipo Skia. El trait `Canvas` de
|
||||
pineal y el `paint_with` de llimphi son la abstracción; no se agrega
|
||||
más arriba.
|
||||
- **No** soportar texto en el backend GPU directo. Texto siempre por
|
||||
vello+parley; si una vista mezcla millones de puntos + labels, hace
|
||||
`gpu_paint_with` para los puntos y un `paint_with` superpuesto para
|
||||
los labels.
|
||||
Reference in New Issue
Block a user