@stevejtrettel/shader-sandbox 0.1.0

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 (106) hide show
  1. package/README.md +391 -0
  2. package/bin/cli.js +389 -0
  3. package/dist-lib/app/App.d.ts +134 -0
  4. package/dist-lib/app/App.d.ts.map +1 -0
  5. package/dist-lib/app/App.js +570 -0
  6. package/dist-lib/app/types.d.ts +32 -0
  7. package/dist-lib/app/types.d.ts.map +1 -0
  8. package/dist-lib/app/types.js +6 -0
  9. package/dist-lib/editor/EditorPanel.d.ts +39 -0
  10. package/dist-lib/editor/EditorPanel.d.ts.map +1 -0
  11. package/dist-lib/editor/EditorPanel.js +274 -0
  12. package/dist-lib/editor/prism-editor.css +99 -0
  13. package/dist-lib/editor/prism-editor.d.ts +19 -0
  14. package/dist-lib/editor/prism-editor.d.ts.map +1 -0
  15. package/dist-lib/editor/prism-editor.js +96 -0
  16. package/dist-lib/embed.d.ts +17 -0
  17. package/dist-lib/embed.d.ts.map +1 -0
  18. package/dist-lib/embed.js +35 -0
  19. package/dist-lib/engine/ShadertoyEngine.d.ts +160 -0
  20. package/dist-lib/engine/ShadertoyEngine.d.ts.map +1 -0
  21. package/dist-lib/engine/ShadertoyEngine.js +704 -0
  22. package/dist-lib/engine/glHelpers.d.ts +79 -0
  23. package/dist-lib/engine/glHelpers.d.ts.map +1 -0
  24. package/dist-lib/engine/glHelpers.js +298 -0
  25. package/dist-lib/engine/types.d.ts +77 -0
  26. package/dist-lib/engine/types.d.ts.map +1 -0
  27. package/dist-lib/engine/types.js +7 -0
  28. package/dist-lib/index.d.ts +12 -0
  29. package/dist-lib/index.d.ts.map +1 -0
  30. package/dist-lib/index.js +9 -0
  31. package/dist-lib/layouts/DefaultLayout.d.ts +17 -0
  32. package/dist-lib/layouts/DefaultLayout.d.ts.map +1 -0
  33. package/dist-lib/layouts/DefaultLayout.js +27 -0
  34. package/dist-lib/layouts/FullscreenLayout.d.ts +17 -0
  35. package/dist-lib/layouts/FullscreenLayout.d.ts.map +1 -0
  36. package/dist-lib/layouts/FullscreenLayout.js +27 -0
  37. package/dist-lib/layouts/SplitLayout.d.ts +26 -0
  38. package/dist-lib/layouts/SplitLayout.d.ts.map +1 -0
  39. package/dist-lib/layouts/SplitLayout.js +61 -0
  40. package/dist-lib/layouts/TabbedLayout.d.ts +38 -0
  41. package/dist-lib/layouts/TabbedLayout.d.ts.map +1 -0
  42. package/dist-lib/layouts/TabbedLayout.js +305 -0
  43. package/dist-lib/layouts/index.d.ts +24 -0
  44. package/dist-lib/layouts/index.d.ts.map +1 -0
  45. package/dist-lib/layouts/index.js +36 -0
  46. package/dist-lib/layouts/split.css +196 -0
  47. package/dist-lib/layouts/tabbed.css +345 -0
  48. package/dist-lib/layouts/types.d.ts +48 -0
  49. package/dist-lib/layouts/types.d.ts.map +1 -0
  50. package/dist-lib/layouts/types.js +4 -0
  51. package/dist-lib/main.d.ts +15 -0
  52. package/dist-lib/main.d.ts.map +1 -0
  53. package/dist-lib/main.js +102 -0
  54. package/dist-lib/project/generatedLoader.d.ts +3 -0
  55. package/dist-lib/project/generatedLoader.d.ts.map +1 -0
  56. package/dist-lib/project/generatedLoader.js +17 -0
  57. package/dist-lib/project/loadProject.d.ts +22 -0
  58. package/dist-lib/project/loadProject.d.ts.map +1 -0
  59. package/dist-lib/project/loadProject.js +350 -0
  60. package/dist-lib/project/loaderHelper.d.ts +7 -0
  61. package/dist-lib/project/loaderHelper.d.ts.map +1 -0
  62. package/dist-lib/project/loaderHelper.js +240 -0
  63. package/dist-lib/project/types.d.ts +192 -0
  64. package/dist-lib/project/types.d.ts.map +1 -0
  65. package/dist-lib/project/types.js +7 -0
  66. package/dist-lib/styles/base.css +29 -0
  67. package/package.json +48 -0
  68. package/src/app/App.ts +699 -0
  69. package/src/app/app.css +208 -0
  70. package/src/app/types.ts +36 -0
  71. package/src/editor/EditorPanel.ts +340 -0
  72. package/src/editor/editor-panel.css +175 -0
  73. package/src/editor/prism-editor.css +99 -0
  74. package/src/editor/prism-editor.ts +124 -0
  75. package/src/embed.ts +55 -0
  76. package/src/engine/ShadertoyEngine.ts +929 -0
  77. package/src/engine/glHelpers.ts +432 -0
  78. package/src/engine/types.ts +118 -0
  79. package/src/index.ts +13 -0
  80. package/src/layouts/DefaultLayout.ts +40 -0
  81. package/src/layouts/FullscreenLayout.ts +40 -0
  82. package/src/layouts/SplitLayout.ts +81 -0
  83. package/src/layouts/TabbedLayout.ts +371 -0
  84. package/src/layouts/default.css +22 -0
  85. package/src/layouts/fullscreen.css +15 -0
  86. package/src/layouts/index.ts +44 -0
  87. package/src/layouts/split.css +196 -0
  88. package/src/layouts/tabbed.css +345 -0
  89. package/src/layouts/types.ts +58 -0
  90. package/src/main.ts +114 -0
  91. package/src/project/generatedLoader.ts +23 -0
  92. package/src/project/loadProject.ts +421 -0
  93. package/src/project/loaderHelper.ts +300 -0
  94. package/src/project/types.ts +243 -0
  95. package/src/styles/base.css +29 -0
  96. package/src/styles/embed.css +14 -0
  97. package/src/vite-env.d.ts +1 -0
  98. package/templates/index.html +28 -0
  99. package/templates/main.ts +126 -0
  100. package/templates/package.json +12 -0
  101. package/templates/shaders/example-buffer/bufferA.glsl +14 -0
  102. package/templates/shaders/example-buffer/config.json +10 -0
  103. package/templates/shaders/example-buffer/image.glsl +5 -0
  104. package/templates/shaders/example-gradient/config.json +4 -0
  105. package/templates/shaders/example-gradient/image.glsl +7 -0
  106. package/templates/vite.config.js +35 -0
package/README.md ADDED
@@ -0,0 +1,391 @@
1
+ # Shader Sandbox
2
+
3
+ A lightweight, Shadertoy-compatible GLSL shader playground built for teaching and learning shader programming.
4
+
5
+ ## Features
6
+
7
+ - **Shadertoy Compatibility** - Copy/paste shaders directly from Shadertoy
8
+ - **Full Shadertoy Uniforms** - `iTime`, `iResolution`, `iFrame`, `iMouse`, `iTimeDelta`, `iChannel0-3`
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
12
+ - **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)
16
+
17
+ ## Quick Start
18
+
19
+ ```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
30
+
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
38
+ cd my-shaders
39
+ shader dev example-gradient
40
+ ```
41
+
42
+ That's it! The `create` command sets up the directory, installs dependencies, and creates example shaders.
43
+
44
+ ### CLI Commands
45
+
46
+ ```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
51
+ shader build <name> # Build shader for production
52
+ shader new <name> # Create a new shader
53
+ ```
54
+
55
+ ### Project Structure
56
+
57
+ After `shader create`:
58
+
59
+ ```
60
+ my-shaders/
61
+ ├── shaders/
62
+ │ ├── example-gradient/
63
+ │ │ ├── image.glsl # Main shader
64
+ │ │ └── config.json # Optional config
65
+ │ └── example-buffer/
66
+ │ ├── image.glsl
67
+ │ ├── bufferA.glsl # Feedback buffer
68
+ │ └── config.json
69
+ ├── main.ts
70
+ ├── vite.config.js
71
+ └── package.json
72
+ ```
73
+
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
+ ```
83
+
84
+ ---
85
+
86
+ ## Common Setups
87
+
88
+ ### 1. Simple Shader (just image.glsl)
89
+
90
+ The simplest setup - no config needed.
91
+
92
+ ```bash
93
+ npm run new my-shader
94
+ ```
95
+
96
+ **Files:**
97
+ ```
98
+ demos/my-shader/
99
+ └── image.glsl
100
+ ```
101
+
102
+ **image.glsl:**
103
+ ```glsl
104
+ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
105
+ vec2 uv = fragCoord / iResolution.xy;
106
+ vec3 col = 0.5 + 0.5 * cos(iTime + uv.xyx + vec3(0, 2, 4));
107
+ fragColor = vec4(col, 1.0);
108
+ }
109
+ ```
110
+
111
+ ---
112
+
113
+ ### 2. One Buffer (feedback/trails)
114
+
115
+ For effects that accumulate over time (trails, paint, fluid).
116
+
117
+ ```bash
118
+ npm run new my-shader 1
119
+ ```
120
+
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
+ ```
140
+
141
+ **bufferA.glsl:**
142
+ ```glsl
143
+ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
144
+ vec2 uv = fragCoord / iResolution.xy;
145
+
146
+ // Read previous frame with fade
147
+ vec4 prev = texture(iChannel0, uv) * 0.98;
148
+
149
+ // Draw at mouse
150
+ vec2 mouse = iMouse.xy / iResolution.xy;
151
+ float d = length(uv - mouse);
152
+ float spot = smoothstep(0.05, 0.0, d);
153
+
154
+ fragColor = prev + vec4(spot);
155
+ }
156
+ ```
157
+
158
+ **image.glsl:**
159
+ ```glsl
160
+ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
161
+ vec2 uv = fragCoord / iResolution.xy;
162
+ fragColor = texture(iChannel0, uv);
163
+ }
164
+ ```
165
+
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:**
186
+ ```json
187
+ {
188
+ "BufferA": {
189
+ "iChannel0": "BufferA",
190
+ "iChannel1": "BufferB"
191
+ },
192
+ "BufferB": {
193
+ "iChannel0": "BufferA",
194
+ "iChannel1": "BufferB"
195
+ },
196
+ "Image": {
197
+ "iChannel0": "BufferA",
198
+ "iChannel1": "BufferB"
199
+ }
200
+ }
201
+ ```
202
+
203
+ **Channel mapping:** `iChannel0` = BufferA, `iChannel1` = BufferB, etc.
204
+
205
+ ---
206
+
207
+ ### 4. Texture + Image (image processing)
208
+
209
+ Load an image and process it.
210
+
211
+ **Files:**
212
+ ```
213
+ demos/my-shader/
214
+ ├── image.glsl
215
+ ├── photo.jpg
216
+ └── config.json
217
+ ```
218
+
219
+ **config.json:**
220
+ ```json
221
+ {
222
+ "Image": {
223
+ "iChannel0": "photo.jpg"
224
+ }
225
+ }
226
+ ```
227
+
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);
233
+
234
+ // Example: grayscale
235
+ float gray = dot(img.rgb, vec3(0.299, 0.587, 0.114));
236
+
237
+ fragColor = vec4(vec3(gray), 1.0);
238
+ }
239
+ ```
240
+
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)
248
+
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
+ ```glsl
254
+ // In your shader, sample with a 3D direction:
255
+ vec3 dir = normalize(rayDirection);
256
+ vec4 sky = texture(iChannel0, dir); // Automatically converted
257
+ ```
258
+
259
+ ---
260
+
261
+ ### 5. Texture + Buffers (image + feedback)
262
+
263
+ Combine textures with buffer feedback for effects like painting on an image.
264
+
265
+ **Files:**
266
+ ```
267
+ demos/my-shader/
268
+ ├── bufferA.glsl
269
+ ├── image.glsl
270
+ ├── photo.jpg
271
+ └── config.json
272
+ ```
273
+
274
+ **config.json:**
275
+ ```json
276
+ {
277
+ "BufferA": {
278
+ "iChannel0": "BufferA",
279
+ "iChannel1": "photo.jpg"
280
+ },
281
+ "Image": {
282
+ "iChannel0": "BufferA",
283
+ "iChannel1": "photo.jpg"
284
+ }
285
+ }
286
+ ```
287
+
288
+ **bufferA.glsl:**
289
+ ```glsl
290
+ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
291
+ vec2 uv = fragCoord / iResolution.xy;
292
+
293
+ vec4 prev = texture(iChannel0, uv); // Previous frame
294
+ vec4 img = texture(iChannel1, uv); // Original image
295
+
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);
300
+
301
+ // Blend: painted areas persist, unpainted fade to original
302
+ fragColor = mix(mix(prev, img, 0.01), prev + brush, brush);
303
+ }
304
+ ```
305
+
306
+ ---
307
+
308
+ ## Buffer Execution & Frame Timing
309
+
310
+ **Execution order:** BufferA → BufferB → BufferC → BufferD → Image
311
+
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)
316
+
317
+ Use `{ "buffer": "BufferA", "current": true }` only if you specifically need the in-progress current frame (rare).
318
+
319
+ ---
320
+
321
+ ## Layouts
322
+
323
+ Control how the shader is displayed with the `layout` option in `config.json`:
324
+
325
+ ```json
326
+ {
327
+ "layout": "split",
328
+ "BufferA": { ... },
329
+ "Image": { ... }
330
+ }
331
+ ```
332
+
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 |
339
+
340
+ **`fullscreen`** - No chrome, canvas fills the screen:
341
+ ```json
342
+ { "layout": "fullscreen" }
343
+ ```
344
+
345
+ **`default`** - Clean centered view with rounded corners:
346
+ ```json
347
+ { "layout": "default" }
348
+ ```
349
+
350
+ **`tabbed`** - Click tabs to switch between live shader and source code:
351
+ ```json
352
+ { "layout": "tabbed" }
353
+ ```
354
+
355
+ **`split`** - See shader and code simultaneously (code panel has tabs for multi-file projects):
356
+ ```json
357
+ { "layout": "split" }
358
+ ```
359
+
360
+ ---
361
+
362
+ ## Keyboard Shortcuts
363
+
364
+ | Key | Action |
365
+ |-----|--------|
366
+ | **S** | Save screenshot (PNG) |
367
+ | **Space** | Play/Pause (when controls enabled) |
368
+ | **R** | Reset to frame 0 (when controls enabled) |
369
+
370
+ ---
371
+
372
+ ## NPM Scripts
373
+
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/
378
+ ```
379
+
380
+ ---
381
+
382
+ ## Documentation
383
+
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
388
+
389
+ ## License
390
+
391
+ MIT