wgsl-play 0.0.31 → 0.0.33

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.
package/README.md CHANGED
@@ -18,12 +18,10 @@ That's it. The component auto-fetches dependencies and starts animating.
18
18
  accepts **fragment shaders**. Write a single `@fragment` function.
19
19
  WESL extensions are supported (imports, conditional compilation).
20
20
 
21
- Standard uniforms are provided at binding 0:
21
+ Standard uniforms are available via `env::u`:
22
22
 
23
23
  ```wgsl
24
- import test::Uniforms;
25
-
26
- @group(0) @binding(0) var<uniform> u: Uniforms;
24
+ import env::u;
27
25
 
28
26
  @fragment fn main(@builtin(position) pos: vec4f) -> @location(0) vec4f {
29
27
  let uv = pos.xy / u.resolution;
@@ -44,8 +42,7 @@ You can include shader code inline if you'd prefer. Use a `<script type="text/wg
44
42
  ```html
45
43
  <wgsl-play>
46
44
  <script type="text/wesl">
47
- import test::Uniforms;
48
- @group(0) @binding(0) var<uniform> u: Uniforms;
45
+ import env::u;
49
46
 
50
47
  @fragment fn fs_main(@builtin(position) pos: vec4f) -> @location(0) vec4f {
51
48
  let uv = pos.xy / u.resolution;
@@ -83,6 +80,7 @@ The `?raw` suffix imports the file as a string. This keeps shaders alongside you
83
80
  - `shader-root` - Root path for internal imports (default: `/shaders`)
84
81
  - `autoplay` - Start animating on load (default: `true`). Set `autoplay="false"` to start paused
85
82
  - `transparent` - Use premultiplied alpha for transparent backgrounds (default: opaque)
83
+ - `fetch-libs` - Auto-fetch missing libraries from npm (default: `true`). Set `fetch-libs="false"` to disable
86
84
 
87
85
  ### Properties
88
86
  - `source: string` - Get/set shader source
@@ -0,0 +1,14 @@
1
+ //#region src/Config.d.ts
2
+ /** Configuration for wgsl-play. */
3
+ interface WgslPlayConfig {
4
+ /** Root path for internal imports (package::, super::). Default: "/shaders" */
5
+ shaderRoot: string;
6
+ }
7
+ /** Set global defaults for all wgsl-play instances. */
8
+ declare function defaults(config: Partial<WgslPlayConfig>): void;
9
+ /** Get resolved config, merging element overrides with global defaults. */
10
+ declare function getConfig(overrides?: Partial<WgslPlayConfig>): WgslPlayConfig;
11
+ /** Reset config to defaults (useful for testing). */
12
+ declare function resetConfig(): void;
13
+ //#endregion
14
+ export { resetConfig as i, defaults as n, getConfig as r, WgslPlayConfig as t };
@@ -0,0 +1,203 @@
1
+ import { requestWeslDevice } from "wesl";
2
+ import { linkAndCreatePipeline, renderFrame, updateRenderUniforms } from "wesl-gpu";
3
+
4
+ //#region src/Config.ts
5
+ const defaultConfig = { shaderRoot: "/shaders" };
6
+ let globalConfig = { ...defaultConfig };
7
+ /** Set global defaults for all wgsl-play instances. */
8
+ function defaults(config) {
9
+ globalConfig = {
10
+ ...globalConfig,
11
+ ...config
12
+ };
13
+ }
14
+ /** Get resolved config, merging element overrides with global defaults. */
15
+ function getConfig(overrides) {
16
+ return {
17
+ ...globalConfig,
18
+ ...overrides
19
+ };
20
+ }
21
+ /** Reset config to defaults (useful for testing). */
22
+ function resetConfig() {
23
+ globalConfig = { ...defaultConfig };
24
+ }
25
+
26
+ //#endregion
27
+ //#region src/ErrorOverlay.ts
28
+ /** Manages an error overlay element within a shadow DOM. */
29
+ var ErrorOverlay = class {
30
+ el;
31
+ _message = null;
32
+ constructor(container) {
33
+ this.el = document.createElement("div");
34
+ this.el.className = "error-overlay";
35
+ container.appendChild(this.el);
36
+ }
37
+ show(message) {
38
+ this._message = message;
39
+ this.el.textContent = message;
40
+ this.el.classList.add("visible");
41
+ console.error("[wgsl-play]", message);
42
+ }
43
+ hide() {
44
+ this._message = null;
45
+ this.el.classList.remove("visible");
46
+ }
47
+ get visible() {
48
+ return this.el.classList.contains("visible");
49
+ }
50
+ get message() {
51
+ return this._message;
52
+ }
53
+ };
54
+
55
+ //#endregion
56
+ //#region src/icons/backToStart.svg?raw
57
+ var backToStart_default = "<svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\"><rect x=\"4\" y=\"4\" width=\"3\" height=\"16\" fill=\"currentColor\"/><polygon points=\"20,4 9,12 20,20\" fill=\"currentColor\"/></svg>\n";
58
+
59
+ //#endregion
60
+ //#region src/icons/expand.svg?raw
61
+ var expand_default = "<svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\"><path d=\"M3 3h6v2H5v4H3V3zm12 0h6v6h-2V5h-4V3zM3 15h2v4h4v2H3v-6zm16 4h-4v2h6v-6h-2v4z\" fill=\"currentColor\"/></svg>\n";
62
+
63
+ //#endregion
64
+ //#region src/icons/pause.svg?raw
65
+ var pause_default = "<svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\"><rect x=\"5\" y=\"4\" width=\"4\" height=\"16\" fill=\"currentColor\"/><rect x=\"15\" y=\"4\" width=\"4\" height=\"16\" fill=\"currentColor\"/></svg>\n";
66
+
67
+ //#endregion
68
+ //#region src/icons/play.svg?raw
69
+ var play_default = "<svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\"><polygon points=\"6,4 20,12 6,20\" fill=\"currentColor\"/></svg>\n";
70
+
71
+ //#endregion
72
+ //#region src/icons/shrink.svg?raw
73
+ var shrink_default = "<svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\"><path d=\"M9 3v6H3v-2h4V3h2zm6 0h2v4h4v2h-6V3zM3 15h6v6H7v-4H3v-2zm12 0h6v2h-4v4h-2v-6z\" fill=\"currentColor\"/></svg>\n";
74
+
75
+ //#endregion
76
+ //#region src/PlaybackControls.ts
77
+ /** Playback controls overlay for wgsl-play. */
78
+ var PlaybackControls = class {
79
+ container;
80
+ playPauseBtn;
81
+ fullscreenBtn;
82
+ playing = true;
83
+ constructor(shadow, onPlay, onPause, onRewind, onFullscreen) {
84
+ this.container = document.createElement("div");
85
+ this.container.className = "controls";
86
+ this.fullscreenBtn = document.createElement("button");
87
+ this.fullscreenBtn.innerHTML = expand_default;
88
+ this.fullscreenBtn.addEventListener("click", onFullscreen);
89
+ const rewindBtn = document.createElement("button");
90
+ rewindBtn.innerHTML = backToStart_default;
91
+ rewindBtn.addEventListener("click", onRewind);
92
+ this.playPauseBtn = document.createElement("button");
93
+ this.playPauseBtn.innerHTML = pause_default;
94
+ this.playPauseBtn.addEventListener("click", () => this.playing ? onPause() : onPlay());
95
+ this.container.append(this.fullscreenBtn, rewindBtn, this.playPauseBtn);
96
+ shadow.appendChild(this.container);
97
+ }
98
+ setPlaying(playing) {
99
+ this.playing = playing;
100
+ this.playPauseBtn.innerHTML = playing ? pause_default : play_default;
101
+ }
102
+ setFullscreen(isFullscreen) {
103
+ this.fullscreenBtn.innerHTML = isFullscreen ? shrink_default : expand_default;
104
+ }
105
+ show() {
106
+ this.container.style.display = "";
107
+ }
108
+ hide() {
109
+ this.container.style.display = "none";
110
+ }
111
+ };
112
+
113
+ //#endregion
114
+ //#region src/Renderer.ts
115
+ /** Initialize WebGPU for a canvas element. */
116
+ async function initWebGPU(canvas, alphaMode = "opaque") {
117
+ const adapter = await navigator.gpu.requestAdapter();
118
+ if (!adapter) throw new Error("WebGPU adapter not available");
119
+ const device = await requestWeslDevice(adapter);
120
+ const context = canvas.getContext("webgpu");
121
+ if (!context) throw new Error("WebGPU context not available");
122
+ const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
123
+ context.configure({
124
+ device,
125
+ format: presentationFormat,
126
+ alphaMode
127
+ });
128
+ const uniformBuffer = device.createBuffer({
129
+ size: 32,
130
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
131
+ });
132
+ const bindGroupLayout = device.createBindGroupLayout({ entries: [{
133
+ binding: 0,
134
+ visibility: GPUShaderStage.FRAGMENT,
135
+ buffer: {}
136
+ }] });
137
+ return {
138
+ device,
139
+ canvas,
140
+ context,
141
+ presentationFormat,
142
+ uniformBuffer,
143
+ pipelineLayout: device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] }),
144
+ bindGroup: device.createBindGroup({
145
+ layout: bindGroupLayout,
146
+ entries: [{
147
+ binding: 0,
148
+ resource: { buffer: uniformBuffer }
149
+ }]
150
+ }),
151
+ frameCount: 0
152
+ };
153
+ }
154
+ /** Compile WESL fragment shader and create render pipeline. */
155
+ async function createPipeline(state, fragmentSource, options) {
156
+ state.device.pushErrorScope("validation");
157
+ const pipeline = await linkAndCreatePipeline({
158
+ device: state.device,
159
+ fragmentSource,
160
+ format: state.presentationFormat,
161
+ layout: state.pipelineLayout,
162
+ ...options
163
+ });
164
+ const gpuError = await state.device.popErrorScope();
165
+ if (gpuError) {
166
+ state.pipeline = void 0;
167
+ throw gpuError;
168
+ }
169
+ state.pipeline = pipeline;
170
+ }
171
+ /** Start the render loop. Returns a stop function. */
172
+ function startRenderLoop(state, playback) {
173
+ let animationId;
174
+ function render() {
175
+ if (!state.pipeline) {
176
+ animationId = requestAnimationFrame(render);
177
+ return;
178
+ }
179
+ const time = calculateTime(playback);
180
+ const resolution = [state.canvas.width, state.canvas.height];
181
+ updateRenderUniforms(state.uniformBuffer, state.device, resolution, time, [0, 0]);
182
+ renderFrame({
183
+ device: state.device,
184
+ pipeline: state.pipeline,
185
+ bindGroup: state.bindGroup,
186
+ targetView: state.context.getCurrentTexture().createView()
187
+ });
188
+ state.frameCount++;
189
+ animationId = requestAnimationFrame(render);
190
+ }
191
+ animationId = requestAnimationFrame(render);
192
+ return () => cancelAnimationFrame(animationId);
193
+ }
194
+ function calculateTime(playback) {
195
+ return ((playback.isPlaying ? performance.now() : playback.startTime + playback.pausedDuration) - playback.startTime) / 1e3;
196
+ }
197
+
198
+ //#endregion
199
+ //#region src/WgslPlay.css?inline
200
+ var WgslPlay_default = ":host {\n --error-bg: rgba(220, 102, 18, 0.8);\n --error-color: white;\n --controls-bg: rgba(0, 0, 0, 0.4);\n --controls-color: rgba(255, 255, 255, 0.8);\n --controls-hover-color: white;\n --controls-hover-bg: rgba(255, 255, 255, 0.15);\n\n display: block;\n position: relative;\n overflow: hidden;\n}\n\n:host(.dark) {\n --error-bg: rgba(180, 60, 10, 0.85);\n --controls-bg: rgba(0, 0, 0, 0.5);\n}\n\ncanvas {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n display: block;\n}\n\n.controls {\n background: var(--controls-bg);\n position: absolute;\n bottom: 8px;\n right: 8px;\n display: flex;\n gap: 20px;\n border-radius: 6px;\n padding: 2px;\n opacity: 0;\n transition: opacity 0.2s;\n z-index: 1;\n}\n\n:host(:hover) .controls {\n opacity: 1;\n}\n\n.controls button {\n border: none;\n background: none;\n color: var(--controls-color);\n cursor: pointer;\n padding: 4px;\n border-radius: 4px;\n display: flex;\n align-items: center;\n}\n\n.controls button:hover {\n color: var(--controls-hover-color);\n background: var(--controls-hover-bg);\n}\n\n.error-overlay {\n position: absolute;\n inset: 0;\n background: var(--error-bg);\n color: var(--error-color);\n padding: 1rem;\n font-family: monospace;\n font-size: 0.875rem;\n white-space: pre-wrap;\n overflow: auto;\n display: none;\n}\n\n.error-overlay.visible {\n display: block;\n}\n";
201
+
202
+ //#endregion
203
+ export { PlaybackControls as a, getConfig as c, startRenderLoop as i, resetConfig as l, createPipeline as n, ErrorOverlay as o, initWebGPU as r, defaults as s, WgslPlay_default as t };
@@ -1,2 +1,107 @@
1
- import { c as resetConfig, i as WgslPlay, n as CompileErrorLocation, o as defaults, r as WeslProject, s as getConfig, t as CompileErrorDetail } from "./WgslPlay-BUiSUZem.js";
1
+ import { i as resetConfig, n as defaults, r as getConfig } from "./Config-BV15Y2ZJ.js";
2
+ import { Conditions, LinkParams } from "wesl";
3
+
4
+ //#region src/WgslPlay.d.ts
5
+ /** Project configuration for multi-file shaders (subset of wesl link() API). */
6
+ type WeslProject = Pick<LinkParams, "weslSrc" | "rootModuleName" | "conditions" | "constants" | "libs" | "packageName">;
7
+ /** One source location within a compile error. */
8
+ interface CompileErrorLocation {
9
+ file?: string;
10
+ line: number;
11
+ column: number;
12
+ length?: number;
13
+ severity: "error" | "warning" | "info";
14
+ message: string;
15
+ }
16
+ /** Compile error detail for events. */
17
+ interface CompileErrorDetail {
18
+ message: string;
19
+ source: "wesl" | "webgpu";
20
+ locations: CompileErrorLocation[];
21
+ }
22
+ /** <wgsl-play> web component for rendering WESL/WGSL fragment shaders. */
23
+ declare class WgslPlay extends HTMLElement {
24
+ static observedAttributes: string[];
25
+ private canvas;
26
+ private errorOverlay;
27
+ private controls;
28
+ private resizeObserver;
29
+ private stopRenderLoop?;
30
+ private renderState?;
31
+ private playback;
32
+ private _weslSrc;
33
+ private _rootModuleName;
34
+ private _libs?;
35
+ private _linkOptions;
36
+ private _fromFullProject;
37
+ private _initPromise?;
38
+ private _sourceEl;
39
+ private _sourceListener;
40
+ private _fetchLibs;
41
+ private _theme;
42
+ private _mediaQuery;
43
+ private _onFullscreenChange;
44
+ /** Get config overrides from element attributes. */
45
+ private getConfigOverrides;
46
+ constructor();
47
+ connectedCallback(): void;
48
+ disconnectedCallback(): void;
49
+ attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
50
+ /** Current shader source code (main module). */
51
+ get source(): string;
52
+ /** Set shader source directly. */
53
+ set source(value: string);
54
+ /** Conditions for conditional compilation (@if/@elif/@else). */
55
+ get conditions(): Conditions;
56
+ set conditions(value: Conditions);
57
+ /** Set project configuration (mirrors wesl link() API). */
58
+ set project(value: WeslProject);
59
+ /** Set sources from a full project with weslSrc. */
60
+ private setProjectSources;
61
+ /** Whether to auto-fetch missing library packages from npm (default: true). */
62
+ get fetchLibs(): boolean;
63
+ set fetchLibs(value: boolean);
64
+ /** Whether autoplay is enabled (default: true). Set autoplay="false" to start paused. */
65
+ get autoplay(): boolean;
66
+ set autoplay(value: boolean | string);
67
+ /** Whether the shader is currently playing. */
68
+ get isPlaying(): boolean;
69
+ /** Current animation time in seconds. */
70
+ get time(): number;
71
+ /** Number of frames rendered (for testing/debugging). */
72
+ get frameCount(): number;
73
+ /** Whether there's a compilation error. */
74
+ get hasError(): boolean;
75
+ /** Current error message, or null if no error. */
76
+ get errorMessage(): string | null;
77
+ /** Start playback. */
78
+ play(): void;
79
+ /** Pause playback. */
80
+ pause(): void;
81
+ private setPlaying;
82
+ /** Reset animation to time 0 and pause. */
83
+ rewind(): void;
84
+ /** Display error message in overlay. Pass empty string to clear. */
85
+ showError(message: string): void;
86
+ /** Toggle fullscreen on this element. */
87
+ toggleFullscreen(): void;
88
+ private updateTheme;
89
+ /** Set up WebGPU and load initial shader. Returns true if successful. */
90
+ private initialize;
91
+ private doInitialize;
92
+ /** Load from source element, src URL, script child, or inline textContent. */
93
+ private loadInitialContent;
94
+ /** Connect to a source provider element (e.g., wgsl-edit). */
95
+ private connectToSource;
96
+ /** Fetch shader from URL, auto-fetching any imported dependencies. */
97
+ private loadFromUrl;
98
+ /** Rebuild GPU pipeline using stored state. For full projects with all sources. */
99
+ private rebuildPipeline;
100
+ /** Discover dependencies and rebuild. For HTTP/inline sources that may need fetching. */
101
+ private discoverAndRebuild;
102
+ private handleCompileError;
103
+ /** Extract source locations from a WESL parse error or GPU compilation error. */
104
+ private extractLocations;
105
+ }
106
+ //#endregion
2
107
  export { CompileErrorDetail, CompileErrorLocation, WeslProject, WgslPlay, defaults, getConfig, resetConfig };