@stevejtrettel/shader-sandbox 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/README.md +259 -235
  2. package/bin/cli.js +106 -14
  3. package/dist-lib/app/App.d.ts +143 -15
  4. package/dist-lib/app/App.d.ts.map +1 -1
  5. package/dist-lib/app/App.js +1343 -108
  6. package/dist-lib/app/app.css +349 -24
  7. package/dist-lib/app/types.d.ts +48 -5
  8. package/dist-lib/app/types.d.ts.map +1 -1
  9. package/dist-lib/editor/EditorPanel.d.ts +2 -2
  10. package/dist-lib/editor/EditorPanel.d.ts.map +1 -1
  11. package/dist-lib/editor/EditorPanel.js +1 -1
  12. package/dist-lib/editor/editor-panel.css +55 -32
  13. package/dist-lib/editor/prism-editor.css +16 -16
  14. package/dist-lib/embed.js +1 -1
  15. package/dist-lib/engine/{ShadertoyEngine.d.ts → ShaderEngine.d.ts} +134 -10
  16. package/dist-lib/engine/ShaderEngine.d.ts.map +1 -0
  17. package/dist-lib/engine/ShaderEngine.js +1523 -0
  18. package/dist-lib/engine/glHelpers.d.ts +24 -0
  19. package/dist-lib/engine/glHelpers.d.ts.map +1 -1
  20. package/dist-lib/engine/glHelpers.js +88 -0
  21. package/dist-lib/engine/std140.d.ts +47 -0
  22. package/dist-lib/engine/std140.d.ts.map +1 -0
  23. package/dist-lib/engine/std140.js +119 -0
  24. package/dist-lib/engine/types.d.ts +55 -5
  25. package/dist-lib/engine/types.d.ts.map +1 -1
  26. package/dist-lib/engine/types.js +1 -1
  27. package/dist-lib/index.d.ts +4 -3
  28. package/dist-lib/index.d.ts.map +1 -1
  29. package/dist-lib/index.js +2 -1
  30. package/dist-lib/layouts/SplitLayout.d.ts +2 -1
  31. package/dist-lib/layouts/SplitLayout.d.ts.map +1 -1
  32. package/dist-lib/layouts/SplitLayout.js +3 -0
  33. package/dist-lib/layouts/TabbedLayout.d.ts.map +1 -1
  34. package/dist-lib/layouts/UILayout.d.ts +55 -0
  35. package/dist-lib/layouts/UILayout.d.ts.map +1 -0
  36. package/dist-lib/layouts/UILayout.js +147 -0
  37. package/dist-lib/layouts/default.css +2 -2
  38. package/dist-lib/layouts/index.d.ts +11 -1
  39. package/dist-lib/layouts/index.d.ts.map +1 -1
  40. package/dist-lib/layouts/index.js +17 -1
  41. package/dist-lib/layouts/split.css +33 -31
  42. package/dist-lib/layouts/tabbed.css +127 -74
  43. package/dist-lib/layouts/types.d.ts +14 -3
  44. package/dist-lib/layouts/types.d.ts.map +1 -1
  45. package/dist-lib/main.js +33 -0
  46. package/dist-lib/project/configHelpers.d.ts +45 -0
  47. package/dist-lib/project/configHelpers.d.ts.map +1 -0
  48. package/dist-lib/project/configHelpers.js +196 -0
  49. package/dist-lib/project/generatedLoader.d.ts +2 -2
  50. package/dist-lib/project/generatedLoader.d.ts.map +1 -1
  51. package/dist-lib/project/generatedLoader.js +23 -5
  52. package/dist-lib/project/loadProject.d.ts +6 -6
  53. package/dist-lib/project/loadProject.d.ts.map +1 -1
  54. package/dist-lib/project/loadProject.js +396 -144
  55. package/dist-lib/project/loaderHelper.d.ts +4 -4
  56. package/dist-lib/project/loaderHelper.d.ts.map +1 -1
  57. package/dist-lib/project/loaderHelper.js +278 -116
  58. package/dist-lib/project/types.d.ts +292 -13
  59. package/dist-lib/project/types.d.ts.map +1 -1
  60. package/dist-lib/project/types.js +13 -1
  61. package/dist-lib/styles/base.css +5 -1
  62. package/dist-lib/uniforms/UniformControls.d.ts +60 -0
  63. package/dist-lib/uniforms/UniformControls.d.ts.map +1 -0
  64. package/dist-lib/uniforms/UniformControls.js +518 -0
  65. package/dist-lib/uniforms/UniformStore.d.ts +74 -0
  66. package/dist-lib/uniforms/UniformStore.d.ts.map +1 -0
  67. package/dist-lib/uniforms/UniformStore.js +145 -0
  68. package/dist-lib/uniforms/UniformsPanel.d.ts +53 -0
  69. package/dist-lib/uniforms/UniformsPanel.d.ts.map +1 -0
  70. package/dist-lib/uniforms/UniformsPanel.js +124 -0
  71. package/dist-lib/uniforms/index.d.ts +11 -0
  72. package/dist-lib/uniforms/index.d.ts.map +1 -0
  73. package/dist-lib/uniforms/index.js +8 -0
  74. package/package.json +16 -1
  75. package/src/app/App.ts +1469 -126
  76. package/src/app/app.css +349 -24
  77. package/src/app/types.ts +53 -5
  78. package/src/editor/EditorPanel.ts +5 -5
  79. package/src/editor/editor-panel.css +55 -32
  80. package/src/editor/prism-editor.css +16 -16
  81. package/src/embed.ts +1 -1
  82. package/src/engine/ShaderEngine.ts +1934 -0
  83. package/src/engine/glHelpers.ts +117 -0
  84. package/src/engine/std140.ts +136 -0
  85. package/src/engine/types.ts +69 -5
  86. package/src/index.ts +4 -3
  87. package/src/layouts/SplitLayout.ts +8 -3
  88. package/src/layouts/TabbedLayout.ts +3 -3
  89. package/src/layouts/UILayout.ts +185 -0
  90. package/src/layouts/default.css +2 -2
  91. package/src/layouts/index.ts +20 -1
  92. package/src/layouts/split.css +33 -31
  93. package/src/layouts/tabbed.css +127 -74
  94. package/src/layouts/types.ts +19 -3
  95. package/src/layouts/ui.css +289 -0
  96. package/src/main.ts +39 -1
  97. package/src/project/configHelpers.ts +225 -0
  98. package/src/project/generatedLoader.ts +27 -6
  99. package/src/project/loadProject.ts +459 -173
  100. package/src/project/loaderHelper.ts +377 -130
  101. package/src/project/types.ts +360 -14
  102. package/src/styles/base.css +5 -1
  103. package/src/styles/theme.css +292 -0
  104. package/src/uniforms/UniformControls.ts +660 -0
  105. package/src/uniforms/UniformStore.ts +166 -0
  106. package/src/uniforms/UniformsPanel.ts +163 -0
  107. package/src/uniforms/index.ts +13 -0
  108. package/src/uniforms/uniform-controls.css +342 -0
  109. package/src/uniforms/uniforms-panel.css +277 -0
  110. package/templates/shaders/example-buffer/config.json +1 -0
  111. package/dist-lib/engine/ShadertoyEngine.d.ts.map +0 -1
  112. package/dist-lib/engine/ShadertoyEngine.js +0 -704
  113. package/src/engine/ShadertoyEngine.ts +0 -929
package/README.md CHANGED
@@ -1,105 +1,83 @@
1
1
  # Shader Sandbox
2
2
 
3
- A lightweight, Shadertoy-compatible GLSL shader playground built for teaching and learning shader programming.
3
+ A lightweight, Shadertoy-compatible GLSL shader development environment. Copy shaders directly from Shadertoy and run them locally with live editing.
4
4
 
5
5
  ## Features
6
6
 
7
7
  - **Shadertoy Compatibility** - Copy/paste shaders directly from Shadertoy
8
- - **Full Shadertoy Uniforms** - `iTime`, `iResolution`, `iFrame`, `iMouse`, `iTimeDelta`, `iChannel0-3`
8
+ - **Full Shadertoy Uniforms** - `iTime`, `iResolution`, `iFrame`, `iMouse`, `iTimeDelta`, `iDate`, `iFrameRate`, `iChannel0-3`
9
9
  - **Multi-Buffer Rendering** - BufferA-D passes with correct ping-pong semantics
10
- - **Texture Support** - Load external images with configurable filtering and wrapping
11
- - **Keyboard Input** - Full keyboard state via Shadertoy-compatible texture
10
+ - **Texture Support** - Load images (including cubemaps) with configurable filtering and wrapping
11
+ - **Keyboard Input** - Full keyboard state via texture. In standard mode, key constants (`KEY_A`–`KEY_Z`, `KEY_SPACE`, etc.) and helpers (`isKeyDown`, `keyToggle`) are auto-injected
12
+ - **Audio Input** - Microphone FFT spectrum and waveform as a texture, as in shadertoy
13
+ - **Webcam & Video** - Live webcam or video files as channel inputs, as in shadertoy
14
+ - **Custom Uniforms** - Float, int, bool, vec2, vec3, vec4 sliders and color pickers via config. *An extension beyond shadertoy*
15
+ - **UBO Array Uniforms** - Large data arrays (positions, colors, matrices) via Uniform Buffer Objects. *An extension beyond shadertoy*
16
+ - **Scripting API** - JavaScript hooks for per-frame computation, texture upload, and GPU readback. *An extension beyond shadertoy*
17
+ - **Touch Support** - Multi-touch, pinch, and gesture uniforms for mobile
18
+ - **Live Code Editing** - Edit shaders in the browser with instant recompilation
19
+ - **Multiple Layouts** - Fullscreen, split-view, or tabbed code display
12
20
  - **Playback Controls** - Play/pause, reset, and screenshot capture
13
- - **Multiple Layout Modes** - Fullscreen, default, split-view, or tabbed code display
14
- - **Zero Runtime Dependencies** - Pure WebGL2
15
- - **Tiny Builds** - ~26KB JS (gzipped)
21
+ - **Themes** - Light, dark, or system-following theme
16
22
 
17
23
  ## Quick Start
18
24
 
19
25
  ```bash
20
- npm install
21
- npm run new my-shader
22
- npm run dev:demo my-shader
23
- ```
24
-
25
- Open `http://localhost:3000` to see your shader.
26
-
27
- ---
28
-
29
- ## Use as NPM Package
26
+ # Create a new shader project
27
+ npx @stevejtrettel/shader-sandbox create my-shaders
30
28
 
31
- Create your own shader collection:
32
-
33
- ```bash
34
- # Create a new project (does everything in one step)
35
- npx shader-sandbox create my-shaders
36
-
37
- # Run a shader
29
+ # Enter the project
38
30
  cd my-shaders
31
+
32
+ # Run an example shader
39
33
  shader dev example-gradient
40
34
  ```
41
35
 
42
- That's it! The `create` command sets up the directory, installs dependencies, and creates example shaders.
36
+ Open http://localhost:3000 to see your shader running.
43
37
 
44
- ### CLI Commands
38
+ ## CLI Commands
45
39
 
46
40
  ```bash
47
- shader create <name> # Create a new shader project (recommended)
48
- shader init # Initialize shaders in current directory
49
- shader list # List all shaders
50
- shader dev <name> # Run shader in development mode
41
+ shader create <name> # Create a new shader project
42
+ shader dev <name> # Run shader with live reload
51
43
  shader build <name> # Build shader for production
52
44
  shader new <name> # Create a new shader
45
+ shader list # List all shaders
46
+ shader init # Initialize shaders in current directory
53
47
  ```
54
48
 
55
- ### Project Structure
49
+ ## Project Structure
56
50
 
57
- After `shader create`:
51
+ After running `shader create my-shaders`:
58
52
 
59
53
  ```
60
54
  my-shaders/
61
55
  ├── shaders/
62
56
  │ ├── example-gradient/
63
- │ │ ├── image.glsl # Main shader
64
- │ │ └── config.json # Optional config
57
+ │ │ ├── image.glsl # Main shader code
58
+ │ │ └── config.json # Optional configuration
65
59
  │ └── example-buffer/
66
- │ ├── image.glsl
67
- │ ├── bufferA.glsl # Feedback buffer
60
+ │ ├── image.glsl # Final output
61
+ │ ├── bufferA.glsl # Feedback buffer
68
62
  │ └── config.json
69
- ├── main.ts
70
- ├── vite.config.js
63
+ ├── main.ts # Entry point
64
+ ├── vite.config.js # Vite configuration
71
65
  └── package.json
72
66
  ```
73
67
 
74
- ### Adding a New Shader
75
-
76
- ```bash
77
- shader new my-cool-shader
78
- # Creates shaders/my-cool-shader/image.glsl
79
-
80
- shader dev my-cool-shader
81
- # Opens browser with live reload
82
- ```
68
+ ## Creating Shaders
83
69
 
84
- ---
70
+ ### Simple Shader
85
71
 
86
- ## Common Setups
87
-
88
- ### 1. Simple Shader (just image.glsl)
89
-
90
- The simplest setup - no config needed.
72
+ Create a new shader with just an image pass:
91
73
 
92
74
  ```bash
93
- npm run new my-shader
75
+ shader new my-shader
76
+ shader dev my-shader
94
77
  ```
95
78
 
96
- **Files:**
97
- ```
98
- demos/my-shader/
99
- └── image.glsl
100
- ```
79
+ Edit `shaders/my-shader/image.glsl`:
101
80
 
102
- **image.glsl:**
103
81
  ```glsl
104
82
  void mainImage(out vec4 fragColor, in vec2 fragCoord) {
105
83
  vec2 uv = fragCoord / iResolution.xy;
@@ -108,45 +86,26 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
108
86
  }
109
87
  ```
110
88
 
111
- ---
89
+ ### Copy from Shadertoy
112
90
 
113
- ### 2. One Buffer (feedback/trails)
91
+ 1. Find a shader on [Shadertoy](https://www.shadertoy.com)
92
+ 2. Copy the code from the "Image" tab
93
+ 3. Paste into `shaders/my-shader/image.glsl`
94
+ 4. Run `shader dev my-shader`
114
95
 
115
- For effects that accumulate over time (trails, paint, fluid).
96
+ Most single-pass shaders work immediately. For multi-buffer shaders, you'll need to create the buffer files and config.
116
97
 
117
- ```bash
118
- npm run new my-shader 1
119
- ```
98
+ ### Multi-Buffer Shaders
120
99
 
121
- **Files:**
122
- ```
123
- demos/my-shader/
124
- ├── bufferA.glsl
125
- ├── image.glsl
126
- └── config.json
127
- ```
128
-
129
- **config.json:**
130
- ```json
131
- {
132
- "BufferA": {
133
- "iChannel0": "BufferA"
134
- },
135
- "Image": {
136
- "iChannel0": "BufferA"
137
- }
138
- }
139
- ```
100
+ For feedback effects (trails, fluid, etc.), create a buffer:
140
101
 
141
- **bufferA.glsl:**
102
+ **shaders/my-effect/bufferA.glsl:**
142
103
  ```glsl
143
104
  void mainImage(out vec4 fragColor, in vec2 fragCoord) {
144
105
  vec2 uv = fragCoord / iResolution.xy;
106
+ vec4 prev = texture(iChannel0, uv) * 0.98; // Previous frame with fade
145
107
 
146
- // Read previous frame with fade
147
- vec4 prev = texture(iChannel0, uv) * 0.98;
148
-
149
- // Draw at mouse
108
+ // Draw at mouse position
150
109
  vec2 mouse = iMouse.xy / iResolution.xy;
151
110
  float d = length(uv - mouse);
152
111
  float spot = smoothstep(0.05, 0.0, d);
@@ -155,7 +114,7 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
155
114
  }
156
115
  ```
157
116
 
158
- **image.glsl:**
117
+ **shaders/my-effect/image.glsl:**
159
118
  ```glsl
160
119
  void mainImage(out vec4 fragColor, in vec2 fragCoord) {
161
120
  vec2 uv = fragCoord / iResolution.xy;
@@ -163,228 +122,293 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
163
122
  }
164
123
  ```
165
124
 
166
- ---
167
-
168
- ### 3. Multiple Buffers (interacting simulations)
169
-
170
- For reaction-diffusion, fluid dynamics, etc. All buffers can read all other buffers.
171
-
172
- ```bash
173
- npm run new my-shader 2
174
- ```
175
-
176
- **Files:**
177
- ```
178
- demos/my-shader/
179
- ├── bufferA.glsl
180
- ├── bufferB.glsl
181
- ├── image.glsl
182
- └── config.json
183
- ```
184
-
185
- **config.json:**
125
+ **shaders/my-effect/config.json:**
186
126
  ```json
187
127
  {
188
128
  "BufferA": {
189
- "iChannel0": "BufferA",
190
- "iChannel1": "BufferB"
191
- },
192
- "BufferB": {
193
- "iChannel0": "BufferA",
194
- "iChannel1": "BufferB"
129
+ "iChannel0": "BufferA"
195
130
  },
196
131
  "Image": {
197
- "iChannel0": "BufferA",
198
- "iChannel1": "BufferB"
132
+ "iChannel0": "BufferA"
199
133
  }
200
134
  }
201
135
  ```
202
136
 
203
- **Channel mapping:** `iChannel0` = BufferA, `iChannel1` = BufferB, etc.
204
-
205
- ---
137
+ ### Using Textures
206
138
 
207
- ### 4. Texture + Image (image processing)
139
+ Place an image in your shader folder and reference it in config:
208
140
 
209
- Load an image and process it.
210
-
211
- **Files:**
212
- ```
213
- demos/my-shader/
214
- ├── image.glsl
215
- ├── photo.jpg
216
- └── config.json
141
+ ```json
142
+ {
143
+ "Image": {
144
+ "iChannel0": "photo.jpg"
145
+ }
146
+ }
217
147
  ```
218
148
 
219
- **config.json:**
149
+ With options:
220
150
  ```json
221
151
  {
222
152
  "Image": {
223
- "iChannel0": "photo.jpg"
153
+ "iChannel0": {
154
+ "texture": "photo.jpg",
155
+ "filter": "nearest",
156
+ "wrap": "clamp"
157
+ }
224
158
  }
225
159
  }
226
160
  ```
227
161
 
228
- **image.glsl:**
229
- ```glsl
230
- void mainImage(out vec4 fragColor, in vec2 fragCoord) {
231
- vec2 uv = fragCoord / iResolution.xy;
232
- vec4 img = texture(iChannel0, uv);
162
+ ### Custom Uniforms (Slider Controls)
233
163
 
234
- // Example: grayscale
235
- float gray = dot(img.rgb, vec3(0.299, 0.587, 0.114));
164
+ Add interactive controls to your shader by defining uniforms in config:
236
165
 
237
- fragColor = vec4(vec3(gray), 1.0);
166
+ ```json
167
+ {
168
+ "controls": true,
169
+ "uniforms": {
170
+ "uSpeed": { "type": "float", "value": 1.0, "min": 0.0, "max": 5.0, "label": "Speed" },
171
+ "uColor": { "type": "vec3", "value": [1.0, 0.5, 0.2], "color": true, "label": "Color" },
172
+ "uAnimate": { "type": "bool", "value": true, "label": "Animate" }
173
+ },
174
+ "Image": {}
238
175
  }
239
176
  ```
240
177
 
241
- **Texture options (all optional):**
242
- ```json
243
- { "texture": "photo.jpg", "filter": "linear", "wrap": "repeat", "type": "2d" }
244
- ```
245
- - `filter`: `"linear"` (smooth, default) or `"nearest"` (pixelated)
246
- - `wrap`: `"repeat"` (tile, default) or `"clamp"` (stretch edges)
247
- - `type`: `"2d"` (standard, default) or `"cubemap"` (equirectangular environment map)
178
+ Uniforms declared in config are **auto-injected** into your shader code — you don't need to write `uniform` declarations. Just use them directly:
248
179
 
249
- **Cubemap textures:** Use `"type": "cubemap"` for equirectangular environment maps (360° panoramas). The engine will automatically convert 3D direction lookups to 2D coordinates:
250
- ```json
251
- { "texture": "environment.jpg", "type": "cubemap" }
252
- ```
253
180
  ```glsl
254
- // In your shader, sample with a 3D direction:
255
- vec3 dir = normalize(rayDirection);
256
- vec4 sky = texture(iChannel0, dir); // Automatically converted
181
+ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
182
+ vec2 uv = fragCoord / iResolution.xy;
183
+ float t = uAnimate ? iTime * uSpeed : 0.0;
184
+ vec3 col = uColor * (0.5 + 0.5 * sin(uv.x * 10.0 - t));
185
+ fragColor = vec4(col, 1.0);
186
+ }
257
187
  ```
258
188
 
259
- ---
189
+ #### Supported Uniform Types
260
190
 
261
- ### 5. Texture + Buffers (image + feedback)
191
+ | Type | UI Control | Config Fields |
192
+ |------|-----------|---------------|
193
+ | `float` | Slider | `value`, `min` (0), `max` (1), `step` (0.01) |
194
+ | `int` | Discrete slider | `value`, `min` (0), `max` (10), `step` (1) |
195
+ | `bool` | Toggle | `value` |
196
+ | `vec2` | XY pad | `value`, `min` ([0,0]), `max` ([1,1]) |
197
+ | `vec3` | 3 sliders or color picker | `value`, `color` (false), `min`, `max`, `step` |
198
+ | `vec4` | 4 sliders or color+alpha picker | `value`, `color` (false), `min`, `max`, `step` |
262
199
 
263
- Combine textures with buffer feedback for effects like painting on an image.
200
+ Set `"hidden": true` on any scalar uniform to exclude it from the UI panel (useful for script-controlled values).
264
201
 
265
- **Files:**
266
- ```
267
- demos/my-shader/
268
- ├── bufferA.glsl
269
- ├── image.glsl
270
- ├── photo.jpg
271
- └── config.json
272
- ```
202
+ #### Array Uniforms (UBOs)
203
+
204
+ For large data arrays (positions, matrices, etc.), use array uniforms backed by Uniform Buffer Objects:
273
205
 
274
- **config.json:**
275
206
  ```json
276
207
  {
277
- "BufferA": {
278
- "iChannel0": "BufferA",
279
- "iChannel1": "photo.jpg"
280
- },
281
- "Image": {
282
- "iChannel0": "BufferA",
283
- "iChannel1": "photo.jpg"
208
+ "uniforms": {
209
+ "matrices": { "type": "mat3", "count": 128 },
210
+ "matrixCount": { "type": "int", "value": 1, "hidden": true }
284
211
  }
285
212
  }
286
213
  ```
287
214
 
288
- **bufferA.glsl:**
215
+ Array uniforms support types: `float`, `vec2`, `vec3`, `vec4`, `mat3`, `mat4`. They have no UI — data is provided from JavaScript via `engine.setUniformValue()`.
216
+
217
+ In the compiled shader, array uniforms are wrapped in a `layout(std140)` uniform block with a `_ub_` prefix (e.g., `_ub_matrices`). The array variable itself uses the original name, so you reference it directly in GLSL:
218
+
289
219
  ```glsl
220
+ // Auto-injected by the engine (you don't write this):
221
+ // layout(std140) uniform _ub_matrices {
222
+ // mat3 matrices[128];
223
+ // };
224
+
290
225
  void mainImage(out vec4 fragColor, in vec2 fragCoord) {
291
- vec2 uv = fragCoord / iResolution.xy;
226
+ for (int i = 0; i < matrixCount; i++) {
227
+ vec3 p = matrices[i] * vec3(fragCoord, 1.0);
228
+ // ...
229
+ }
230
+ }
231
+ ```
292
232
 
293
- vec4 prev = texture(iChannel0, uv); // Previous frame
294
- vec4 img = texture(iChannel1, uv); // Original image
233
+ See the [Configuration Reference](docs/learn/configuration.md) for all uniform types.
295
234
 
296
- // Paint with mouse
297
- vec2 mouse = iMouse.xy / iResolution.xy;
298
- float d = length(uv - mouse);
299
- float brush = smoothstep(0.05, 0.0, d);
235
+ ### Scripting (JavaScript Hooks)
300
236
 
301
- // Blend: painted areas persist, unpainted fade to original
302
- fragColor = mix(mix(prev, img, 0.01), prev + brush, brush);
237
+ For computed data that changes every frame, add a `script.js` to your shader folder:
238
+
239
+ **shaders/my-shader/script.js:**
240
+ ```js
241
+ const COUNT = 32;
242
+
243
+ export function onFrame(engine, time) {
244
+ const data = new Float32Array(COUNT * 4);
245
+ for (let i = 0; i < COUNT; i++) {
246
+ const phase = (i / COUNT) * Math.PI * 2.0;
247
+ data[i * 4 + 0] = 0.5 + Math.cos(time + phase) * 0.3; // x
248
+ data[i * 4 + 1] = 0.5 + Math.sin(time + phase) * 0.3; // y
249
+ data[i * 4 + 2] = 0.02; // radius
250
+ data[i * 4 + 3] = i / COUNT; // hue
251
+ }
252
+ engine.setUniformValue('positions', data);
303
253
  }
304
254
  ```
305
255
 
306
- ---
307
-
308
- ## Buffer Execution & Frame Timing
256
+ Scripts can export `setup(engine)` (called once) and/or `onFrame(engine, time, deltaTime, frame)` (called every frame).
309
257
 
310
- **Execution order:** BufferA → BufferB → BufferC → BufferD → Image
258
+ The script API provides:
259
+ - `engine.setUniformValue(name, value)` — set any uniform
260
+ - `engine.getUniformValue(name)` — read current value
261
+ - `engine.updateTexture(name, width, height, data)` — upload a texture from JS
262
+ - `engine.readPixels(passName, x, y, w, h)` — read pixels from a buffer (GPU readback)
263
+ - `engine.width` / `engine.height` — canvas dimensions
311
264
 
312
- All buffer reads default to the **previous frame**. This is safe for all cases:
313
- - Self-reference (feedback effects)
314
- - Reading buffers that haven't run yet this frame
315
- - Reading buffers that have already run (you get their latest output)
265
+ ## Channel Types
316
266
 
317
- Use `{ "buffer": "BufferA", "current": true }` only if you specifically need the in-progress current frame (rare).
267
+ Channels can be bound using string shortcuts or full objects:
318
268
 
319
- ---
269
+ | Shorthand | Object Form | Description |
270
+ |-----------|-------------|-------------|
271
+ | `"BufferA"` | `{ "buffer": "BufferA" }` | Buffer pass output |
272
+ | `"photo.jpg"` | `{ "texture": "photo.jpg" }` | Image texture |
273
+ | `"keyboard"` | `{ "keyboard": true }` | Keyboard state |
274
+ | `"audio"` | `{ "audio": true }` | Microphone FFT + waveform |
275
+ | `"webcam"` | `{ "webcam": true }` | Live webcam feed |
276
+ | — | `{ "video": "clip.mp4" }` | Video file |
277
+ | — | `{ "script": "myData" }` | Script-uploaded texture |
320
278
 
321
279
  ## Layouts
322
280
 
323
- Control how the shader is displayed with the `layout` option in `config.json`:
281
+ Control how the shader is displayed in `config.json`:
324
282
 
325
283
  ```json
326
284
  {
327
- "layout": "split",
328
- "BufferA": { ... },
329
- "Image": { ... }
285
+ "layout": "split"
330
286
  }
331
287
  ```
332
288
 
333
- | Layout | Description | Best for |
334
- |--------|-------------|----------|
335
- | `fullscreen` | Canvas fills entire viewport | Immersive art, games, installations |
336
- | `default` | Canvas centered with max-width | General viewing (default without config) |
337
- | `tabbed` | Tabs to switch between shader and code | Exploring/debugging |
338
- | `split` | Side-by-side: shader left, code right | Teaching, presentations, tutorials |
289
+ | Layout | Description |
290
+ |--------|-------------|
291
+ | `fullscreen` | Canvas fills the viewport |
292
+ | `default` | Centered canvas with controls |
293
+ | `tabbed` | Tabs to switch between shader and code |
294
+ | `split` | Side-by-side shader and code editor |
339
295
 
340
- **`fullscreen`** - No chrome, canvas fills the screen:
341
- ```json
342
- { "layout": "fullscreen" }
343
- ```
296
+ ## Shadertoy Uniforms
344
297
 
345
- **`default`** - Clean centered view with rounded corners:
346
- ```json
347
- { "layout": "default" }
348
- ```
298
+ All standard Shadertoy uniforms are supported:
349
299
 
350
- **`tabbed`** - Click tabs to switch between live shader and source code:
351
- ```json
352
- { "layout": "tabbed" }
353
- ```
300
+ | Uniform | Type | Description |
301
+ |---------|------|-------------|
302
+ | `iResolution` | `vec3` | Viewport resolution (width, height, 1) |
303
+ | `iTime` | `float` | Elapsed time in seconds |
304
+ | `iTimeDelta` | `float` | Time since last frame |
305
+ | `iFrame` | `int` | Frame counter |
306
+ | `iFrameRate` | `float` | Frames per second |
307
+ | `iMouse` | `vec4` | Mouse position and click state |
308
+ | `iChannel0-3` | `sampler2D` | Input textures/buffers |
309
+ | `iChannelResolution[4]` | `vec3[]` | Resolution of each channel |
310
+ | `iDate` | `vec4` | Year, month, day, time in seconds |
354
311
 
355
- **`split`** - See shader and code simultaneously (code panel has tabs for multi-file projects):
356
- ```json
357
- { "layout": "split" }
358
- ```
312
+ ### Touch Uniforms (Mobile / Multi-touch)
359
313
 
360
- ---
314
+ | Uniform | Type | Description |
315
+ |---------|------|-------------|
316
+ | `iTouchCount` | `int` | Number of active touches |
317
+ | `iTouch0-2` | `vec4` | Per-touch position and state |
318
+ | `iPinch` | `float` | Current pinch distance |
319
+ | `iPinchDelta` | `float` | Change in pinch distance |
320
+ | `iPinchCenter` | `vec2` | Center point between pinch fingers |
361
321
 
362
322
  ## Keyboard Shortcuts
363
323
 
364
324
  | Key | Action |
365
325
  |-----|--------|
366
326
  | **S** | Save screenshot (PNG) |
367
- | **Space** | Play/Pause (when controls enabled) |
368
- | **R** | Reset to frame 0 (when controls enabled) |
327
+ | **Space** | Play/Pause |
328
+ | **R** | Reset to frame 0 |
369
329
 
370
- ---
330
+ ## Recording and Export
371
331
 
372
- ## NPM Scripts
332
+ ### Video Recording
373
333
 
374
- ```bash
375
- npm run new <name> [buffers] # Create new shader project
376
- npm run dev:demo <name> # Development server with hot reload
377
- npm run build:demo <name> # Production build to dist/
334
+ When `controls: true`, click the record button (or use the controls menu) to capture your shader as a WebM video. Recording uses the browser's `MediaRecorder` API at 60fps with VP9 encoding. Click the stop button to end recording — the video downloads automatically.
335
+
336
+ ### HTML Export
337
+
338
+ Click the export button in the controls menu to generate a standalone HTML file with your shader embedded. The exported file includes:
339
+ - All shader passes and common code
340
+ - Current custom uniform values baked in
341
+ - Full WebGL2 rendering pipeline (no dependencies)
342
+ - Mouse interaction support
343
+ - Resize handling
344
+
345
+ **Limitations:** Array uniforms (UBOs), audio, webcam, video, and script hooks are not included in the export. Textures are replaced with a procedural checkerboard pattern.
346
+
347
+ ## Embedding as a Library
348
+
349
+ The package exports its core classes for use in custom applications:
350
+
351
+ ```typescript
352
+ import { App, createLayout, loadDemo } from '@stevejtrettel/shader-sandbox';
353
+ import type { ShaderProject, LayoutMode } from '@stevejtrettel/shader-sandbox';
378
354
  ```
379
355
 
380
- ---
356
+ ### Exports
357
+
358
+ | Export | Description |
359
+ |--------|-------------|
360
+ | `App` | Main application class — creates canvas, engine, and animation loop |
361
+ | `createLayout(mode, options)` | Factory to create a layout (fullscreen, default, split, tabbed) |
362
+ | `applyTheme(mode)` | Apply a theme (light, dark, system) |
363
+ | `loadDemo(files)` | Load a shader project from bundled file data |
364
+
365
+ ### Example: Embedding a shader
366
+
367
+ ```typescript
368
+ import { App, createLayout } from '@stevejtrettel/shader-sandbox';
369
+
370
+ const project = /* your ShaderProject object */;
381
371
 
382
- ## Documentation
372
+ const layout = createLayout(project.layout, {
373
+ container: document.getElementById('shader-container'),
374
+ project,
375
+ });
376
+
377
+ const app = new App({
378
+ container: layout.getCanvasContainer(),
379
+ project,
380
+ });
381
+
382
+ if (!app.hasErrors()) {
383
+ app.start();
384
+ }
385
+
386
+ // Clean up when done
387
+ app.dispose();
388
+ ```
389
+
390
+ ### Embed entry point
391
+
392
+ For build-time embedding of a specific shader, the package also provides an `embed()` function:
393
+
394
+ ```typescript
395
+ import { embed } from '@stevejtrettel/shader-sandbox/embed';
396
+
397
+ const { app, destroy } = await embed({
398
+ container: '#my-container', // CSS selector or HTMLElement
399
+ pixelRatio: window.devicePixelRatio,
400
+ });
401
+
402
+ // Later: destroy() to clean up
403
+ ```
404
+
405
+ ## Building for Production
406
+
407
+ ```bash
408
+ shader build my-shader
409
+ ```
383
410
 
384
- - [Getting Started](docs/learn/getting-started.md) - Your first shader
385
- - [Buffers and Channels](docs/learn/buffers-and-channels.md) - Multi-pass rendering
386
- - [Configuration](docs/learn/configuration.md) - Full config reference
387
- - [Architecture](docs/dev/architecture.md) - How the engine works
411
+ Output is in `dist/` - a single HTML file with embedded JavaScript that can be hosted anywhere.
388
412
 
389
413
  ## License
390
414