wgsl-play 0.0.35 → 0.0.36
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/dist/{WgslPlay-Ben4VdsC.js → WgslPlay-BRvURGA3.js} +17 -11
- package/dist/WgslPlay.d.ts +7 -5
- package/dist/WgslPlay.js +69 -80
- package/dist/index.js +1 -1
- package/dist/wgsl-play.js +85 -90
- package/package.json +1 -1
- package/src/Renderer.ts +17 -11
- package/src/WgslPlay.ts +73 -92
|
@@ -154,19 +154,25 @@ async function initWebGPU(canvas, alphaMode = "opaque") {
|
|
|
154
154
|
/** Compile WESL fragment shader and create render pipeline. */
|
|
155
155
|
async function createPipeline(state, fragmentSource, options) {
|
|
156
156
|
state.device.pushErrorScope("validation");
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
157
|
+
let gpuError;
|
|
158
|
+
let jsError;
|
|
159
|
+
try {
|
|
160
|
+
state.pipeline = await linkAndCreatePipeline({
|
|
161
|
+
device: state.device,
|
|
162
|
+
fragmentSource,
|
|
163
|
+
format: state.presentationFormat,
|
|
164
|
+
layout: state.pipelineLayout,
|
|
165
|
+
...options
|
|
166
|
+
});
|
|
167
|
+
} catch (e) {
|
|
168
|
+
jsError = e;
|
|
169
|
+
} finally {
|
|
170
|
+
gpuError = await state.device.popErrorScope();
|
|
171
|
+
}
|
|
172
|
+
if (jsError || gpuError) {
|
|
166
173
|
state.pipeline = void 0;
|
|
167
|
-
throw gpuError;
|
|
174
|
+
throw jsError ?? gpuError;
|
|
168
175
|
}
|
|
169
|
-
state.pipeline = pipeline;
|
|
170
176
|
}
|
|
171
177
|
/** Start the render loop. Returns a stop function. */
|
|
172
178
|
function startRenderLoop(state, playback) {
|
package/dist/WgslPlay.d.ts
CHANGED
|
@@ -38,6 +38,8 @@ declare class WgslPlay extends HTMLElement {
|
|
|
38
38
|
private _sourceEl;
|
|
39
39
|
private _sourceListener;
|
|
40
40
|
private _fetchLibs;
|
|
41
|
+
private _dirty;
|
|
42
|
+
private _building;
|
|
41
43
|
private _theme;
|
|
42
44
|
private _mediaQuery;
|
|
43
45
|
private _onFullscreenChange;
|
|
@@ -93,12 +95,12 @@ declare class WgslPlay extends HTMLElement {
|
|
|
93
95
|
private loadInitialContent;
|
|
94
96
|
/** Connect to a source provider element (e.g., wgsl-edit). */
|
|
95
97
|
private connectToSource;
|
|
96
|
-
/** Fetch shader from URL,
|
|
98
|
+
/** Fetch shader from URL, then trigger a build. */
|
|
97
99
|
private loadFromUrl;
|
|
98
|
-
/**
|
|
99
|
-
private
|
|
100
|
-
/**
|
|
101
|
-
private
|
|
100
|
+
/** Mark build as needed. Coalesces rapid requests into a single build. */
|
|
101
|
+
private requestBuild;
|
|
102
|
+
/** Run builds until no longer dirty. Only one instance runs at a time. */
|
|
103
|
+
private runBuild;
|
|
102
104
|
private handleCompileError;
|
|
103
105
|
/** Extract source locations from a WESL parse error or GPU compilation error. */
|
|
104
106
|
private extractLocations;
|
package/dist/WgslPlay.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as PlaybackControls, c as getConfig, i as startRenderLoop, l as resetConfig, n as createPipeline, o as ErrorOverlay, r as initWebGPU, s as defaults, t as WgslPlay_default } from "./WgslPlay-
|
|
1
|
+
import { a as PlaybackControls, c as getConfig, i as startRenderLoop, l as resetConfig, n as createPipeline, o as ErrorOverlay, r as initWebGPU, s as defaults, t as WgslPlay_default } from "./WgslPlay-BRvURGA3.js";
|
|
2
2
|
import { fetchDependencies, loadShaderFromUrl } from "wesl-fetch";
|
|
3
3
|
import { WeslParseError, fileToModulePath } from "wesl";
|
|
4
4
|
|
|
@@ -37,6 +37,8 @@ var WgslPlay = class extends HTMLElement {
|
|
|
37
37
|
_sourceEl = null;
|
|
38
38
|
_sourceListener = null;
|
|
39
39
|
_fetchLibs = true;
|
|
40
|
+
_dirty = false;
|
|
41
|
+
_building = false;
|
|
40
42
|
_theme = "auto";
|
|
41
43
|
_mediaQuery = null;
|
|
42
44
|
_onFullscreenChange = () => this.controls.setFullscreen(!!document.fullscreenElement);
|
|
@@ -117,7 +119,7 @@ var WgslPlay = class extends HTMLElement {
|
|
|
117
119
|
this._weslSrc = { [this._rootModuleName]: value };
|
|
118
120
|
this._libs = void 0;
|
|
119
121
|
this._fromFullProject = false;
|
|
120
|
-
this.
|
|
122
|
+
this.requestBuild();
|
|
121
123
|
}
|
|
122
124
|
/** Conditions for conditional compilation (@if/@elif/@else). */
|
|
123
125
|
get conditions() {
|
|
@@ -129,8 +131,7 @@ var WgslPlay = class extends HTMLElement {
|
|
|
129
131
|
conditions: value
|
|
130
132
|
};
|
|
131
133
|
if (Object.keys(this._weslSrc).length === 0) return;
|
|
132
|
-
|
|
133
|
-
else this.discoverAndRebuild();
|
|
134
|
+
this.requestBuild();
|
|
134
135
|
}
|
|
135
136
|
/** Set project configuration (mirrors wesl link() API). */
|
|
136
137
|
set project(value) {
|
|
@@ -146,8 +147,7 @@ var WgslPlay = class extends HTMLElement {
|
|
|
146
147
|
return;
|
|
147
148
|
}
|
|
148
149
|
if (Object.keys(this._weslSrc).length === 0) return;
|
|
149
|
-
|
|
150
|
-
else this.discoverAndRebuild();
|
|
150
|
+
this.requestBuild();
|
|
151
151
|
}
|
|
152
152
|
/** Set sources from a full project with weslSrc. */
|
|
153
153
|
setProjectSources(weslSrc, rootModuleName) {
|
|
@@ -156,7 +156,7 @@ var WgslPlay = class extends HTMLElement {
|
|
|
156
156
|
this._weslSrc = toModulePaths(weslSrc, pkg);
|
|
157
157
|
this._rootModuleName = fileToModulePath(root, pkg, false);
|
|
158
158
|
this._fromFullProject = true;
|
|
159
|
-
this.
|
|
159
|
+
this.requestBuild();
|
|
160
160
|
}
|
|
161
161
|
/** Whether to auto-fetch missing library packages from npm (default: true). */
|
|
162
162
|
get fetchLibs() {
|
|
@@ -248,7 +248,7 @@ var WgslPlay = class extends HTMLElement {
|
|
|
248
248
|
try {
|
|
249
249
|
const alphaMode = this.hasAttribute("transparent") ? "premultiplied" : "opaque";
|
|
250
250
|
this.renderState = await initWebGPU(this.canvas, alphaMode);
|
|
251
|
-
|
|
251
|
+
this.loadInitialContent();
|
|
252
252
|
this.stopRenderLoop = startRenderLoop(this.renderState, this.playback);
|
|
253
253
|
this.dispatchEvent(new CustomEvent("ready"));
|
|
254
254
|
return true;
|
|
@@ -261,19 +261,25 @@ var WgslPlay = class extends HTMLElement {
|
|
|
261
261
|
}
|
|
262
262
|
}
|
|
263
263
|
/** Load from source element, src URL, script child, or inline textContent. */
|
|
264
|
-
|
|
264
|
+
loadInitialContent() {
|
|
265
265
|
const sourceId = this.getAttribute("source");
|
|
266
|
-
if (sourceId)
|
|
266
|
+
if (sourceId) {
|
|
267
|
+
this.connectToSource(sourceId);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
267
270
|
const src = this.getAttribute("src");
|
|
268
|
-
if (src)
|
|
271
|
+
if (src) {
|
|
272
|
+
this.loadFromUrl(src);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
269
275
|
const inlineSource = this.querySelector("script[type=\"text/wgsl\"], script[type=\"text/wesl\"]")?.textContent?.trim() ?? this.textContent?.trim();
|
|
270
276
|
if (!inlineSource) return;
|
|
271
277
|
this._weslSrc = { [this._rootModuleName]: inlineSource };
|
|
272
278
|
this._fromFullProject = false;
|
|
273
|
-
|
|
279
|
+
this.requestBuild();
|
|
274
280
|
}
|
|
275
281
|
/** Connect to a source provider element (e.g., wgsl-edit). */
|
|
276
|
-
|
|
282
|
+
connectToSource(id) {
|
|
277
283
|
const el = document.getElementById(id);
|
|
278
284
|
if (!el) {
|
|
279
285
|
console.error(`wgsl-play: source element "${id}" not found`);
|
|
@@ -296,8 +302,7 @@ var WgslPlay = class extends HTMLElement {
|
|
|
296
302
|
};
|
|
297
303
|
if (libs) this._libs = libs;
|
|
298
304
|
this._fromFullProject = false;
|
|
299
|
-
|
|
300
|
-
this._fromFullProject = true;
|
|
305
|
+
this.requestBuild();
|
|
301
306
|
this._sourceListener = (e) => {
|
|
302
307
|
const detail = e.detail;
|
|
303
308
|
const fallback = { [this._rootModuleName]: detail?.source ?? "" };
|
|
@@ -310,83 +315,61 @@ var WgslPlay = class extends HTMLElement {
|
|
|
310
315
|
};
|
|
311
316
|
el.addEventListener("change", this._sourceListener);
|
|
312
317
|
}
|
|
313
|
-
/** Fetch shader from URL,
|
|
318
|
+
/** Fetch shader from URL, then trigger a build. */
|
|
314
319
|
async loadFromUrl(url) {
|
|
315
|
-
if (!this.renderState) return;
|
|
316
320
|
try {
|
|
317
|
-
this.errorOverlay.hide();
|
|
318
321
|
const { weslSrc, libs, rootModuleName } = await loadShaderFromUrl(url, this.getConfigOverrides()?.shaderRoot);
|
|
319
322
|
this._weslSrc = weslSrc;
|
|
320
323
|
this._libs = libs;
|
|
321
324
|
this._fromFullProject = false;
|
|
322
325
|
if (rootModuleName) this._rootModuleName = rootModuleName;
|
|
323
|
-
|
|
324
|
-
if (!mainSource) {
|
|
325
|
-
console.warn(`wgsl-play: root module "${this._rootModuleName}" not found in sources:`, Object.keys(weslSrc));
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
await createPipeline(this.renderState, mainSource, {
|
|
329
|
-
...this._linkOptions,
|
|
330
|
-
weslSrc,
|
|
331
|
-
libs,
|
|
332
|
-
rootModuleName: this._rootModuleName
|
|
333
|
-
});
|
|
334
|
-
this.dispatchEvent(new CustomEvent("compile-success"));
|
|
335
|
-
} catch (error) {
|
|
336
|
-
this.handleCompileError(error);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
/** Rebuild GPU pipeline using stored state. For full projects with all sources. */
|
|
340
|
-
async rebuildPipeline() {
|
|
341
|
-
if (!await this.initialize()) return;
|
|
342
|
-
const mainSource = this._weslSrc[this._rootModuleName];
|
|
343
|
-
if (!mainSource) {
|
|
344
|
-
console.warn(`wgsl-play: root module "${this._rootModuleName}" not found in sources:`, Object.keys(this._weslSrc));
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
try {
|
|
348
|
-
this.errorOverlay.hide();
|
|
349
|
-
await createPipeline(this.renderState, mainSource, {
|
|
350
|
-
...this._linkOptions,
|
|
351
|
-
weslSrc: this._weslSrc,
|
|
352
|
-
libs: this._libs,
|
|
353
|
-
rootModuleName: this._rootModuleName
|
|
354
|
-
});
|
|
355
|
-
this.dispatchEvent(new CustomEvent("compile-success"));
|
|
326
|
+
this.requestBuild();
|
|
356
327
|
} catch (error) {
|
|
357
328
|
this.handleCompileError(error);
|
|
358
329
|
}
|
|
359
330
|
}
|
|
360
|
-
/**
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
this.
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
331
|
+
/** Mark build as needed. Coalesces rapid requests into a single build. */
|
|
332
|
+
requestBuild() {
|
|
333
|
+
this._dirty = true;
|
|
334
|
+
if (!this._building) this.runBuild();
|
|
335
|
+
}
|
|
336
|
+
/** Run builds until no longer dirty. Only one instance runs at a time. */
|
|
337
|
+
async runBuild() {
|
|
338
|
+
this._building = true;
|
|
339
|
+
while (this._dirty) {
|
|
340
|
+
this._dirty = false;
|
|
341
|
+
if (!await this.initialize()) break;
|
|
342
|
+
const mainSource = this._weslSrc[this._rootModuleName];
|
|
343
|
+
if (!mainSource) {
|
|
344
|
+
console.warn(`wgsl-play: root module "${this._rootModuleName}" not found in sources:`, Object.keys(this._weslSrc));
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
try {
|
|
348
|
+
this.errorOverlay.hide();
|
|
349
|
+
if (!this._fromFullProject) {
|
|
350
|
+
const { weslSrc, libs } = await fetchDependencies(mainSource, {
|
|
351
|
+
shaderRoot: this.getConfigOverrides()?.shaderRoot,
|
|
352
|
+
existingSources: this._weslSrc,
|
|
353
|
+
skipExternal: !this._fetchLibs
|
|
354
|
+
});
|
|
355
|
+
this._weslSrc = {
|
|
356
|
+
...this._weslSrc,
|
|
357
|
+
...weslSrc
|
|
358
|
+
};
|
|
359
|
+
this._libs = dedupLibs(this._libs, libs);
|
|
360
|
+
}
|
|
361
|
+
await createPipeline(this.renderState, mainSource, {
|
|
362
|
+
...this._linkOptions,
|
|
363
|
+
weslSrc: this._weslSrc,
|
|
364
|
+
libs: this._libs,
|
|
365
|
+
rootModuleName: this._rootModuleName
|
|
366
|
+
});
|
|
367
|
+
if (!this._dirty) this.dispatchEvent(new CustomEvent("compile-success"));
|
|
368
|
+
} catch (error) {
|
|
369
|
+
if (!this._dirty) this.handleCompileError(error);
|
|
370
|
+
}
|
|
389
371
|
}
|
|
372
|
+
this._building = false;
|
|
390
373
|
}
|
|
391
374
|
handleCompileError(error) {
|
|
392
375
|
const message = error?.message ?? String(error);
|
|
@@ -443,6 +426,12 @@ function upgradeProperty(el, prop) {
|
|
|
443
426
|
el[prop] = value;
|
|
444
427
|
}
|
|
445
428
|
}
|
|
429
|
+
/** Merge new libs, deduplicating by bundle name. */
|
|
430
|
+
function dedupLibs(existing, newLibs) {
|
|
431
|
+
if (!existing || newLibs.length === 0) return [...existing ?? [], ...newLibs];
|
|
432
|
+
const names = new Set(newLibs.map((b) => b.name));
|
|
433
|
+
return [...existing.filter((b) => !names.has(b.name)), ...newLibs];
|
|
434
|
+
}
|
|
446
435
|
/** Normalize all keys in a weslSrc record to module paths. */
|
|
447
436
|
function toModulePaths(weslSrc, pkg) {
|
|
448
437
|
const result = {};
|
package/dist/index.js
CHANGED
package/dist/wgsl-play.js
CHANGED
|
@@ -5641,19 +5641,25 @@ async function initWebGPU(canvas, alphaMode = "opaque") {
|
|
|
5641
5641
|
/** Compile WESL fragment shader and create render pipeline. */
|
|
5642
5642
|
async function createPipeline(state, fragmentSource, options) {
|
|
5643
5643
|
state.device.pushErrorScope("validation");
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5644
|
+
let gpuError;
|
|
5645
|
+
let jsError;
|
|
5646
|
+
try {
|
|
5647
|
+
state.pipeline = await linkAndCreatePipeline({
|
|
5648
|
+
device: state.device,
|
|
5649
|
+
fragmentSource,
|
|
5650
|
+
format: state.presentationFormat,
|
|
5651
|
+
layout: state.pipelineLayout,
|
|
5652
|
+
...options
|
|
5653
|
+
});
|
|
5654
|
+
} catch (e) {
|
|
5655
|
+
jsError = e;
|
|
5656
|
+
} finally {
|
|
5657
|
+
gpuError = await state.device.popErrorScope();
|
|
5658
|
+
}
|
|
5659
|
+
if (jsError || gpuError) {
|
|
5653
5660
|
state.pipeline = void 0;
|
|
5654
|
-
throw gpuError;
|
|
5661
|
+
throw jsError ?? gpuError;
|
|
5655
5662
|
}
|
|
5656
|
-
state.pipeline = pipeline;
|
|
5657
5663
|
}
|
|
5658
5664
|
/** Start the render loop. Returns a stop function. */
|
|
5659
5665
|
function startRenderLoop(state, playback) {
|
|
@@ -5722,6 +5728,8 @@ var WgslPlay = class extends HTMLElement {
|
|
|
5722
5728
|
_sourceEl = null;
|
|
5723
5729
|
_sourceListener = null;
|
|
5724
5730
|
_fetchLibs = true;
|
|
5731
|
+
_dirty = false;
|
|
5732
|
+
_building = false;
|
|
5725
5733
|
_theme = "auto";
|
|
5726
5734
|
_mediaQuery = null;
|
|
5727
5735
|
_onFullscreenChange = () => this.controls.setFullscreen(!!document.fullscreenElement);
|
|
@@ -5802,7 +5810,7 @@ var WgslPlay = class extends HTMLElement {
|
|
|
5802
5810
|
this._weslSrc = { [this._rootModuleName]: value };
|
|
5803
5811
|
this._libs = void 0;
|
|
5804
5812
|
this._fromFullProject = false;
|
|
5805
|
-
this.
|
|
5813
|
+
this.requestBuild();
|
|
5806
5814
|
}
|
|
5807
5815
|
/** Conditions for conditional compilation (@if/@elif/@else). */
|
|
5808
5816
|
get conditions() {
|
|
@@ -5814,8 +5822,7 @@ var WgslPlay = class extends HTMLElement {
|
|
|
5814
5822
|
conditions: value
|
|
5815
5823
|
};
|
|
5816
5824
|
if (Object.keys(this._weslSrc).length === 0) return;
|
|
5817
|
-
|
|
5818
|
-
else this.discoverAndRebuild();
|
|
5825
|
+
this.requestBuild();
|
|
5819
5826
|
}
|
|
5820
5827
|
/** Set project configuration (mirrors wesl link() API). */
|
|
5821
5828
|
set project(value) {
|
|
@@ -5831,8 +5838,7 @@ var WgslPlay = class extends HTMLElement {
|
|
|
5831
5838
|
return;
|
|
5832
5839
|
}
|
|
5833
5840
|
if (Object.keys(this._weslSrc).length === 0) return;
|
|
5834
|
-
|
|
5835
|
-
else this.discoverAndRebuild();
|
|
5841
|
+
this.requestBuild();
|
|
5836
5842
|
}
|
|
5837
5843
|
/** Set sources from a full project with weslSrc. */
|
|
5838
5844
|
setProjectSources(weslSrc, rootModuleName) {
|
|
@@ -5841,7 +5847,7 @@ var WgslPlay = class extends HTMLElement {
|
|
|
5841
5847
|
this._weslSrc = toModulePaths(weslSrc, pkg);
|
|
5842
5848
|
this._rootModuleName = fileToModulePath(root, pkg, false);
|
|
5843
5849
|
this._fromFullProject = true;
|
|
5844
|
-
this.
|
|
5850
|
+
this.requestBuild();
|
|
5845
5851
|
}
|
|
5846
5852
|
/** Whether to auto-fetch missing library packages from npm (default: true). */
|
|
5847
5853
|
get fetchLibs() {
|
|
@@ -5933,7 +5939,7 @@ var WgslPlay = class extends HTMLElement {
|
|
|
5933
5939
|
try {
|
|
5934
5940
|
const alphaMode = this.hasAttribute("transparent") ? "premultiplied" : "opaque";
|
|
5935
5941
|
this.renderState = await initWebGPU(this.canvas, alphaMode);
|
|
5936
|
-
|
|
5942
|
+
this.loadInitialContent();
|
|
5937
5943
|
this.stopRenderLoop = startRenderLoop(this.renderState, this.playback);
|
|
5938
5944
|
this.dispatchEvent(new CustomEvent("ready"));
|
|
5939
5945
|
return true;
|
|
@@ -5946,19 +5952,25 @@ var WgslPlay = class extends HTMLElement {
|
|
|
5946
5952
|
}
|
|
5947
5953
|
}
|
|
5948
5954
|
/** Load from source element, src URL, script child, or inline textContent. */
|
|
5949
|
-
|
|
5955
|
+
loadInitialContent() {
|
|
5950
5956
|
const sourceId = this.getAttribute("source");
|
|
5951
|
-
if (sourceId)
|
|
5957
|
+
if (sourceId) {
|
|
5958
|
+
this.connectToSource(sourceId);
|
|
5959
|
+
return;
|
|
5960
|
+
}
|
|
5952
5961
|
const src = this.getAttribute("src");
|
|
5953
|
-
if (src)
|
|
5962
|
+
if (src) {
|
|
5963
|
+
this.loadFromUrl(src);
|
|
5964
|
+
return;
|
|
5965
|
+
}
|
|
5954
5966
|
const inlineSource = this.querySelector("script[type=\"text/wgsl\"], script[type=\"text/wesl\"]")?.textContent?.trim() ?? this.textContent?.trim();
|
|
5955
5967
|
if (!inlineSource) return;
|
|
5956
5968
|
this._weslSrc = { [this._rootModuleName]: inlineSource };
|
|
5957
5969
|
this._fromFullProject = false;
|
|
5958
|
-
|
|
5970
|
+
this.requestBuild();
|
|
5959
5971
|
}
|
|
5960
5972
|
/** Connect to a source provider element (e.g., wgsl-edit). */
|
|
5961
|
-
|
|
5973
|
+
connectToSource(id) {
|
|
5962
5974
|
const el = document.getElementById(id);
|
|
5963
5975
|
if (!el) {
|
|
5964
5976
|
console.error(`wgsl-play: source element "${id}" not found`);
|
|
@@ -5981,8 +5993,7 @@ var WgslPlay = class extends HTMLElement {
|
|
|
5981
5993
|
};
|
|
5982
5994
|
if (libs) this._libs = libs;
|
|
5983
5995
|
this._fromFullProject = false;
|
|
5984
|
-
|
|
5985
|
-
this._fromFullProject = true;
|
|
5996
|
+
this.requestBuild();
|
|
5986
5997
|
this._sourceListener = (e) => {
|
|
5987
5998
|
const detail = e.detail;
|
|
5988
5999
|
const fallback = { [this._rootModuleName]: detail?.source ?? "" };
|
|
@@ -5995,83 +6006,61 @@ var WgslPlay = class extends HTMLElement {
|
|
|
5995
6006
|
};
|
|
5996
6007
|
el.addEventListener("change", this._sourceListener);
|
|
5997
6008
|
}
|
|
5998
|
-
/** Fetch shader from URL,
|
|
6009
|
+
/** Fetch shader from URL, then trigger a build. */
|
|
5999
6010
|
async loadFromUrl(url) {
|
|
6000
|
-
if (!this.renderState) return;
|
|
6001
6011
|
try {
|
|
6002
|
-
this.errorOverlay.hide();
|
|
6003
6012
|
const { weslSrc, libs, rootModuleName } = await loadShaderFromUrl(url, this.getConfigOverrides()?.shaderRoot);
|
|
6004
6013
|
this._weslSrc = weslSrc;
|
|
6005
6014
|
this._libs = libs;
|
|
6006
6015
|
this._fromFullProject = false;
|
|
6007
6016
|
if (rootModuleName) this._rootModuleName = rootModuleName;
|
|
6008
|
-
|
|
6009
|
-
if (!mainSource) {
|
|
6010
|
-
console.warn(`wgsl-play: root module "${this._rootModuleName}" not found in sources:`, Object.keys(weslSrc));
|
|
6011
|
-
return;
|
|
6012
|
-
}
|
|
6013
|
-
await createPipeline(this.renderState, mainSource, {
|
|
6014
|
-
...this._linkOptions,
|
|
6015
|
-
weslSrc,
|
|
6016
|
-
libs,
|
|
6017
|
-
rootModuleName: this._rootModuleName
|
|
6018
|
-
});
|
|
6019
|
-
this.dispatchEvent(new CustomEvent("compile-success"));
|
|
6017
|
+
this.requestBuild();
|
|
6020
6018
|
} catch (error) {
|
|
6021
6019
|
this.handleCompileError(error);
|
|
6022
6020
|
}
|
|
6023
6021
|
}
|
|
6024
|
-
/**
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
this.
|
|
6034
|
-
await
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
}
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
}
|
|
6064
|
-
this._libs = [...this._libs ?? [], ...libs];
|
|
6065
|
-
await createPipeline(this.renderState, mainSource, {
|
|
6066
|
-
...this._linkOptions,
|
|
6067
|
-
weslSrc: this._weslSrc,
|
|
6068
|
-
libs: this._libs,
|
|
6069
|
-
rootModuleName: this._rootModuleName
|
|
6070
|
-
});
|
|
6071
|
-
this.dispatchEvent(new CustomEvent("compile-success"));
|
|
6072
|
-
} catch (error) {
|
|
6073
|
-
this.handleCompileError(error);
|
|
6022
|
+
/** Mark build as needed. Coalesces rapid requests into a single build. */
|
|
6023
|
+
requestBuild() {
|
|
6024
|
+
this._dirty = true;
|
|
6025
|
+
if (!this._building) this.runBuild();
|
|
6026
|
+
}
|
|
6027
|
+
/** Run builds until no longer dirty. Only one instance runs at a time. */
|
|
6028
|
+
async runBuild() {
|
|
6029
|
+
this._building = true;
|
|
6030
|
+
while (this._dirty) {
|
|
6031
|
+
this._dirty = false;
|
|
6032
|
+
if (!await this.initialize()) break;
|
|
6033
|
+
const mainSource = this._weslSrc[this._rootModuleName];
|
|
6034
|
+
if (!mainSource) {
|
|
6035
|
+
console.warn(`wgsl-play: root module "${this._rootModuleName}" not found in sources:`, Object.keys(this._weslSrc));
|
|
6036
|
+
continue;
|
|
6037
|
+
}
|
|
6038
|
+
try {
|
|
6039
|
+
this.errorOverlay.hide();
|
|
6040
|
+
if (!this._fromFullProject) {
|
|
6041
|
+
const { weslSrc, libs } = await fetchDependencies(mainSource, {
|
|
6042
|
+
shaderRoot: this.getConfigOverrides()?.shaderRoot,
|
|
6043
|
+
existingSources: this._weslSrc,
|
|
6044
|
+
skipExternal: !this._fetchLibs
|
|
6045
|
+
});
|
|
6046
|
+
this._weslSrc = {
|
|
6047
|
+
...this._weslSrc,
|
|
6048
|
+
...weslSrc
|
|
6049
|
+
};
|
|
6050
|
+
this._libs = dedupLibs(this._libs, libs);
|
|
6051
|
+
}
|
|
6052
|
+
await createPipeline(this.renderState, mainSource, {
|
|
6053
|
+
...this._linkOptions,
|
|
6054
|
+
weslSrc: this._weslSrc,
|
|
6055
|
+
libs: this._libs,
|
|
6056
|
+
rootModuleName: this._rootModuleName
|
|
6057
|
+
});
|
|
6058
|
+
if (!this._dirty) this.dispatchEvent(new CustomEvent("compile-success"));
|
|
6059
|
+
} catch (error) {
|
|
6060
|
+
if (!this._dirty) this.handleCompileError(error);
|
|
6061
|
+
}
|
|
6074
6062
|
}
|
|
6063
|
+
this._building = false;
|
|
6075
6064
|
}
|
|
6076
6065
|
handleCompileError(error) {
|
|
6077
6066
|
const message = error?.message ?? String(error);
|
|
@@ -6128,6 +6117,12 @@ function upgradeProperty(el, prop) {
|
|
|
6128
6117
|
el[prop] = value;
|
|
6129
6118
|
}
|
|
6130
6119
|
}
|
|
6120
|
+
/** Merge new libs, deduplicating by bundle name. */
|
|
6121
|
+
function dedupLibs(existing, newLibs) {
|
|
6122
|
+
if (!existing || newLibs.length === 0) return [...existing ?? [], ...newLibs];
|
|
6123
|
+
const names = new Set(newLibs.map((b) => b.name));
|
|
6124
|
+
return [...existing.filter((b) => !names.has(b.name)), ...newLibs];
|
|
6125
|
+
}
|
|
6131
6126
|
/** Normalize all keys in a weslSrc record to module paths. */
|
|
6132
6127
|
function toModulePaths(weslSrc, pkg) {
|
|
6133
6128
|
const result = {};
|
package/package.json
CHANGED
package/src/Renderer.ts
CHANGED
|
@@ -84,19 +84,25 @@ export async function createPipeline(
|
|
|
84
84
|
options?: LinkOptions,
|
|
85
85
|
): Promise<void> {
|
|
86
86
|
state.device.pushErrorScope("validation");
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
87
|
+
let gpuError: unknown;
|
|
88
|
+
let jsError: unknown;
|
|
89
|
+
try {
|
|
90
|
+
state.pipeline = await linkAndCreatePipeline({
|
|
91
|
+
device: state.device,
|
|
92
|
+
fragmentSource,
|
|
93
|
+
format: state.presentationFormat,
|
|
94
|
+
layout: state.pipelineLayout,
|
|
95
|
+
...options,
|
|
96
|
+
});
|
|
97
|
+
} catch (e) {
|
|
98
|
+
jsError = e;
|
|
99
|
+
} finally {
|
|
100
|
+
gpuError = await state.device.popErrorScope();
|
|
101
|
+
}
|
|
102
|
+
if (jsError || gpuError) {
|
|
96
103
|
state.pipeline = undefined;
|
|
97
|
-
throw gpuError;
|
|
104
|
+
throw jsError ?? gpuError;
|
|
98
105
|
}
|
|
99
|
-
state.pipeline = pipeline;
|
|
100
106
|
}
|
|
101
107
|
|
|
102
108
|
/** Start the render loop. Returns a stop function. */
|
package/src/WgslPlay.ts
CHANGED
|
@@ -83,6 +83,8 @@ export class WgslPlay extends HTMLElement {
|
|
|
83
83
|
private _sourceEl: HTMLElement | null = null;
|
|
84
84
|
private _sourceListener: ((e: Event) => void) | null = null;
|
|
85
85
|
private _fetchLibs = true;
|
|
86
|
+
private _dirty = false;
|
|
87
|
+
private _building = false;
|
|
86
88
|
private _theme: "light" | "dark" | "auto" = "auto";
|
|
87
89
|
private _mediaQuery: MediaQueryList | null = null;
|
|
88
90
|
private _onFullscreenChange = () =>
|
|
@@ -193,7 +195,7 @@ export class WgslPlay extends HTMLElement {
|
|
|
193
195
|
this._weslSrc = { [this._rootModuleName]: value };
|
|
194
196
|
this._libs = undefined;
|
|
195
197
|
this._fromFullProject = false;
|
|
196
|
-
this.
|
|
198
|
+
this.requestBuild();
|
|
197
199
|
}
|
|
198
200
|
|
|
199
201
|
/** Conditions for conditional compilation (@if/@elif/@else). */
|
|
@@ -204,8 +206,7 @@ export class WgslPlay extends HTMLElement {
|
|
|
204
206
|
set conditions(value: Conditions) {
|
|
205
207
|
this._linkOptions = { ...this._linkOptions, conditions: value };
|
|
206
208
|
if (Object.keys(this._weslSrc).length === 0) return;
|
|
207
|
-
|
|
208
|
-
else this.discoverAndRebuild();
|
|
209
|
+
this.requestBuild();
|
|
209
210
|
}
|
|
210
211
|
|
|
211
212
|
/** Set project configuration (mirrors wesl link() API). */
|
|
@@ -230,10 +231,8 @@ export class WgslPlay extends HTMLElement {
|
|
|
230
231
|
return;
|
|
231
232
|
}
|
|
232
233
|
|
|
233
|
-
// Partial update - may need to refetch if conditions changed
|
|
234
234
|
if (Object.keys(this._weslSrc).length === 0) return;
|
|
235
|
-
|
|
236
|
-
else this.discoverAndRebuild();
|
|
235
|
+
this.requestBuild();
|
|
237
236
|
}
|
|
238
237
|
|
|
239
238
|
/** Set sources from a full project with weslSrc. */
|
|
@@ -246,7 +245,7 @@ export class WgslPlay extends HTMLElement {
|
|
|
246
245
|
this._weslSrc = toModulePaths(weslSrc, pkg);
|
|
247
246
|
this._rootModuleName = fileToModulePath(root, pkg, false);
|
|
248
247
|
this._fromFullProject = true;
|
|
249
|
-
this.
|
|
248
|
+
this.requestBuild();
|
|
250
249
|
}
|
|
251
250
|
|
|
252
251
|
/** Whether to auto-fetch missing library packages from npm (default: true). */
|
|
@@ -365,7 +364,7 @@ export class WgslPlay extends HTMLElement {
|
|
|
365
364
|
? "premultiplied"
|
|
366
365
|
: "opaque";
|
|
367
366
|
this.renderState = await initWebGPU(this.canvas, alphaMode);
|
|
368
|
-
|
|
367
|
+
this.loadInitialContent();
|
|
369
368
|
this.stopRenderLoop = startRenderLoop(this.renderState, this.playback);
|
|
370
369
|
this.dispatchEvent(new CustomEvent("ready"));
|
|
371
370
|
return true;
|
|
@@ -383,12 +382,18 @@ export class WgslPlay extends HTMLElement {
|
|
|
383
382
|
}
|
|
384
383
|
|
|
385
384
|
/** Load from source element, src URL, script child, or inline textContent. */
|
|
386
|
-
private
|
|
385
|
+
private loadInitialContent(): void {
|
|
387
386
|
const sourceId = this.getAttribute("source");
|
|
388
|
-
if (sourceId)
|
|
387
|
+
if (sourceId) {
|
|
388
|
+
this.connectToSource(sourceId);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
389
391
|
|
|
390
392
|
const src = this.getAttribute("src");
|
|
391
|
-
if (src)
|
|
393
|
+
if (src) {
|
|
394
|
+
this.loadFromUrl(src);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
392
397
|
|
|
393
398
|
// Prefer <script type="text/wgsl"> or <script type="text/wesl"> (no HTML escaping needed)
|
|
394
399
|
const script = this.querySelector(
|
|
@@ -400,11 +405,11 @@ export class WgslPlay extends HTMLElement {
|
|
|
400
405
|
|
|
401
406
|
this._weslSrc = { [this._rootModuleName]: inlineSource };
|
|
402
407
|
this._fromFullProject = false;
|
|
403
|
-
|
|
408
|
+
this.requestBuild();
|
|
404
409
|
}
|
|
405
410
|
|
|
406
411
|
/** Connect to a source provider element (e.g., wgsl-edit). */
|
|
407
|
-
private
|
|
412
|
+
private connectToSource(id: string): void {
|
|
408
413
|
const el = document.getElementById(id);
|
|
409
414
|
if (!el) {
|
|
410
415
|
console.error(`wgsl-play: source element "${id}" not found`);
|
|
@@ -421,7 +426,6 @@ export class WgslPlay extends HTMLElement {
|
|
|
421
426
|
};
|
|
422
427
|
|
|
423
428
|
// Load initial sources, conditions, and libs from source element.
|
|
424
|
-
// Use discoverAndRebuild (not project setter) so external deps are fetched.
|
|
425
429
|
const { conditions, rootModuleName, libs } = el as any;
|
|
426
430
|
const root = rootModuleName ?? "main";
|
|
427
431
|
this._weslSrc = getSources();
|
|
@@ -429,8 +433,7 @@ export class WgslPlay extends HTMLElement {
|
|
|
429
433
|
if (conditions) this._linkOptions = { ...this._linkOptions, conditions };
|
|
430
434
|
if (libs) this._libs = libs;
|
|
431
435
|
this._fromFullProject = false;
|
|
432
|
-
|
|
433
|
-
this._fromFullProject = true; // fast rebuilds on subsequent edits
|
|
436
|
+
this.requestBuild();
|
|
434
437
|
|
|
435
438
|
// Listen for changes - rebuild with libs from source element
|
|
436
439
|
this._sourceListener = (e: Event) => {
|
|
@@ -446,12 +449,9 @@ export class WgslPlay extends HTMLElement {
|
|
|
446
449
|
el.addEventListener("change", this._sourceListener);
|
|
447
450
|
}
|
|
448
451
|
|
|
449
|
-
/** Fetch shader from URL,
|
|
452
|
+
/** Fetch shader from URL, then trigger a build. */
|
|
450
453
|
private async loadFromUrl(url: string): Promise<void> {
|
|
451
|
-
if (!this.renderState) return;
|
|
452
|
-
|
|
453
454
|
try {
|
|
454
|
-
this.errorOverlay.hide();
|
|
455
455
|
const { weslSrc, libs, rootModuleName } = await loadShaderFromUrl(
|
|
456
456
|
url,
|
|
457
457
|
this.getConfigOverrides()?.shaderRoot,
|
|
@@ -460,88 +460,58 @@ export class WgslPlay extends HTMLElement {
|
|
|
460
460
|
this._libs = libs;
|
|
461
461
|
this._fromFullProject = false;
|
|
462
462
|
if (rootModuleName) this._rootModuleName = rootModuleName;
|
|
463
|
-
|
|
464
|
-
const mainSource = weslSrc[this._rootModuleName];
|
|
465
|
-
if (!mainSource) {
|
|
466
|
-
console.warn(
|
|
467
|
-
`wgsl-play: root module "${this._rootModuleName}" not found in sources:`,
|
|
468
|
-
Object.keys(weslSrc),
|
|
469
|
-
);
|
|
470
|
-
return;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
await createPipeline(this.renderState, mainSource, {
|
|
474
|
-
...this._linkOptions,
|
|
475
|
-
weslSrc,
|
|
476
|
-
libs,
|
|
477
|
-
rootModuleName: this._rootModuleName,
|
|
478
|
-
});
|
|
479
|
-
this.dispatchEvent(new CustomEvent("compile-success"));
|
|
463
|
+
this.requestBuild();
|
|
480
464
|
} catch (error) {
|
|
481
465
|
this.handleCompileError(error);
|
|
482
466
|
}
|
|
483
467
|
}
|
|
484
468
|
|
|
485
|
-
/**
|
|
486
|
-
private
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
const mainSource = this._weslSrc[this._rootModuleName];
|
|
490
|
-
if (!mainSource) {
|
|
491
|
-
console.warn(
|
|
492
|
-
`wgsl-play: root module "${this._rootModuleName}" not found in sources:`,
|
|
493
|
-
Object.keys(this._weslSrc),
|
|
494
|
-
);
|
|
495
|
-
return;
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
try {
|
|
499
|
-
this.errorOverlay.hide();
|
|
500
|
-
await createPipeline(this.renderState!, mainSource, {
|
|
501
|
-
...this._linkOptions,
|
|
502
|
-
weslSrc: this._weslSrc,
|
|
503
|
-
libs: this._libs,
|
|
504
|
-
rootModuleName: this._rootModuleName,
|
|
505
|
-
});
|
|
506
|
-
this.dispatchEvent(new CustomEvent("compile-success"));
|
|
507
|
-
} catch (error) {
|
|
508
|
-
this.handleCompileError(error);
|
|
509
|
-
}
|
|
469
|
+
/** Mark build as needed. Coalesces rapid requests into a single build. */
|
|
470
|
+
private requestBuild(): void {
|
|
471
|
+
this._dirty = true;
|
|
472
|
+
if (!this._building) this.runBuild();
|
|
510
473
|
}
|
|
511
474
|
|
|
512
|
-
/**
|
|
513
|
-
private async
|
|
514
|
-
|
|
475
|
+
/** Run builds until no longer dirty. Only one instance runs at a time. */
|
|
476
|
+
private async runBuild(): Promise<void> {
|
|
477
|
+
this._building = true;
|
|
478
|
+
while (this._dirty) {
|
|
479
|
+
this._dirty = false;
|
|
480
|
+
if (!(await this.initialize())) break;
|
|
515
481
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
482
|
+
const mainSource = this._weslSrc[this._rootModuleName];
|
|
483
|
+
if (!mainSource) {
|
|
484
|
+
console.warn(
|
|
485
|
+
`wgsl-play: root module "${this._rootModuleName}" not found in sources:`,
|
|
486
|
+
Object.keys(this._weslSrc),
|
|
487
|
+
);
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
524
490
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
491
|
+
try {
|
|
492
|
+
this.errorOverlay.hide();
|
|
493
|
+
if (!this._fromFullProject) {
|
|
494
|
+
const { weslSrc, libs } = await fetchDependencies(mainSource, {
|
|
495
|
+
shaderRoot: this.getConfigOverrides()?.shaderRoot,
|
|
496
|
+
existingSources: this._weslSrc,
|
|
497
|
+
skipExternal: !this._fetchLibs,
|
|
498
|
+
});
|
|
499
|
+
this._weslSrc = { ...this._weslSrc, ...weslSrc };
|
|
500
|
+
this._libs = dedupLibs(this._libs, libs);
|
|
501
|
+
}
|
|
502
|
+
await createPipeline(this.renderState!, mainSource, {
|
|
503
|
+
...this._linkOptions,
|
|
504
|
+
weslSrc: this._weslSrc,
|
|
505
|
+
libs: this._libs,
|
|
506
|
+
rootModuleName: this._rootModuleName,
|
|
507
|
+
});
|
|
508
|
+
if (!this._dirty)
|
|
509
|
+
this.dispatchEvent(new CustomEvent("compile-success"));
|
|
510
|
+
} catch (error) {
|
|
511
|
+
if (!this._dirty) this.handleCompileError(error);
|
|
512
|
+
}
|
|
544
513
|
}
|
|
514
|
+
this._building = false;
|
|
545
515
|
}
|
|
546
516
|
|
|
547
517
|
private handleCompileError(error: unknown): void {
|
|
@@ -617,6 +587,17 @@ function upgradeProperty(el: HTMLElement, prop: string): void {
|
|
|
617
587
|
}
|
|
618
588
|
}
|
|
619
589
|
|
|
590
|
+
/** Merge new libs, deduplicating by bundle name. */
|
|
591
|
+
function dedupLibs(
|
|
592
|
+
existing: WeslBundle[] | undefined,
|
|
593
|
+
newLibs: WeslBundle[],
|
|
594
|
+
): WeslBundle[] {
|
|
595
|
+
if (!existing || newLibs.length === 0)
|
|
596
|
+
return [...(existing ?? []), ...newLibs];
|
|
597
|
+
const names = new Set(newLibs.map(b => b.name));
|
|
598
|
+
return [...existing.filter(b => !names.has(b.name)), ...newLibs];
|
|
599
|
+
}
|
|
600
|
+
|
|
620
601
|
/** Normalize all keys in a weslSrc record to module paths. */
|
|
621
602
|
function toModulePaths(
|
|
622
603
|
weslSrc: Record<string, string>,
|