# WGSL Shader Editor - Technical Details ## Architecture **Single-file HTML app** (~850 lines) - No build step, no external dependencies - Inline JavaScript (ES6 classes) - Overlay-based syntax highlighting **Components:** 1. `ShaderComposer` - Recursive #include resolution with placeholders 2. `WebGPUPreview` - Full-screen quad rendering, uniform management 3. Syntax highlighter - Regex-based token classification ## Uniform Buffer Layout All shaders receive `CommonUniforms` at `@group(0) @binding(2)`: ```wgsl struct CommonUniforms { resolution: vec2, // Canvas width/height (1280, 720) _pad0: f32, // Alignment padding _pad1: f32, aspect_ratio: f32, // resolution.x / resolution.y time: f32, // Seconds since start (resetable) beat: f32, // Loop time 0.0→1.0 (period: 0.1-10s) audio_intensity: f32, // Manual slider 0-1 or auto-pulse }; ``` **Size:** 32 bytes (matches C++ `CommonPostProcessUniforms`) ## Bind Group Specification Standard bindings for all shaders: ```wgsl @group(0) @binding(0) var smplr: sampler; // Linear sampler @group(0) @binding(1) var txt: texture_2d; // 1x1 black texture @group(0) @binding(2) var uniforms: CommonUniforms; ``` **Note:** All three bindings must be referenced in shader code (even if unused) for WebGPU's auto layout to include them in the bind group. ## Shader Composition `#include "name"` directives are resolved recursively: 1. Replace comments/strings with `__PLACEHOLDER_N__` 2. Scan for `#include "snippet_name"` 3. Recursively resolve snippet (with duplicate detection) 4. Inject as `// --- Included: name ---` block 5. Restore placeholders **Snippet Registry:** - Hardcoded: `common_uniforms` - Fetched: `math/*`, `render/*`, `sdf_primitives`, etc. ## Syntax Highlighting **Technique:** Transparent textarea over styled `
` overlay

**Token Classes:**
- `.hl-keyword` - `fn`, `var`, `let`, `struct`, etc. (blue)
- `.hl-type` - `f32`, `vec3`, `mat4x4`, etc. (cyan)
- `.hl-attribute` - `@vertex`, `@binding`, etc. (light blue)
- `.hl-function` - Function names before `(` (yellow)
- `.hl-number` - Numeric literals (green)
- `.hl-comment` - `// ...` (green)
- `.hl-string` - `"..."` (orange)

**Process:**
1. Escape HTML entities (`<`, `>`, `&`)
2. Extract comments/strings → placeholders
3. Highlight syntax in remaining text
4. Restore placeholders with highlighted versions
5. Update overlay on input (debounced 300ms)

## Example Shaders

### Minimal

```wgsl
@group(0) @binding(0) var smplr: sampler;
@group(0) @binding(1) var txt: texture_2d;

struct CommonUniforms {
    resolution: vec2,
    _pad0: f32, _pad1: f32,
    aspect_ratio: f32,
    time: f32,
    beat: f32,
    audio_intensity: f32,
};
@group(0) @binding(2) var uniforms: CommonUniforms;

@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4 {
    var pos = array, 3>(
        vec2(-1, -1), vec2(3, -1), vec2(-1, 3)
    );
    return vec4(pos[i], 0.0, 1.0);
}

@fragment fn fs_main(@builtin(position) p: vec4) -> @location(0) vec4 {
    let uv = p.xy / uniforms.resolution;
    return vec4(uv, 0.5 + 0.5 * sin(uniforms.time), 1.0);
}
```

### With Composition

```wgsl
@group(0) @binding(0) var smplr: sampler;
@group(0) @binding(1) var txt: texture_2d;

#include "common_uniforms"
@group(0) @binding(2) var uniforms: CommonUniforms;

@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4 {
    var pos = array, 3>(
        vec2(-1, -1), vec2(3, -1), vec2(-1, 3)
    );
    return vec4(pos[i], 0.0, 1.0);
}

#include "math/sdf_shapes"

@fragment fn fs_main(@builtin(position) p: vec4) -> @location(0) vec4 {
    var uv = (p.xy / uniforms.resolution) * 2.0 - 1.0;
    uv.x *= uniforms.aspect_ratio;

    let d = sdSphere(vec3(uv, 0.0), 0.3);
    let col = mix(vec3(0.1, 0.2, 0.3), vec3(0.8, 0.4, 0.9), step(d, 0.0));

    return vec4(col, 1.0);
}
```

### Default Scene (Loaded on Start)

Animated gradient background with pulsing circle synced to `beat`:

```wgsl
@fragment fn fs_main(@builtin(position) p: vec4) -> @location(0) vec4 {
    var uv = (p.xy / uniforms.resolution) * 2.0 - 1.0;
    uv.x *= uniforms.aspect_ratio;

    // Animated gradient
    let t = uniforms.time * 0.3;
    let bg = vec3(
        0.1 + 0.1 * sin(t + uv.y * 2.0),
        0.15 + 0.1 * cos(t * 0.7 + uv.x * 1.5),
        0.2 + 0.1 * sin(t * 0.5)
    );

    // Pulsing circle
    let d = length(uv) - 0.3 - 0.1 * sin(uniforms.beat * 6.28);
    let circle = smoothstep(0.02, 0.0, d);
    let glow = 0.02 / (abs(d) + 0.02);

    let col = bg + vec3(circle) * vec3(0.8, 0.4, 0.9)
              + glow * 0.1 * vec3(0.6, 0.3, 0.8);

    // Sample base texture (unused but required for bind group layout)
    let base = textureSample(txt, smplr, p.xy / uniforms.resolution);

    return vec4(col * uniforms.audio_intensity + base.rgb * 0.0, 1.0);
}
```

## Testing

Load existing workspace shaders:
- `../../workspaces/main/shaders/passthrough.wgsl` - Passthrough with #include
- `../../workspaces/main/shaders/distort.wgsl` - Post-process effect
- `../../workspaces/main/shaders/vignette.wgsl` - Simple effect

## Performance

- ~60 FPS at 1280×720 on integrated GPU
- Syntax highlighting: <5ms per update (debounced 300ms)
- Shader compilation: 10-50ms (cached by WebGPU)
- File I/O: Instant (browser FileReader API)

## Browser Requirements

- **Chrome 113+** or **Edge 113+** (WebGPU support)
- **Not supported:** Firefox (WebGPU behind flag), Safari (partial)
- **CORS:** Use local HTTP server for module imports (if re-modularized)