feat(kotlin-compose): finalize design system + apps + sync sub-repo gitlinks
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,254 @@
|
||||
# Shader DAG Lab — Arquitectura y hoja de ruta
|
||||
|
||||
Este documento resume el diseño acumulado tras iterar en artifacts desde
|
||||
"composición funcional sobre listas" hasta "DAG de shaders WebGPU con fan-in".
|
||||
Sirve como contexto para retomar el proyecto en Claude Code sin perder las
|
||||
decisiones de diseño.
|
||||
|
||||
## El problema que resuelve
|
||||
|
||||
Un entorno para componer fragment shaders WGSL visualmente: el usuario arrastra
|
||||
"nodos" (primitivas shader) desde una paleta a un pipeline, configura
|
||||
parámetros con sliders / XY pads / color pickers, y el sistema compila el DAG
|
||||
resultante a un único fragment shader ejecutado en WebGPU.
|
||||
|
||||
Las dos vistas — grafo y código — son proyecciones del mismo modelo interno.
|
||||
El usuario casual arrastra cajas; el usuario avanzado lee el WGSL generado.
|
||||
|
||||
## Arquitectura en una frase
|
||||
|
||||
```
|
||||
Pipeline state (árbol JSON)
|
||||
↓ compileDagToWGSL()
|
||||
WGSL source
|
||||
↓ device.createShaderModule()
|
||||
GPU pipeline
|
||||
↓ render loop, escribe uniforms cada frame
|
||||
Canvas
|
||||
```
|
||||
|
||||
Separación crítica: la **topología** del DAG (qué nodos, en qué orden, con qué
|
||||
aristas) dispara recompilación del shader. Los **valores de parámetros** NO
|
||||
disparan recompilación — solo se escriben al uniform buffer cada frame. Esto
|
||||
hace que mover un slider sea instantáneo mientras que añadir/quitar nodos
|
||||
paga el coste de compilar un nuevo pipeline.
|
||||
|
||||
## Modelo de datos
|
||||
|
||||
Un `step` del pipeline es:
|
||||
|
||||
```ts
|
||||
type Step = {
|
||||
id: string; // UUID estable (sobrevive reorders)
|
||||
name: string; // clave en el catálogo NODES
|
||||
params: { // valores de parámetros editables
|
||||
[key: string]: number
|
||||
};
|
||||
meta?: { // metadatos que afectan compilación
|
||||
sourceId?: string; // para blends: id del otro nodo fuente
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
El `topologyKey` que dispara recompilación se computa como:
|
||||
```
|
||||
pipeline.map(s => `${s.name}:${s.meta?.sourceId ?? ''}`).join('|')
|
||||
```
|
||||
|
||||
## Catálogo de nodos
|
||||
|
||||
Cada entrada en `NODES` declara:
|
||||
|
||||
```ts
|
||||
{
|
||||
kind: 'gen' | 'op' | 'blend' | 'warp' | 'sdf' | 'filter' | 'modulator',
|
||||
label: string,
|
||||
desc: string,
|
||||
params: [{ k: string, d: number }], // hasta 4 slots del vec4<f32>
|
||||
controls: Control[], // descriptores de UI
|
||||
body: (idx: number) => string, // emite el cuerpo WGSL
|
||||
}
|
||||
```
|
||||
|
||||
### Tipos de control UI actualmente soportados
|
||||
- `slider`: rango numérico con thumb
|
||||
- `xy`: pad 2D que controla dos params contiguos
|
||||
- `color`: picker RGB que controla tres params contiguos
|
||||
- `select`: dropdown con opciones discretas (valor = índice)
|
||||
- `source`: selector del nodo fuente para blends (escribe a `meta.sourceId`)
|
||||
|
||||
## Compilación del DAG a WGSL
|
||||
|
||||
`compileDagToWGSL(pipeline)` emite un shader con esta estructura:
|
||||
|
||||
```wgsl
|
||||
struct Uniforms {
|
||||
time: f32,
|
||||
_pad: f32,
|
||||
resolution: vec2<f32>,
|
||||
params: array<vec4<f32>, 16>,
|
||||
};
|
||||
|
||||
@group(0) @binding(0) var<uniform> u: Uniforms;
|
||||
|
||||
@vertex fn vs(...) { /* fullscreen triangle */ }
|
||||
|
||||
// Una función por nodo, nombrada node_<idx>
|
||||
fn node_0(c: vec4<f32>, uv: vec2<f32>) -> vec4<f32> { ... }
|
||||
fn node_1(a: vec4<f32>, b: vec4<f32>, uv: vec2<f32>) -> vec4<f32> { ... } // blend
|
||||
|
||||
@fragment fn fs(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> {
|
||||
let uv = pos.xy / u.resolution;
|
||||
let out_0 = node_0(vec4<f32>(0.0), uv);
|
||||
let out_1 = node_1(out_0, out_0, uv); // blend con source=out_0
|
||||
return out_1;
|
||||
}
|
||||
```
|
||||
|
||||
Los outputs intermedios `out_<i>` se preservan como variables locales para
|
||||
que los blends puedan referenciar nodos anteriores arbitrarios. Esto es lo
|
||||
que hace que el DAG sea más que un pipeline lineal.
|
||||
|
||||
## Estado actual de tipos de nodo
|
||||
|
||||
**Implementados (lab 004):**
|
||||
- `gen`: solid, gradient, plasma, checker, circle, stripes, noise (hash)
|
||||
- `op`: invert, gamma, contrast, saturate, hueShift, tint, posterize, vignette, ripple, pulse
|
||||
- `blend`: mix, multiply, screen, add, difference, darken, lighten, mask
|
||||
|
||||
**Pendientes de implementar (ver hoja de ruta abajo):**
|
||||
- `warp`: distorsiones de UV (twirl, polar, kaleidoscope, pixelate, chromatic)
|
||||
- `sdf`: campos de distancia con compositing (smooth_union, subtract, intersect)
|
||||
- `modulator`: LFOs que producen escalares animados para alimentar parámetros
|
||||
- Ruidos procedurales reales: perlin, simplex, worley, fbm
|
||||
- Filtros de luminancia: threshold, levels, duotone, channel_swap
|
||||
- Inputs externos: mouse como uniform adicional
|
||||
|
||||
## Hoja de ruta post-artifact
|
||||
|
||||
### Lab 005 — Warps + Ruidos + Filtros de luma + Mouse
|
||||
Cambios que requiere:
|
||||
- **Refactor de compilación**: generadores pasan a ser `fn sample_gen_<i>(uv) -> vec4<f32>`
|
||||
para que los warps puedan modificar uv antes del muestreo.
|
||||
- La cadena main mantiene dos estados: `uv` (mutable por warps) y `c` (color).
|
||||
- Nuevo kind `warp` con snippets que transforman `uv`.
|
||||
- Nuevo uniform `mouse: vec2<f32>` (coords 0..1) actualizado desde pointer events.
|
||||
- Perlin/FBM como snippets WGSL copiados de implementaciones conocidas
|
||||
(hash-based gradient noise).
|
||||
|
||||
### Lab 006 — Multi-pass (convoluciones + feedback)
|
||||
Cambio arquitectónico mayor: cada nodo puede escribir a una textura offscreen,
|
||||
el siguiente samplea esa textura. Requiere:
|
||||
- Render targets intermedios (pool de texturas)
|
||||
- Múltiples bind groups
|
||||
- Double buffer para feedback temporal (frame N+1 lee frame N)
|
||||
- Detección de qué nodos necesitan aislar su pass y cuáles pueden fusionarse
|
||||
|
||||
Desbloquea: blur gaussiano, sobel, edge detection, bloom, reaction-diffusion,
|
||||
trails, motion blur.
|
||||
|
||||
### Lab 007 — SDFs tipados
|
||||
Introduce heterogeneidad de tipos en las aristas del DAG:
|
||||
- Aristas de tipo `field` (`f32`) vs `color` (`vec4<f32>`)
|
||||
- Validación de tipos en compilación: un operador de color no acepta un field
|
||||
- Nodo terminal `render_sdf` que convierte field → color con shading opciones
|
||||
(planar, gradient, stroke, inflate/outline)
|
||||
- Operadores SDF: `smooth_union`, `subtract`, `intersect`, `round`, `onion`
|
||||
|
||||
Este es el salto conceptual a "DAG tipado" que formaliza lo que el lab 001
|
||||
insinuaba (el fold cambiaba de tipo la arista).
|
||||
|
||||
### Lab 008 — Bidireccional código ↔ grafo
|
||||
Hasta ahora solo va grafo → código. El inverso requiere:
|
||||
- Parser acotado del WGSL que nosotros mismos emitimos (no WGSL general)
|
||||
- Marcadores en comentarios `// @meta node=<name> id=<id>` para robustez
|
||||
- Detección de diff estructural para mantener posiciones / parámetros al editar
|
||||
- Editor de código integrado (CodeMirror) sincronizado con el pipeline
|
||||
|
||||
### Lab 009 — Nodos custom definidos por usuario
|
||||
Modal donde el usuario escribe body WGSL + declara params, y se registra en
|
||||
la paleta como si viniera del catálogo. Cierra la asimetría código→grafo sin
|
||||
necesitar parser completo. Persiste en localStorage o export/import JSON.
|
||||
|
||||
## Decisiones de diseño que vale la pena recordar
|
||||
|
||||
1. **Los IDs de nodo son UUIDs, no índices posicionales**. Las referencias
|
||||
de `sourceId` sobreviven a reorderings. Los índices se re-derivan en
|
||||
compilación.
|
||||
|
||||
2. **El patrón "armed drag"** para el drag handle: el nodo es `draggable=false`
|
||||
por defecto y solo se arma a `true` cuando ocurre pointerdown sobre el
|
||||
header con el handle. Esto evita que los sliders internos activen drag
|
||||
accidentalmente.
|
||||
|
||||
3. **Uniform packing**: todos los parámetros de un nodo van en `u.params[idx]`
|
||||
(un `vec4<f32>`). Si un nodo necesita más de 4 floats, habría que
|
||||
reasignar slots o usar dos slots. No hay nodos hasta ahora que lo pidan.
|
||||
|
||||
4. **MAX_NODES = 16** es arbitrario, limitado solo por el tamaño del array
|
||||
de params en el uniform buffer. Subir es trivial: cambia la constante.
|
||||
|
||||
5. **Las arbitrary values de Tailwind no funcionan** en algunos entornos
|
||||
sin JIT. Grid templates, min-h con calc, etc. se escriben con
|
||||
`style={{...}}` inline. En el proyecto de Claude Code esto no debería
|
||||
ser problema si usas Tailwind 3+ con su compilador.
|
||||
|
||||
## Stack sugerido para Claude Code
|
||||
|
||||
- **Vite + React + TypeScript**: setup estándar, HMR inmediato
|
||||
- **Tailwind 3+** con JIT: los arbitrary values funcionarán esta vez
|
||||
- **Zustand o Jotai** para el pipeline state (se va a hacer más complejo)
|
||||
- **Biome o ESLint + Prettier** para formato consistente
|
||||
- Opcional pero recomendado en cuanto crezca:
|
||||
- **Vitest** para tests unitarios del compilador (`compileDagToWGSL`)
|
||||
- **React Testing Library** si tests de UI
|
||||
- **reactflow** si en lab 008 quieres visualizar el DAG como grafo editable
|
||||
|
||||
## Organización de archivos sugerida
|
||||
|
||||
```
|
||||
src/
|
||||
nodes/
|
||||
index.ts # export del catálogo completo
|
||||
generators.ts # gen kind
|
||||
operators.ts # op kind
|
||||
blends.ts # blend kind
|
||||
warps.ts # (lab 005)
|
||||
sdfs.ts # (lab 007)
|
||||
types.ts # tipos compartidos NodeDef, Control, etc.
|
||||
compiler/
|
||||
compileDagToWGSL.ts # la función principal
|
||||
uniforms.ts # packing y escritura del buffer
|
||||
validate.ts # validación de tipos (lab 007+)
|
||||
webgpu/
|
||||
useWebGPU.ts # el hook
|
||||
renderer.ts # setup del device, context, pipeline
|
||||
ui/
|
||||
PipelineNode.tsx
|
||||
controls/
|
||||
Slider.tsx
|
||||
XYPad.tsx
|
||||
ColorPicker.tsx
|
||||
Select.tsx
|
||||
SourceSelector.tsx
|
||||
Palette.tsx
|
||||
Canvas.tsx
|
||||
WGSLView.tsx
|
||||
store/
|
||||
pipeline.ts # Zustand store
|
||||
App.tsx
|
||||
main.tsx
|
||||
```
|
||||
|
||||
## Primeros pasos en Claude Code
|
||||
|
||||
1. `npm create vite@latest shader-dag -- --template react-ts`
|
||||
2. Copiar `shader-dag-blends.jsx` como base monolítica, renombrar a `.tsx`
|
||||
3. Arreglar los tipos TypeScript (muchas funciones del artifact no están tipadas)
|
||||
4. Romper el monolito según la estructura de arriba
|
||||
5. Implementar lab 005: refactor de compilación para habilitar warps
|
||||
|
||||
Nota: el artifact de base (`shader-dag-blends.jsx`) funciona pero tiene el
|
||||
tipado implícito de JSX. Convertir a TS te va a revelar varios tipos que
|
||||
merece la pena modelar explícitamente (especialmente `Control`, que ahora es
|
||||
un union discriminado informal).
|
||||
Reference in New Issue
Block a user