# 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 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, params: array, 16>, }; @group(0) @binding(0) var u: Uniforms; @vertex fn vs(...) { /* fullscreen triangle */ } // Una función por nodo, nombrada node_ fn node_0(c: vec4, uv: vec2) -> vec4 { ... } fn node_1(a: vec4, b: vec4, uv: vec2) -> vec4 { ... } // blend @fragment fn fs(@builtin(position) pos: vec4) -> @location(0) vec4 { let uv = pos.xy / u.resolution; let out_0 = node_0(vec4(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_` 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_(uv) -> vec4` 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` (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`) - 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= 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`). 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).