@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
@@ -0,0 +1,432 @@
1
+ /**
2
+ * Engine Layer - WebGL Helper Functions
3
+ *
4
+ * Low-level WebGL utilities for shader compilation, texture creation,
5
+ * framebuffer management, and VAO setup.
6
+ *
7
+ * Based on docs/engine-spec.md
8
+ */
9
+
10
+ // =============================================================================
11
+ // Shader Compilation
12
+ // =============================================================================
13
+
14
+ /**
15
+ * Compile a shader of given type from source. Throws on error.
16
+ */
17
+ export function createShader(
18
+ gl: WebGL2RenderingContext,
19
+ type: GLenum,
20
+ source: string
21
+ ): WebGLShader {
22
+ const shader = gl.createShader(type);
23
+ if (!shader) {
24
+ throw new Error('Failed to create shader object');
25
+ }
26
+
27
+ gl.shaderSource(shader, source);
28
+ gl.compileShader(shader);
29
+
30
+ const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
31
+ if (!success) {
32
+ const infoLog = gl.getShaderInfoLog(shader);
33
+ gl.deleteShader(shader);
34
+ throw new Error(`Shader compilation failed:\n${infoLog}`);
35
+ }
36
+
37
+ return shader;
38
+ }
39
+
40
+ /**
41
+ * Link a program from vertex + fragment source strings.
42
+ * Throws with a descriptive error if link fails.
43
+ */
44
+ export function createProgramFromSources(
45
+ gl: WebGL2RenderingContext,
46
+ vertexSource: string,
47
+ fragmentSource: string
48
+ ): WebGLProgram {
49
+ const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexSource);
50
+ const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource);
51
+
52
+ const program = gl.createProgram();
53
+ if (!program) {
54
+ throw new Error('Failed to create program object');
55
+ }
56
+
57
+ gl.attachShader(program, vertexShader);
58
+ gl.attachShader(program, fragmentShader);
59
+ gl.linkProgram(program);
60
+
61
+ const success = gl.getProgramParameter(program, gl.LINK_STATUS);
62
+ if (!success) {
63
+ const infoLog = gl.getProgramInfoLog(program);
64
+ gl.deleteProgram(program);
65
+ gl.deleteShader(vertexShader);
66
+ gl.deleteShader(fragmentShader);
67
+ throw new Error(`Program linking failed:\n${infoLog}`);
68
+ }
69
+
70
+ // Clean up shaders (no longer needed after linking)
71
+ gl.detachShader(program, vertexShader);
72
+ gl.detachShader(program, fragmentShader);
73
+ gl.deleteShader(vertexShader);
74
+ gl.deleteShader(fragmentShader);
75
+
76
+ return program;
77
+ }
78
+
79
+ // =============================================================================
80
+ // Fullscreen Triangle VAO
81
+ // =============================================================================
82
+
83
+ /**
84
+ * Create a full-screen triangle VAO.
85
+ *
86
+ * Attribute layout:
87
+ * - location 0: vec2 position in clip space.
88
+ *
89
+ * The triangle covers the entire screen using three vertices:
90
+ * (-1, -1), (3, -1), (-1, 3)
91
+ * This ensures all pixels are covered without needing two triangles.
92
+ */
93
+ export function createFullscreenTriangleVAO(gl: WebGL2RenderingContext): WebGLVertexArrayObject {
94
+ const vao = gl.createVertexArray();
95
+ if (!vao) {
96
+ throw new Error('Failed to create VAO');
97
+ }
98
+
99
+ gl.bindVertexArray(vao);
100
+
101
+ // Create VBO with triangle vertices
102
+ const positions = new Float32Array([
103
+ -1, -1, // Bottom-left
104
+ 3, -1, // Bottom-right (extends beyond viewport)
105
+ -1, 3, // Top-left (extends beyond viewport)
106
+ ]);
107
+
108
+ const vbo = gl.createBuffer();
109
+ if (!vbo) {
110
+ throw new Error('Failed to create VBO');
111
+ }
112
+
113
+ gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
114
+ gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
115
+
116
+ // Enable and configure vertex attribute at location 0
117
+ gl.enableVertexAttribArray(0);
118
+ gl.vertexAttribPointer(
119
+ 0, // attribute location
120
+ 2, // size (vec2)
121
+ gl.FLOAT, // type
122
+ false, // normalized
123
+ 0, // stride
124
+ 0 // offset
125
+ );
126
+
127
+ // Unbind
128
+ gl.bindVertexArray(null);
129
+ gl.bindBuffer(gl.ARRAY_BUFFER, null);
130
+
131
+ return vao;
132
+ }
133
+
134
+ // =============================================================================
135
+ // Texture Creation
136
+ // =============================================================================
137
+
138
+ /**
139
+ * Create a float RGBA texture (no data) for use as a render target.
140
+ * This MUST use an internal format compatible with EXT_color_buffer_float.
141
+ *
142
+ * Per spec: ALL render targets MUST be RGBA32F.
143
+ */
144
+ export function createRenderTargetTexture(
145
+ gl: WebGL2RenderingContext,
146
+ width: number,
147
+ height: number
148
+ ): WebGLTexture {
149
+ const tex = gl.createTexture();
150
+ if (!tex) {
151
+ throw new Error('Failed to create texture');
152
+ }
153
+
154
+ gl.bindTexture(gl.TEXTURE_2D, tex);
155
+
156
+ // Allocate RGBA32F texture (float format required by Shadertoy spec)
157
+ gl.texImage2D(
158
+ gl.TEXTURE_2D,
159
+ 0, // mip level
160
+ gl.RGBA32F, // internal format (32-bit float per channel)
161
+ width,
162
+ height,
163
+ 0, // border (must be 0)
164
+ gl.RGBA, // format
165
+ gl.FLOAT, // type
166
+ null // no data (allocate only)
167
+ );
168
+
169
+ // Set filtering to NEAREST (common for simulation/compute shaders)
170
+ // This prevents interpolation artifacts in PDE/physics shaders
171
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
172
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
173
+
174
+ // Set wrap mode to CLAMP_TO_EDGE (prevent wrap-around at boundaries)
175
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
176
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
177
+
178
+ gl.bindTexture(gl.TEXTURE_2D, null);
179
+
180
+ return tex;
181
+ }
182
+
183
+ /**
184
+ * Create a framebuffer with a single color attachment at COLOR_ATTACHMENT0.
185
+ * Throws if framebuffer is not complete.
186
+ */
187
+ export function createFramebufferWithColorAttachment(
188
+ gl: WebGL2RenderingContext,
189
+ texture: WebGLTexture
190
+ ): WebGLFramebuffer {
191
+ const fbo = gl.createFramebuffer();
192
+ if (!fbo) {
193
+ throw new Error('Failed to create framebuffer');
194
+ }
195
+
196
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
197
+
198
+ // Attach texture to color attachment 0
199
+ gl.framebufferTexture2D(
200
+ gl.FRAMEBUFFER,
201
+ gl.COLOR_ATTACHMENT0,
202
+ gl.TEXTURE_2D,
203
+ texture,
204
+ 0 // mip level
205
+ );
206
+
207
+ // Check framebuffer completeness
208
+ const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
209
+ if (status !== gl.FRAMEBUFFER_COMPLETE) {
210
+ gl.deleteFramebuffer(fbo);
211
+ throw new Error(`Framebuffer incomplete: ${getFramebufferStatusString(gl, status)}`);
212
+ }
213
+
214
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
215
+
216
+ return fbo;
217
+ }
218
+
219
+ /**
220
+ * Create a 1x1 black float texture for unused channels.
221
+ *
222
+ * Per spec: UNUSED CHANNELS MUST STILL BIND A 1×1 BLACK FLOAT TEXTURE.
223
+ * This prevents NaN/undefined behavior when sampling unused channels.
224
+ */
225
+ export function createBlackTexture(gl: WebGL2RenderingContext): WebGLTexture {
226
+ const tex = gl.createTexture();
227
+ if (!tex) {
228
+ throw new Error('Failed to create black texture');
229
+ }
230
+
231
+ gl.bindTexture(gl.TEXTURE_2D, tex);
232
+
233
+ // 1x1 black pixel [0, 0, 0, 1] as float
234
+ const blackPixel = new Float32Array([0, 0, 0, 1]);
235
+
236
+ gl.texImage2D(
237
+ gl.TEXTURE_2D,
238
+ 0,
239
+ gl.RGBA32F,
240
+ 1,
241
+ 1,
242
+ 0,
243
+ gl.RGBA,
244
+ gl.FLOAT,
245
+ blackPixel
246
+ );
247
+
248
+ // Set filtering
249
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
250
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
251
+
252
+ // Set wrap mode
253
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
254
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
255
+
256
+ gl.bindTexture(gl.TEXTURE_2D, null);
257
+
258
+ return tex;
259
+ }
260
+
261
+ /**
262
+ * Create a 256x3 keyboard state texture.
263
+ *
264
+ * Shadertoy keyboard texture format:
265
+ * - Width: 256 (one column per ASCII keycode)
266
+ * - Height: 3 (3 rows for different data)
267
+ * - Row 0 (sample at y=0.25): Current key state (0.0 = up, 1.0 = down)
268
+ * - Row 1: Unused
269
+ * - Row 2 (sample at y=0.75): Toggle state (flips between 0.0 and 1.0 on each press)
270
+ *
271
+ * Returns the WebGLTexture. Data is initialized to all zeros.
272
+ * Use updateKeyboardTexture() to update the state.
273
+ */
274
+ export function createKeyboardTexture(gl: WebGL2RenderingContext): WebGLTexture {
275
+ const tex = gl.createTexture();
276
+ if (!tex) {
277
+ throw new Error('Failed to create keyboard texture');
278
+ }
279
+
280
+ gl.bindTexture(gl.TEXTURE_2D, tex);
281
+
282
+ // 256x3 texture, all zeros initially
283
+ const width = 256;
284
+ const height = 3;
285
+ const data = new Float32Array(width * height * 4); // RGBA, all zeros
286
+
287
+ gl.texImage2D(
288
+ gl.TEXTURE_2D,
289
+ 0,
290
+ gl.RGBA32F,
291
+ width,
292
+ height,
293
+ 0,
294
+ gl.RGBA,
295
+ gl.FLOAT,
296
+ data
297
+ );
298
+
299
+ // NEAREST filtering - no interpolation between keys!
300
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
301
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
302
+
303
+ // CLAMP to edge
304
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
305
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
306
+
307
+ gl.bindTexture(gl.TEXTURE_2D, null);
308
+
309
+ return tex;
310
+ }
311
+
312
+ /**
313
+ * Update keyboard texture with current key states.
314
+ *
315
+ * @param gl WebGL context
316
+ * @param texture The keyboard texture to update
317
+ * @param keyStates Map of keycode -> current state (true = down, false = up)
318
+ * @param toggleStates Map of keycode -> toggle state (0.0 or 1.0)
319
+ */
320
+ export function updateKeyboardTexture(
321
+ gl: WebGL2RenderingContext,
322
+ texture: WebGLTexture,
323
+ keyStates: Map<number, boolean>,
324
+ toggleStates: Map<number, number>
325
+ ): void {
326
+ const width = 256;
327
+ const height = 3;
328
+ const data = new Float32Array(width * height * 4);
329
+
330
+ // Fill in key states
331
+ for (let keycode = 0; keycode < 256; keycode++) {
332
+ const isDown = keyStates.get(keycode) || false;
333
+ const toggleValue = toggleStates.get(keycode) || 0.0;
334
+
335
+ // Row 0 (y=0): Current key state
336
+ const row0Index = (0 * width + keycode) * 4;
337
+ data[row0Index + 0] = isDown ? 1.0 : 0.0; // R channel
338
+ data[row0Index + 1] = isDown ? 1.0 : 0.0; // G channel (redundant but matches Shadertoy)
339
+ data[row0Index + 2] = isDown ? 1.0 : 0.0; // B channel
340
+ data[row0Index + 3] = 1.0; // A channel
341
+
342
+ // Row 1 (y=1): Unused (keep as zeros)
343
+
344
+ // Row 2 (y=2): Toggle state
345
+ const row2Index = (2 * width + keycode) * 4;
346
+ data[row2Index + 0] = toggleValue;
347
+ data[row2Index + 1] = toggleValue;
348
+ data[row2Index + 2] = toggleValue;
349
+ data[row2Index + 3] = 1.0;
350
+ }
351
+
352
+ gl.bindTexture(gl.TEXTURE_2D, texture);
353
+ gl.texSubImage2D(
354
+ gl.TEXTURE_2D,
355
+ 0,
356
+ 0, 0, // x, y offset
357
+ width, height,
358
+ gl.RGBA,
359
+ gl.FLOAT,
360
+ data
361
+ );
362
+ gl.bindTexture(gl.TEXTURE_2D, null);
363
+ }
364
+
365
+ /**
366
+ * Create a 2D texture from an HTMLImageElement (or ImageBitmap).
367
+ * This is used for project textures (dog.png, noise.png, etc.)
368
+ *
369
+ * NOTE: actual image loading is done by the App; engine just gets an
370
+ * already-loaded image object.
371
+ */
372
+ export function createTextureFromImage(
373
+ gl: WebGL2RenderingContext,
374
+ image: HTMLImageElement | ImageBitmap,
375
+ filter: 'nearest' | 'linear',
376
+ wrap: 'clamp' | 'repeat'
377
+ ): WebGLTexture {
378
+ const tex = gl.createTexture();
379
+ if (!tex) {
380
+ throw new Error('Failed to create texture');
381
+ }
382
+
383
+ gl.bindTexture(gl.TEXTURE_2D, tex);
384
+
385
+ // Upload image data
386
+ gl.texImage2D(
387
+ gl.TEXTURE_2D,
388
+ 0,
389
+ gl.RGBA,
390
+ gl.RGBA,
391
+ gl.UNSIGNED_BYTE,
392
+ image
393
+ );
394
+
395
+ // Set filtering
396
+ const glFilter = filter === 'nearest' ? gl.NEAREST : gl.LINEAR;
397
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, glFilter);
398
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, glFilter);
399
+
400
+ // Set wrap mode
401
+ const glWrap = wrap === 'clamp' ? gl.CLAMP_TO_EDGE : gl.REPEAT;
402
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, glWrap);
403
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, glWrap);
404
+
405
+ gl.bindTexture(gl.TEXTURE_2D, null);
406
+
407
+ return tex;
408
+ }
409
+
410
+ // =============================================================================
411
+ // Helper Utilities
412
+ // =============================================================================
413
+
414
+ /**
415
+ * Get a human-readable string for framebuffer status.
416
+ */
417
+ function getFramebufferStatusString(gl: WebGL2RenderingContext, status: GLenum): string {
418
+ switch (status) {
419
+ case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
420
+ return 'FRAMEBUFFER_INCOMPLETE_ATTACHMENT';
421
+ case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
422
+ return 'FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT';
423
+ case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
424
+ return 'FRAMEBUFFER_INCOMPLETE_DIMENSIONS';
425
+ case gl.FRAMEBUFFER_UNSUPPORTED:
426
+ return 'FRAMEBUFFER_UNSUPPORTED';
427
+ case gl.FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
428
+ return 'FRAMEBUFFER_INCOMPLETE_MULTISAMPLE';
429
+ default:
430
+ return `Unknown status: ${status}`;
431
+ }
432
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Engine Layer - Type Definitions
3
+ *
4
+ * Internal types used by ShadertoyEngine for managing WebGL resources.
5
+ * Based on docs/engine-spec.md
6
+ */
7
+
8
+ import type {
9
+ ShadertoyProject,
10
+ PassName,
11
+ Channels,
12
+ } from '../project/types';
13
+
14
+ // =============================================================================
15
+ // Engine Options
16
+ // =============================================================================
17
+
18
+ /**
19
+ * Options for constructing a ShadertoyEngine.
20
+ *
21
+ * The App is responsible for creating the WebGL2RenderingContext
22
+ * and passing it in.
23
+ */
24
+ export interface EngineOptions {
25
+ gl: WebGL2RenderingContext;
26
+ project: ShadertoyProject;
27
+ }
28
+
29
+ // =============================================================================
30
+ // Per-Pass Uniform Locations
31
+ // =============================================================================
32
+
33
+ /**
34
+ * Per-pass uniform locations and metadata.
35
+ *
36
+ * NOTE: This is separate from RuntimePass so that we can keep
37
+ * the GL program + locations together.
38
+ */
39
+ export interface PassUniformLocations {
40
+ program: WebGLProgram;
41
+
42
+ // Core Shadertoy uniforms
43
+ iResolution: WebGLUniformLocation | null;
44
+ iTime: WebGLUniformLocation | null;
45
+ iTimeDelta: WebGLUniformLocation | null;
46
+ iFrame: WebGLUniformLocation | null;
47
+ iMouse: WebGLUniformLocation | null;
48
+
49
+ // iChannel0..3
50
+ iChannel: (WebGLUniformLocation | null)[];
51
+ }
52
+
53
+ // =============================================================================
54
+ // Runtime Pass Representation
55
+ // =============================================================================
56
+
57
+ /**
58
+ * A runtime representation of a pass:
59
+ * - knows which project pass it corresponds to
60
+ * - owns two textures (current + previous) for ping-pong
61
+ * - owns a framebuffer and VAO for drawing
62
+ */
63
+ export interface RuntimePass {
64
+ name: PassName;
65
+ projectChannels: Channels;
66
+
67
+ vao: WebGLVertexArrayObject;
68
+ uniforms: PassUniformLocations;
69
+
70
+ framebuffer: WebGLFramebuffer;
71
+
72
+ // Ping-pong textures:
73
+ // - current: where we write this frame
74
+ // - previous: where we read "previous" from when needed
75
+ currentTexture: WebGLTexture;
76
+ previousTexture: WebGLTexture;
77
+ }
78
+
79
+ // =============================================================================
80
+ // Runtime Texture Representation
81
+ // =============================================================================
82
+
83
+ /**
84
+ * Runtime representation of an external 2D texture.
85
+ * This corresponds 1:1 to ShadertoyTexture2D from the project.
86
+ */
87
+ export interface RuntimeTexture2D {
88
+ name: string; // e.g. "tex0" (same as project texture name)
89
+ texture: WebGLTexture;
90
+ width: number;
91
+ height: number;
92
+ }
93
+
94
+ /**
95
+ * Keyboard texture representation.
96
+ * For v1, you can leave this unimplemented or just stub it out.
97
+ */
98
+ export interface RuntimeKeyboardTexture {
99
+ texture: WebGLTexture;
100
+ width: number;
101
+ height: number;
102
+ }
103
+
104
+ // =============================================================================
105
+ // Engine Stats
106
+ // =============================================================================
107
+
108
+ /**
109
+ * Engine stats (for optional overlay / debugging).
110
+ */
111
+ export interface EngineStats {
112
+ frame: number; // iFrame
113
+ time: number; // total time in seconds (iTime)
114
+ deltaTime: number; // last frame delta in seconds (iTimeDelta)
115
+ width: number;
116
+ height: number;
117
+ }
118
+
package/src/index.ts ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Shadertoy Runner - Public API
3
+ *
4
+ * This module exports everything needed to create a shader playground.
5
+ */
6
+
7
+ import './styles/base.css';
8
+
9
+ export { App } from './app/App';
10
+ export { createLayout } from './layouts';
11
+ export { loadDemo } from './project/loaderHelper';
12
+ export type { ShadertoyProject, ShadertoyConfig, PassName } from './project/types';
13
+ export type { RecompileResult, BaseLayout, LayoutMode, LayoutOptions, RecompileHandler } from './layouts/types';
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Default Layout
3
+ *
4
+ * Centered canvas with rounded corners and drop shadow.
5
+ * Default layout mode for a polished presentation.
6
+ */
7
+
8
+ import './default.css';
9
+
10
+ import { BaseLayout, LayoutOptions } from './types';
11
+
12
+ export class DefaultLayout implements BaseLayout {
13
+ private container: HTMLElement;
14
+ private root: HTMLElement;
15
+ private canvasContainer: HTMLElement;
16
+
17
+ constructor(opts: LayoutOptions) {
18
+ this.container = opts.container;
19
+
20
+ // Create root layout container
21
+ this.root = document.createElement('div');
22
+ this.root.className = 'layout-default';
23
+
24
+ // Create canvas container
25
+ this.canvasContainer = document.createElement('div');
26
+ this.canvasContainer.className = 'canvas-container';
27
+
28
+ // Assemble and append to DOM
29
+ this.root.appendChild(this.canvasContainer);
30
+ this.container.appendChild(this.root);
31
+ }
32
+
33
+ getCanvasContainer(): HTMLElement {
34
+ return this.canvasContainer;
35
+ }
36
+
37
+ dispose(): void {
38
+ this.container.innerHTML = '';
39
+ }
40
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Fullscreen Layout
3
+ *
4
+ * Canvas fills entire viewport, no padding or styling.
5
+ * Used for immersive shader experiences.
6
+ */
7
+
8
+ import './fullscreen.css';
9
+
10
+ import { BaseLayout, LayoutOptions } from './types';
11
+
12
+ export class FullscreenLayout implements BaseLayout {
13
+ private container: HTMLElement;
14
+ private root: HTMLElement;
15
+ private canvasContainer: HTMLElement;
16
+
17
+ constructor(opts: LayoutOptions) {
18
+ this.container = opts.container;
19
+
20
+ // Create root layout container
21
+ this.root = document.createElement('div');
22
+ this.root.className = 'layout-fullscreen';
23
+
24
+ // Create canvas container
25
+ this.canvasContainer = document.createElement('div');
26
+ this.canvasContainer.className = 'canvas-container';
27
+
28
+ // Assemble and append to DOM
29
+ this.root.appendChild(this.canvasContainer);
30
+ this.container.appendChild(this.root);
31
+ }
32
+
33
+ getCanvasContainer(): HTMLElement {
34
+ return this.canvasContainer;
35
+ }
36
+
37
+ dispose(): void {
38
+ this.container.innerHTML = '';
39
+ }
40
+ }