wgsl-play 0.0.38 → 0.0.40

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 (57) hide show
  1. package/README.md +169 -6
  2. package/dist/{WgslPlay-uTIOrTQq.d.ts → WgslPlay-ouKx64GN.d.ts} +73 -2
  3. package/dist/WgslPlay-zK9mE5rV.js +1544 -0
  4. package/dist/WgslPlay.d.ts +2 -2
  5. package/dist/WgslPlay.js +2 -2
  6. package/dist/index.d.ts +2 -2
  7. package/dist/index.js +2 -2
  8. package/dist/jsx-preact.d.ts +12 -0
  9. package/dist/jsx-preact.js +0 -0
  10. package/dist/wgsl-play.js +2573 -754
  11. package/package.json +12 -7
  12. package/src/CanvasSize.ts +27 -0
  13. package/src/ComputeBuild.ts +151 -0
  14. package/src/FragmentRender.ts +147 -0
  15. package/src/PlaybackControls.ts +36 -13
  16. package/src/RenderResources.ts +132 -0
  17. package/src/Renderer.ts +136 -101
  18. package/src/ResultsPanel.ts +261 -0
  19. package/src/UniformControls.ts +124 -0
  20. package/src/WgslPlay.css +212 -2
  21. package/src/WgslPlay.ts +524 -126
  22. package/src/icons/refresh.svg +1 -0
  23. package/src/jsx-preact.ts +15 -0
  24. package/src/test/BundleHydrator.test.ts +8 -11
  25. package/src/test/CanvasSize.test.ts +48 -0
  26. package/src/test/E2eHelpers.ts +117 -0
  27. package/src/test/RenderFrame.e2e.ts +33 -0
  28. package/src/test/ResultsPanel.test.ts +164 -0
  29. package/src/test/WgslPlay.basics.e2e.ts +87 -0
  30. package/src/test/WgslPlay.compute.e2e.ts +147 -0
  31. package/src/test/WgslPlay.connect.e2e.ts +101 -0
  32. package/src/test/WgslPlay.loading.e2e.ts +68 -0
  33. package/src/test/WgslPlay.resize.e2e.ts +199 -0
  34. package/src/test/WgslPlay.resources.e2e.ts +67 -0
  35. package/src/test/WgslPlay.resources.e2e.ts-snapshots/buffer-zero-init-chromium-darwin.png +0 -0
  36. package/src/test/WgslPlay.resources.e2e.ts-snapshots/texture-grid-chromium-darwin.png +0 -0
  37. package/src/test/WgslPlay.resources.e2e.ts-snapshots/texture-img-green-chromium-darwin.png +0 -0
  38. package/src/test/WgslPlay.resources.e2e.ts-snapshots/texture-img-magenta-chromium-darwin.png +0 -0
  39. package/src/test/WgslPlay.resources.e2e.ts-snapshots/texture-multi-chromium-darwin.png +0 -0
  40. package/src/test/WgslPlay.uniforms.e2e.ts +57 -0
  41. package/src/test/WgslPlay.uniforms.e2e.ts-snapshots/uniforms-initial-chromium-darwin.png +0 -0
  42. package/src/test/WgslPlay.uniforms.e2e.ts-snapshots/uniforms-slider-changed-chromium-darwin.png +0 -0
  43. package/dist/WgslPlay-BuIuE3cB.js +0 -620
  44. package/src/test/WgslPlay.e2e.ts +0 -277
  45. /package/src/test/{WgslPlay.e2e.ts-snapshots → WgslPlay.basics.e2e.ts-snapshots}/basic-shader-chromium-darwin.png +0 -0
  46. /package/src/test/{WgslPlay.e2e.ts-snapshots → WgslPlay.basics.e2e.ts-snapshots}/conditions-after-red-chromium-darwin.png +0 -0
  47. /package/src/test/{WgslPlay.e2e.ts-snapshots → WgslPlay.basics.e2e.ts-snapshots}/conditions-initial-green-chromium-darwin.png +0 -0
  48. /package/src/test/{WgslPlay.e2e.ts-snapshots → WgslPlay.connect.e2e.ts-snapshots}/connect-conditions-green-chromium-darwin.png +0 -0
  49. /package/src/test/{WgslPlay.e2e.ts-snapshots → WgslPlay.connect.e2e.ts-snapshots}/connect-conditions-red-chromium-darwin.png +0 -0
  50. /package/src/test/{WgslPlay.e2e.ts-snapshots → WgslPlay.connect.e2e.ts-snapshots}/connect-dynamic-npm-chromium-darwin.png +0 -0
  51. /package/src/test/{WgslPlay.e2e.ts-snapshots → WgslPlay.connect.e2e.ts-snapshots}/connect-source-external-chromium-darwin.png +0 -0
  52. /package/src/test/{WgslPlay.e2e.ts-snapshots → WgslPlay.connect.e2e.ts-snapshots}/connect-source-multifile-chromium-darwin.png +0 -0
  53. /package/src/test/{WgslPlay.e2e.ts-snapshots → WgslPlay.loading.e2e.ts-snapshots}/link-import-chromium-darwin.png +0 -0
  54. /package/src/test/{WgslPlay.e2e.ts-snapshots → WgslPlay.loading.e2e.ts-snapshots}/npm-cdn-chromium-darwin.png +0 -0
  55. /package/src/test/{WgslPlay.e2e.ts-snapshots → WgslPlay.loading.e2e.ts-snapshots}/shader-root-internal-chromium-darwin.png +0 -0
  56. /package/src/test/{WgslPlay.e2e.ts-snapshots → WgslPlay.loading.e2e.ts-snapshots}/shader-root-src-chromium-darwin.png +0 -0
  57. /package/src/test/{WgslPlay.e2e.ts-snapshots → WgslPlay.loading.e2e.ts-snapshots}/static-import-chromium-darwin.png +0 -0
package/README.md CHANGED
@@ -29,11 +29,150 @@ import env::u;
29
29
  }
30
30
  ```
31
31
 
32
- | Uniform | Type | Description |
33
- |---------|------|-------------|
34
- | `resolution` | `vec2f` | Canvas dimensions in pixels |
32
+ When no `@uniforms` struct is declared, a default is provided with `resolution` and `time`.
33
+
34
+ ### Custom Uniforms
35
+
36
+ Declare a struct with `@uniforms` to add your own fields with UI controls:
37
+
38
+ ```wgsl
39
+ import env::u;
40
+
41
+ @uniforms struct Params {
42
+ @auto resolution: vec2f,
43
+ @auto time: f32,
44
+ @range(1.0, 20.0, 5.0, 6.0) frequency: f32,
45
+ @color(0.2, 0.5, 1.0) tint: vec3f,
46
+ @toggle(0) invert: u32,
47
+ }
48
+
49
+ @fragment fn main(@builtin(position) pos: vec4f) -> @location(0) vec4f {
50
+ let wave = sin(pos.x * u.frequency + u.time);
51
+ var color = wave * u.tint;
52
+ if u.invert == 1u { color = 1.0 - color; }
53
+ return vec4f(color, 1.0);
54
+ }
55
+ ```
56
+
57
+ ### `@auto` -- Runtime Fields
58
+
59
+ The player fills these automatically each frame. The field name determines
60
+ which value is bound (or use `@auto(name)` when the field name differs):
61
+
62
+ | Name | Type | Description |
63
+ |------|------|-------------|
64
+ | `resolution` | `vec2f` | Canvas size in pixels |
35
65
  | `time` | `f32` | Elapsed time in seconds |
36
- | `mouse` | `vec2f` | Mouse position (normalized 0-1) |
66
+ | `delta_time` | `f32` | Delta time since last frame |
67
+ | `frame` | `u32` | Frame count |
68
+ | `mouse_pos` | `vec2f` | Pointer position in pixels |
69
+ | `mouse_delta` | `vec2f` | Pointer movement since last frame |
70
+ | `mouse_button` | `i32` | Active button: 0=none, 1=left, 2=middle, 3=right |
71
+
72
+ ### UI Annotations
73
+
74
+ These generate interactive controls in the player.
75
+
76
+ #### `@range(min, max [, step [, initial]])`
77
+
78
+ Slider for `f32` or `i32`. Step defaults to `0.01` for `f32`, `1` for `i32`.
79
+ Initial defaults to `min`.
80
+
81
+ ```wgsl
82
+ @range(1.0, 20.0) frequency: f32,
83
+ @range(1.0, 20.0, 5.0) frequency: f32, // step=5
84
+ @range(1.0, 20.0, 0.5, 5.0) frequency: f32, // step=0.5, initial=5
85
+ ```
86
+
87
+ #### `@color(r, g, b)`
88
+
89
+ Color picker for `vec3f`:
90
+
91
+ ```wgsl
92
+ @color(0.2, 0.5, 1.0) tint: vec3f,
93
+ ```
94
+
95
+ #### `@toggle([initial])`
96
+
97
+ Boolean toggle for `u32` (0 or 1). WGSL forbids `bool` in uniform buffers.
98
+
99
+ ```wgsl
100
+ @toggle invert: u32, // default=0
101
+ @toggle(1) invert: u32, // default=1
102
+ ```
103
+
104
+ ### Plain Fields
105
+
106
+ Fields without annotations are zero-initialized and settable from JavaScript
107
+ via `setUniform()`. This works before or after compilation.
108
+
109
+ ```wgsl
110
+ @uniforms struct Params {
111
+ @auto resolution: vec2f,
112
+ brightness: f32, // no annotation — set from JS
113
+ }
114
+ ```
115
+
116
+ ```javascript
117
+ const player = document.querySelector("wgsl-play");
118
+ player.setUniform("brightness", 0.8);
119
+ ```
120
+
121
+ ### Resource Annotations
122
+
123
+ Bind GPU resources by annotating shader globals. The component owns
124
+ `@group(0)` — `@binding(0)` is the uniform buffer, and annotated resources
125
+ take `@binding(1)` and onward in declaration order.
126
+
127
+ #### `@texture(name)` — host-provided image
128
+
129
+ Resolves to a child `<img>` or `<canvas>` of the `<wgsl-play>` element.
130
+ Lookup matches `[data-texture="name"]` first, then falls back to `#name`.
131
+ The image is decoded and uploaded as `rgba8unorm`.
132
+
133
+ ```html
134
+ <wgsl-play>
135
+ <script type="text/wesl">
136
+ import env::u;
137
+ @texture(nebula) var photo: texture_2d<f32>;
138
+ @sampler(linear) var samp: sampler;
139
+
140
+ @fragment fn fs_main(@builtin(position) pos: vec4f) -> @location(0) vec4f {
141
+ return textureSample(photo, samp, pos.xy / u.resolution);
142
+ }
143
+ </script>
144
+ <img data-texture="nebula" src="/images/nebula.jpg" hidden>
145
+ </wgsl-play>
146
+ ```
147
+
148
+ Only `texture_2d<f32>` is supported. `texture_cube`, `texture_2d_array`,
149
+ and storage textures are rejected with a clear error. `<img>` decoding
150
+ uses `imageOrientation: "from-image"`, `premultiplyAlpha: "none"`, and
151
+ `colorSpaceConversion: "none"` for deterministic uploads.
152
+
153
+ Changing an `<img>` `src`, swapping `data-texture`, or adding/removing
154
+ texture children rebuilds the pipeline automatically.
155
+
156
+ #### `@sampler(filter)`
157
+
158
+ Creates a sampler with `clamp-to-edge` addressing. Filter is `linear` or
159
+ `nearest`.
160
+
161
+ ```wgsl
162
+ @sampler(nearest) var samp: sampler;
163
+ ```
164
+
165
+ #### `@buffer`
166
+
167
+ Zero-initialized storage buffer; size inferred from the WGSL type.
168
+ `read` and `read_write` are both allowed.
169
+
170
+ ```wgsl
171
+ @buffer var<storage, read> palette: array<vec4f, 8>;
172
+ ```
173
+
174
+ `@test_texture` (the wgsl-test fixture annotation) is rejected at
175
+ runtime — use `@texture(name)` with a host element instead.
37
176
 
38
177
  ### Inline source
39
178
 
@@ -81,6 +220,11 @@ The `?raw` suffix imports the file as a string. This keeps shaders alongside you
81
220
  - `autoplay` - Start animating on load (default: `true`). Set `autoplay="false"` to start paused
82
221
  - `transparent` - Use premultiplied alpha for transparent backgrounds (default: opaque)
83
222
  - `from` - Element ID of a source provider (e.g., wgsl-edit) to connect to
223
+ - `no-controls` - Hide playback controls (play/pause, rewind, fullscreen)
224
+ - `no-settings` - Hide the uniform controls panel
225
+ - `width` / `height` - Fixed canvas resolution in pixels, independent of display size. When set, the canvas is not resized by the CSS layout
226
+ - `pixel-ratio` - Scale factor from CSS pixels to canvas pixels (default: `devicePixelRatio`). Set `pixel-ratio="1"` for 1:1 CSS pixels (no HiDPI scaling)
227
+ - `resizable` - Show a drag handle to let users resize the element interactively
84
228
  - `fetch-libs` - Auto-fetch missing libraries from npm (default: `true`). Set `fetch-libs="false"` to disable
85
229
  - `fetch-sources` - Auto-fetch local .wesl source files via HTTP (default: `true`). Set `fetch-sources="false"` to disable
86
230
 
@@ -88,6 +232,7 @@ The `?raw` suffix imports the file as a string. This keeps shaders alongside you
88
232
  - `shader: string` - Get/set shader source (single-file convenience)
89
233
  - `conditions: Record<string, boolean>` - Get/set conditions for conditional compilation (`@if`/`@elif`/`@else`)
90
234
  - `project: WeslProject` - Get/set full project config (weslSrc, libs, conditions, constants)
235
+ - `pixelRatio: number` - Get/set canvas-to-CSS pixel ratio (default: `devicePixelRatio`)
91
236
  - `isPlaying: boolean` - Playback state (readonly)
92
237
  - `time: number` - Animation time in seconds (readonly)
93
238
  - `hasError: boolean` - Compilation error state (readonly)
@@ -97,12 +242,30 @@ The `?raw` suffix imports the file as a string. This keeps shaders alongside you
97
242
  - `play()` - Start/resume animation
98
243
  - `pause()` - Pause animation
99
244
  - `rewind()` - Reset to t=0
245
+ - `renderFrame()` - Wait for any in-flight build, render one frame, and resolve once it has been presented (for snapshotting the canvas)
246
+ - `setUniform(name, value)` - Set a uniform value programmatically
100
247
  - `showError(message)` - Display error (empty string clears)
101
248
 
102
249
  ### Events
103
- - `compile-error` - `{ message: string }`
250
+ - `compile-error` - `{ message, source: "wesl"|"webgpu", kind: "shader"|"resource", resourceSource?, locations }`. `kind: "resource"` covers missing `@texture` host elements, unsupported texture dims, and `@test_texture` rejection; `resourceSource` names the offending var/source
104
251
  - `init-error` - `{ message: string }` (WebGPU init failed)
105
252
  - `playback-change` - `{ isPlaying: boolean }`
253
+ - `uniforms-layout` - `{ detail: AnnotatedLayout }` (fired after each compile)
254
+
255
+ ## Canvas Sizing
256
+
257
+ By default the canvas resolution tracks CSS size at `devicePixelRatio`.
258
+ Use `pixel-ratio` or `width`/`height` to decouple:
259
+
260
+ ```html
261
+ <!-- 1:1 CSS pixels (blocky on HiDPI, great for pixel art) -->
262
+ <wgsl-play pixel-ratio="1" style="width:512px; height:512px"></wgsl-play>
263
+
264
+ <!-- Fixed 64x64 canvas, stretched to whatever CSS size -->
265
+ <wgsl-play width="64" height="64" style="width:512px; height:512px"></wgsl-play>
266
+ ```
267
+
268
+ For crisp upscaling of low-res canvases, add `image-rendering: pixelated`:
106
269
 
107
270
  ## Styling
108
271
 
@@ -149,7 +312,7 @@ import super::common::tint;
149
312
 
150
313
  ## Using with wesl-plugin
151
314
 
152
- For more control, use the [wesl-plugin](https://github.com/wgsl-tooling-wg/wesl-js/tree/main/packages/wesl-plugin) to
315
+ For more control, use the [wesl-plugin](https://github.com/webgpu-tools/wesl-js/tree/main/packages/wesl-plugin) to
153
316
  assemble shaders and libraries at build time and provide
154
317
  them wgsl-play in JavaScript or TypeScript.
155
318
  - provides full support for Hot Module Reloading during development
@@ -1,4 +1,5 @@
1
1
  import { Conditions, WeslProject } from "wesl";
2
+ import { ResourceLoadError as ResourceLoadError$1 } from "wesl-gpu";
2
3
 
3
4
  //#region src/Config.d.ts
4
5
  /** Configuration for wgsl-play. */
@@ -20,6 +21,8 @@ interface CompileErrorLocation {
20
21
  line: number;
21
22
  column: number;
22
23
  length?: number;
24
+ /** byte offset into the source file */
25
+ offset: number;
23
26
  severity: "error" | "warning" | "info";
24
27
  message: string;
25
28
  }
@@ -27,17 +30,36 @@ interface CompileErrorLocation {
27
30
  interface CompileErrorDetail {
28
31
  message: string;
29
32
  source: "wesl" | "webgpu";
33
+ /** What kind of failure: a shader compile/link problem or a host-side resource problem. */
34
+ kind: "shader" | "resource";
35
+ /** For resource errors, the `@texture(name)` or buffer var referenced. */
36
+ resourceSource?: string;
30
37
  locations: CompileErrorLocation[];
31
38
  }
32
- /** <wgsl-play> web component for rendering WESL/WGSL fragment shaders. */
39
+ /** Attributes accepted by `<wgsl-play>` beyond standard HTML.
40
+ * Source of truth for framework-specific JSX augmentations (see `./jsx-preact.ts`). */
41
+ interface WgslPlayAttrs {
42
+ /** ID of a `<wgsl-edit>` (or compatible) element to source shader content from. */
43
+ from?: string;
44
+ /** Color theme; `"auto"` follows `prefers-color-scheme`. */
45
+ theme?: "light" | "dark" | "auto";
46
+ /** Allow drag-resize of the player via the corner handle. */
47
+ resizable?: boolean;
48
+ }
49
+ /** <wgsl-play> web component for rendering WESL/WGSL fragment shaders. */
33
50
  declare class WgslPlay extends HTMLElement {
34
51
  static observedAttributes: string[];
35
52
  private canvas;
53
+ private resultsPanel;
36
54
  private errorOverlay;
37
55
  private controls;
56
+ private settings;
38
57
  private resizeObserver;
39
58
  private stopRenderLoop?;
59
+ private _currentMode;
60
+ private _rerunPending;
40
61
  private renderState?;
62
+ private pendingUniforms;
41
63
  private playback;
42
64
  private _weslSrc;
43
65
  private _rootModuleName;
@@ -53,10 +75,16 @@ declare class WgslPlay extends HTMLElement {
53
75
  private _theme;
54
76
  private _mediaQuery;
55
77
  private _onFullscreenChange;
78
+ private _pointerCleanup?;
79
+ private _resizeCleanups;
80
+ private _childObserver?;
56
81
  /** Get config overrides from element attributes. */
57
82
  private getConfigOverrides;
58
83
  constructor();
59
84
  connectedCallback(): void;
85
+ /** Start watching element size. Deferred until after `initWebGPU` so the
86
+ * observer has a real `maxTextureDimension2D` to clamp against. */
87
+ private observeCanvasSize;
60
88
  disconnectedCallback(): void;
61
89
  attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
62
90
  /** Current shader source code (main module). */
@@ -77,6 +105,9 @@ declare class WgslPlay extends HTMLElement {
77
105
  /** Whether autoplay is enabled (default: true). Set autoplay="false" to start paused. */
78
106
  get autoplay(): boolean;
79
107
  set autoplay(value: boolean | string);
108
+ /** Scale factor from CSS pixels to canvas pixels (default: devicePixelRatio). */
109
+ get pixelRatio(): number;
110
+ set pixelRatio(value: number);
80
111
  /** Whether the shader is currently playing. */
81
112
  get isPlaying(): boolean;
82
113
  /** Current animation time in seconds. */
@@ -92,15 +123,41 @@ declare class WgslPlay extends HTMLElement {
92
123
  /** Pause playback. */
93
124
  pause(): void;
94
125
  private setPlaying;
126
+ /** Mirror a default-true setter onto its attribute: absent = on, "false" = off. */
127
+ private setBoolAttr;
128
+ /** Wait for any in-flight build and render one frame to the canvas, resolving
129
+ * once it has been presented. Read the canvas via
130
+ * `element.shadowRoot.querySelector('canvas')`. */
131
+ renderFrame(): Promise<void>;
132
+ /** Resolve once initialization and any pending build have settled. Yields via
133
+ * setTimeout so async build steps (network fetch, GPU validation) get a
134
+ * chance to advance between polls. */
135
+ private awaitIdleBuild;
95
136
  /** Reset animation to time 0 and pause. */
96
137
  rewind(): void;
138
+ /** Stop the RAF loop, mark paused, and re-present the current frame. */
139
+ private stopAndPresent;
97
140
  /** Display error message in overlay. Pass empty string to clear. */
98
141
  showError(message: string): void;
142
+ /** Set a uniform value by name. Works before or after compilation. */
143
+ setUniform(name: string, value: number | number[]): void;
144
+ /** Apply uniforms set before the pipeline existed. */
145
+ private flushPendingUniforms;
146
+ /** Current uniform control values (readable). */
147
+ get uniforms(): Record<string, number | number[]>;
99
148
  /** Toggle fullscreen on this element. */
100
149
  toggleFullscreen(): void;
150
+ /** Track pointer events on canvas for mouse_pos @auto fields. */
151
+ private setupMouseTracking;
152
+ /** Drag-to-resize via a custom handle (works on touch + mouse). */
153
+ private setupResizeHandle;
154
+ /** Recompute canvas resolution from attributes or CSS size. */
155
+ private updateCanvasSize;
156
+ /** Toggle the `dark` class to match `theme` attr or system preference. */
101
157
  private updateTheme;
102
158
  /** Set up WebGPU and load initial shader. Returns true if successful. */
103
159
  private initialize;
160
+ /** Init WebGPU, wire observers/listeners, and dispatch ready/init-error. */
104
161
  private doInitialize;
105
162
  /** Load from source element, src URL, script child, or inline textContent. */
106
163
  private loadInitialContent;
@@ -112,9 +169,23 @@ declare class WgslPlay extends HTMLElement {
112
169
  private requestBuild;
113
170
  /** Run builds until no longer dirty. Only one instance runs at a time. */
114
171
  private runBuild;
172
+ /** Fetch deps if needed and create the render pipeline. */
173
+ private buildPipeline;
174
+ /** Resolve a @texture(name) to a decoded image from light-DOM children.
175
+ * Prefers [data-texture="name"], falls back to #id. */
176
+ private resolveHostTexture;
177
+ /** Watch light-DOM for <img> add/remove/src changes and trigger a rebuild. */
178
+ private observeLightDomChildren;
179
+ /** Apply a successful build: flush uniforms, update controls, render. */
180
+ private applyBuild;
181
+ /** Show canvas vs results panel and (re-)render based on build mode. */
182
+ private applyMode;
183
+ /** Coalesce rapid uniform/refresh events into a single re-dispatch. */
184
+ private scheduleComputeRerun;
185
+ /** Show the error overlay and dispatch a `compile-error` event. */
115
186
  private handleCompileError;
116
187
  /** Extract source locations from a WESL parse error or GPU compilation error. */
117
188
  private extractLocations;
118
189
  }
119
190
  //#endregion
120
- export { defaults as a, WgslPlayConfig as i, CompileErrorLocation as n, getConfig as o, WgslPlay as r, resetConfig as s, CompileErrorDetail as t };
191
+ export { WgslPlayAttrs as a, getConfig as c, WgslPlay as i, resetConfig as l, CompileErrorLocation as n, WgslPlayConfig as o, ResourceLoadError$1 as r, defaults as s, CompileErrorDetail as t };