summaryrefslogtreecommitdiff
path: root/tools/shader_editor/SHADER_EDITOR_DETAILS.md
blob: b8bbb25decf66daddab5cc0d2fffc671eb25a9dc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# 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<f32>,      // 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<f32>;     // 1x1 black texture
@group(0) @binding(2) var<uniform> 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 `<pre>` 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<f32>;

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

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

@fragment fn fs_main(@builtin(position) p: vec4<f32>) -> @location(0) vec4<f32> {
    let uv = p.xy / uniforms.resolution;
    return vec4<f32>(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<f32>;

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

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

#include "math/sdf_shapes"

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

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

    return vec4<f32>(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<f32>) -> @location(0) vec4<f32> {
    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<f32>(
        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<f32>(circle) * vec3<f32>(0.8, 0.4, 0.9)
              + glow * 0.1 * vec3<f32>(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<f32>(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)