wgsl-play 0.0.32 → 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;
@@ -1,4 +1,4 @@
1
- import { BundleResolver, CompositeResolver, RecordResolver, requestWeslDevice } from "wesl";
1
+ import { requestWeslDevice } from "wesl";
2
2
  import { linkAndCreatePipeline, renderFrame, updateRenderUniforms } from "wesl-gpu";
3
3
 
4
4
  //#region src/Config.ts
@@ -153,26 +153,13 @@ async function initWebGPU(canvas, alphaMode = "opaque") {
153
153
  }
154
154
  /** Compile WESL fragment shader and create render pipeline. */
155
155
  async function createPipeline(state, fragmentSource, options) {
156
- const { weslSrc, libs = [], conditions, constants } = options ?? {};
157
- const { packageName, rootModuleName } = options ?? {};
158
- let resolver;
159
- if (weslSrc || libs.length > 0) {
160
- const resolvers = [];
161
- if (weslSrc) resolvers.push(new RecordResolver(weslSrc, { packageName }));
162
- for (const bundle of libs) resolvers.push(new BundleResolver(bundle));
163
- resolver = resolvers.length === 1 ? resolvers[0] : new CompositeResolver(resolvers);
164
- }
165
156
  state.device.pushErrorScope("validation");
166
157
  const pipeline = await linkAndCreatePipeline({
167
158
  device: state.device,
168
159
  fragmentSource,
169
- resolver,
170
160
  format: state.presentationFormat,
171
161
  layout: state.pipelineLayout,
172
- conditions,
173
- constants,
174
- packageName,
175
- rootModuleName
162
+ ...options
176
163
  });
177
164
  const gpuError = await state.device.popErrorScope();
178
165
  if (gpuError) {
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-CrELe3mK.js";
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-Ben4VdsC.js";
2
2
  import { fetchDependencies, loadShaderFromUrl } from "wesl-fetch";
3
3
  import { WeslParseError, fileToModulePath } from "wesl";
4
4
 
@@ -155,7 +155,7 @@ var WgslPlay = class extends HTMLElement {
155
155
  this._weslSrc = Object.fromEntries(entries);
156
156
  this._rootModuleName = rootModuleName ? toModulePath(rootModuleName) : "package::main";
157
157
  this._fromFullProject = true;
158
- this.discoverAndRebuild();
158
+ this.rebuildPipeline();
159
159
  }
160
160
  /** Whether to auto-fetch missing library packages from npm (default: true). */
161
161
  get fetchLibs() {
@@ -286,15 +286,26 @@ var WgslPlay = class extends HTMLElement {
286
286
  return { [this._rootModuleName]: source };
287
287
  };
288
288
  const conditions = el.conditions;
289
- this.project = {
290
- weslSrc: getSources(),
289
+ const rootModuleName = el.rootModuleName;
290
+ const libs = el.libs;
291
+ const weslSrc = getSources();
292
+ const entries = Object.entries(weslSrc).map(([k, v]) => [toModulePath(k), v]);
293
+ this._weslSrc = Object.fromEntries(entries);
294
+ this._rootModuleName = rootModuleName ? toModulePath(rootModuleName) : "package::main";
295
+ if (conditions) this._linkOptions = {
296
+ ...this._linkOptions,
291
297
  conditions
292
298
  };
299
+ if (libs) this._libs = libs;
300
+ this._fromFullProject = false;
301
+ await this.discoverAndRebuild();
302
+ this._fromFullProject = true;
293
303
  this._sourceListener = (e) => {
294
304
  const detail = e.detail;
295
305
  const fallback = { [this._rootModuleName]: detail?.source ?? "" };
296
306
  this.project = {
297
307
  weslSrc: detail?.sources ?? fallback,
308
+ rootModuleName: detail?.rootModuleName,
298
309
  conditions: detail?.conditions
299
310
  };
300
311
  };
@@ -311,7 +322,10 @@ var WgslPlay = class extends HTMLElement {
311
322
  this._fromFullProject = false;
312
323
  if (rootModuleName) this._rootModuleName = rootModuleName;
313
324
  const mainSource = weslSrc[this._rootModuleName];
314
- if (!mainSource) return;
325
+ if (!mainSource) {
326
+ console.warn(`wgsl-play: root module "${this._rootModuleName}" not found in sources:`, Object.keys(weslSrc));
327
+ return;
328
+ }
315
329
  await createPipeline(this.renderState, mainSource, {
316
330
  ...this._linkOptions,
317
331
  weslSrc,
@@ -327,7 +341,10 @@ var WgslPlay = class extends HTMLElement {
327
341
  async rebuildPipeline() {
328
342
  if (!await this.initialize()) return;
329
343
  const mainSource = this._weslSrc[this._rootModuleName];
330
- if (!mainSource) return;
344
+ if (!mainSource) {
345
+ console.warn(`wgsl-play: root module "${this._rootModuleName}" not found in sources:`, Object.keys(this._weslSrc));
346
+ return;
347
+ }
331
348
  try {
332
349
  this.errorOverlay.hide();
333
350
  await createPipeline(this.renderState, mainSource, {
@@ -345,7 +362,10 @@ var WgslPlay = class extends HTMLElement {
345
362
  async discoverAndRebuild() {
346
363
  if (!await this.initialize()) return;
347
364
  const mainSource = this._weslSrc[this._rootModuleName];
348
- if (!mainSource) return;
365
+ if (!mainSource) {
366
+ console.warn(`wgsl-play: root module "${this._rootModuleName}" not found in sources:`, Object.keys(this._weslSrc));
367
+ return;
368
+ }
349
369
  try {
350
370
  this.errorOverlay.hide();
351
371
  const { weslSrc, libs } = await fetchDependencies(mainSource, {
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { c as getConfig, l as resetConfig, s as defaults } from "./WgslPlay-CrELe3mK.js";
1
+ import { c as getConfig, l as resetConfig, s as defaults } from "./WgslPlay-Ben4VdsC.js";
2
2
  import { WgslPlay } from "./WgslPlay.js";
3
3
 
4
4
  export * from "wesl-fetch"
package/dist/wgsl-play.js CHANGED
@@ -970,11 +970,78 @@ function emptyScope(parent, kind = "scope") {
970
970
  }
971
971
 
972
972
  //#endregion
973
- //#region ../wesl/src/LowerAndEmit.ts
974
- /** Valid WGSL standard attributes (from spec). Non-WGSL attributes are stripped.
975
- * See: https://www.w3.org/TR/WGSL/#attributes
976
- * Note: @builtin, @diagnostic, @interpolate are parsed as separate attribute types. */
977
- const wgslStandardAttributes$1 = new Set([
973
+ //#region ../wesl/src/StandardTypes.ts
974
+ const stdFns = `bitcast all any select arrayLength
975
+ abs acos acosh asin asinh atan atanh atan2 ceil clamp cos cosh
976
+ countLeadingZeros countOneBits countTrailingZeros cross
977
+ degrees determinant distance dot dot4U8Packed dot4I8Packed
978
+ exp exp2 extractBits faceForward firstLeadingBit firstTrailingBit
979
+ floor fma fract frexp insertBits inverseSqrt ldexp length log log2
980
+ max min mix modf normalize pow quantizeToF16 radians reflect refract
981
+ reverseBits round saturate sign sin sinh smoothstep sqrt step tan tanh
982
+ transpose trunc
983
+ dpdx dpdxCoarse dpdxFine dpdy dpdyCoarse dpdyFine fwidth
984
+ fwidthCoarse fwidthFine
985
+ textureDimensions textureGather textureGatherCompare textureLoad
986
+ textureNumLayers textureNumLevels textureNumSamples
987
+ textureSample textureSampleBias textureSampleCompare textureSampleCompareLevel
988
+ textureSampleGrad textureSampleLevel textureSampleBaseClampToEdge
989
+ textureStore
990
+ atomicLoad atomicStore atomicAdd atomicSub atomicMax atomicMin
991
+ atomicAnd atomicOr atomicXor atomicExchange atomicCompareExchangeWeak
992
+ pack4x8snorm pack4x8unorm pack4xI8 pack4xU8 pack4xI8Clamp pack4xU8Clamp
993
+ pack2x16snorm pack2x16unorm pack2x16float
994
+ unpack4x8snorm unpack4x8unorm unpack4xI8 unpack4xU8
995
+ unpack2x16snorm unpack2x16unorm unpack2x16float
996
+ storageBarrier textureBarrier workgroupBarrier workgroupUniformLoad
997
+ subgroupAdd subgroupAll subgroupAnd subgroupAny subgroupBallot
998
+ subgroupBroadcast subgroupBroadcastFirst subgroupElect
999
+ subgroupExclusiveAdd subgroupExclusiveMul subgroupInclusiveAdd
1000
+ subgroupInclusiveMul subgroupMax subgroupMin subgroupMul subgroupOr
1001
+ subgroupShuffle subgroupShuffleUp subgroupShuffleXor subgroupXor
1002
+ quadBroadcast quadSwapDiagonal quadSwapX quadSwapY`.split(/\s+/);
1003
+ const sampledTextureTypes = `
1004
+ texture_1d texture_2d texture_2d_array texture_3d
1005
+ texture_cube texture_cube_array
1006
+ `;
1007
+ const multisampledTextureTypes = `
1008
+ texture_multisampled_2d texture_depth_multisampled_2d
1009
+ `;
1010
+ const textureStorageTypes = `
1011
+ texture_storage_1d texture_storage_2d texture_storage_2d_array
1012
+ texture_storage_3d
1013
+ `;
1014
+ const stdTypes = `array atomic bool f16 f32 i32
1015
+ mat2x2 mat2x3 mat2x4 mat3x2 mat3x3 mat3x4 mat4x2 mat4x3 mat4x4
1016
+ mat2x2f mat2x3f mat2x4f mat3x2f mat3x3f mat3x4f
1017
+ mat4x2f mat4x3f mat4x4f
1018
+ mat2x2h mat2x3h mat2x4h mat3x2h mat3x3h mat3x4h
1019
+ mat4x2h mat4x3h mat4x4h
1020
+ u32 vec2 vec3 vec4 ptr
1021
+ vec2i vec3i vec4i vec2u vec3u vec4u
1022
+ vec2f vec3f vec4f vec2h vec3h vec4h
1023
+ ${sampledTextureTypes}
1024
+ ${multisampledTextureTypes}
1025
+ texture_external
1026
+ ${textureStorageTypes}
1027
+ texture_depth_2d texture_depth_2d_array texture_depth_cube
1028
+ texture_depth_cube_array
1029
+ sampler sampler_comparison
1030
+ rgba8unorm rgba8snorm rgba8uint rgba8sint
1031
+ rgba16uint rgba16sint rgba16float
1032
+ r32uint r32sint r32float rg32uint rg32sint rg32float
1033
+ rgba32uint rgba32sint rgba32float
1034
+ bgra8unorm`.split(/\s+/);
1035
+ /** https://www.w3.org/TR/WGSL/#predeclared-enumerants */
1036
+ const stdEnumerants = `read write read_write
1037
+ function private workgroup uniform storage
1038
+ rgba8unorm rgba8snorm rgba8uint rgba8sint
1039
+ rgba16uint rgba16sint rgba16float
1040
+ r32uint r32sint r32float rg32uint rg32sint rg32float
1041
+ rgba32uint rgba32sint rgba32float bgra8unorm`.split(/\s+/);
1042
+ /** WGSL standard attributes whose params need binding (e.g., @workgroup_size).
1043
+ * See: https://www.w3.org/TR/WGSL/#attributes */
1044
+ const wgslStandardAttributes = new Set([
978
1045
  "align",
979
1046
  "binding",
980
1047
  "blend_src",
@@ -990,6 +1057,21 @@ const wgslStandardAttributes$1 = new Set([
990
1057
  "vertex",
991
1058
  "workgroup_size"
992
1059
  ]);
1060
+ /** return true if the name is for a built in type (not a user struct) */
1061
+ function stdType(name) {
1062
+ return stdTypes.includes(name);
1063
+ }
1064
+ /** return true if the name is for a built in fn (not a user function) */
1065
+ function stdFn(name) {
1066
+ return stdFns.includes(name) || stdType(name);
1067
+ }
1068
+ /** return true if the name is for a built in enumerant */
1069
+ function stdEnumerant(name) {
1070
+ return stdEnumerants.includes(name);
1071
+ }
1072
+
1073
+ //#endregion
1074
+ //#region ../wesl/src/LowerAndEmit.ts
993
1075
  /** Traverse the AST, starting from root elements, emitting WGSL for each. */
994
1076
  function lowerAndEmit(params) {
995
1077
  const { srcBuilder, rootElems, conditions } = params;
@@ -1269,7 +1351,7 @@ function emitAttribute(e, ctx) {
1269
1351
  const { kind } = e.attribute;
1270
1352
  if (kind === "@if" || kind === "@elif" || kind === "@else") return false;
1271
1353
  if (kind === "@attribute") {
1272
- if (!wgslStandardAttributes$1.has(e.attribute.name)) return false;
1354
+ if (!wgslStandardAttributes.has(e.attribute.name)) return false;
1273
1355
  emitStandardAttribute(e, ctx);
1274
1356
  return true;
1275
1357
  }
@@ -3750,115 +3832,16 @@ function flatImports(ast, conditions) {
3750
3832
  return flat;
3751
3833
  }
3752
3834
 
3753
- //#endregion
3754
- //#region ../wesl/src/StandardTypes.ts
3755
- const stdFns = `bitcast all any select arrayLength
3756
- abs acos acosh asin asinh atan atanh atan2 ceil clamp cos cosh
3757
- countLeadingZeros countOneBits countTrailingZeros cross
3758
- degrees determinant distance dot dot4U8Packed dot4I8Packed
3759
- exp exp2 extractBits faceForward firstLeadingBit firstTrailingBit
3760
- floor fma fract frexp insertBits inverseSqrt ldexp length log log2
3761
- max min mix modf normalize pow quantizeToF16 radians reflect refract
3762
- reverseBits round saturate sign sin sinh smoothstep sqrt step tan tanh
3763
- transpose trunc
3764
- dpdx dpdxCoarse dpdxFine dpdy dpdyCoarse dpdyFine fwidth
3765
- fwidthCoarse fwidthFine
3766
- textureDimensions textureGather textureGatherCompare textureLoad
3767
- textureNumLayers textureNumLevels textureNumSamples
3768
- textureSample textureSampleBias textureSampleCompare textureSampleCompareLevel
3769
- textureSampleGrad textureSampleLevel textureSampleBaseClampToEdge
3770
- textureStore
3771
- atomicLoad atomicStore atomicAdd atomicSub atomicMax atomicMin
3772
- atomicAnd atomicOr atomicXor atomicExchange atomicCompareExchangeWeak
3773
- pack4x8snorm pack4x8unorm pack4xI8 pack4xU8 pack4xI8Clamp pack4xU8Clamp
3774
- pack2x16snorm pack2x16unorm pack2x16float
3775
- unpack4x8snorm unpack4x8unorm unpack4xI8 unpack4xU8
3776
- unpack2x16snorm unpack2x16unorm unpack2x16float
3777
- storageBarrier textureBarrier workgroupBarrier workgroupUniformLoad
3778
- subgroupAdd subgroupAll subgroupAnd subgroupAny subgroupBallot
3779
- subgroupBroadcast subgroupBroadcastFirst subgroupElect
3780
- subgroupExclusiveAdd subgroupExclusiveMul subgroupInclusiveAdd
3781
- subgroupInclusiveMul subgroupMax subgroupMin subgroupMul subgroupOr
3782
- subgroupShuffle subgroupShuffleUp subgroupShuffleXor subgroupXor
3783
- quadBroadcast quadSwapDiagonal quadSwapX quadSwapY`.split(/\s+/);
3784
- const sampledTextureTypes = `
3785
- texture_1d texture_2d texture_2d_array texture_3d
3786
- texture_cube texture_cube_array
3787
- `;
3788
- const multisampledTextureTypes = `
3789
- texture_multisampled_2d texture_depth_multisampled_2d
3790
- `;
3791
- const textureStorageTypes = `
3792
- texture_storage_1d texture_storage_2d texture_storage_2d_array
3793
- texture_storage_3d
3794
- `;
3795
- const stdTypes = `array atomic bool f16 f32 i32
3796
- mat2x2 mat2x3 mat2x4 mat3x2 mat3x3 mat3x4 mat4x2 mat4x3 mat4x4
3797
- mat2x2f mat2x3f mat2x4f mat3x2f mat3x3f mat3x4f
3798
- mat4x2f mat4x3f mat4x4f
3799
- mat2x2h mat2x3h mat2x4h mat3x2h mat3x3h mat3x4h
3800
- mat4x2h mat4x3h mat4x4h
3801
- u32 vec2 vec3 vec4 ptr
3802
- vec2i vec3i vec4i vec2u vec3u vec4u
3803
- vec2f vec3f vec4f vec2h vec3h vec4h
3804
- ${sampledTextureTypes}
3805
- ${multisampledTextureTypes}
3806
- texture_external
3807
- ${textureStorageTypes}
3808
- texture_depth_2d texture_depth_2d_array texture_depth_cube
3809
- texture_depth_cube_array
3810
- sampler sampler_comparison
3811
- rgba8unorm rgba8snorm rgba8uint rgba8sint
3812
- rgba16uint rgba16sint rgba16float
3813
- r32uint r32sint r32float rg32uint rg32sint rg32float
3814
- rgba32uint rgba32sint rgba32float
3815
- bgra8unorm`.split(/\s+/);
3816
- /** https://www.w3.org/TR/WGSL/#predeclared-enumerants */
3817
- const stdEnumerants = `read write read_write
3818
- function private workgroup uniform storage
3819
- rgba8unorm rgba8snorm rgba8uint rgba8sint
3820
- rgba16uint rgba16sint rgba16float
3821
- r32uint r32sint r32float rg32uint rg32sint rg32float
3822
- rgba32uint rgba32sint rgba32float bgra8unorm`.split(/\s+/);
3823
- /** return true if the name is for a built in type (not a user struct) */
3824
- function stdType(name) {
3825
- return stdTypes.includes(name);
3826
- }
3827
- /** return true if the name is for a built in fn (not a user function) */
3828
- function stdFn(name) {
3829
- return stdFns.includes(name) || stdType(name);
3830
- }
3831
- /** return true if the name is for a built in enumerant */
3832
- function stdEnumerant(name) {
3833
- return stdEnumerants.includes(name);
3834
- }
3835
-
3836
3835
  //#endregion
3837
3836
  //#region ../wesl/src/BindIdents.ts
3838
- /** WGSL standard attributes whose params need binding (e.g., @workgroup_size). */
3839
- const wgslStandardAttributes = new Set([
3840
- "align",
3841
- "binding",
3842
- "blend_src",
3843
- "compute",
3844
- "const",
3845
- "fragment",
3846
- "group",
3847
- "id",
3848
- "invariant",
3849
- "location",
3850
- "must_use",
3851
- "size",
3852
- "vertex",
3853
- "workgroup_size"
3854
- ]);
3855
3837
  /** Bind ref idents to declarations and mangle global declaration names. */
3856
3838
  function bindIdents(params) {
3857
3839
  const { rootAst, resolver, virtuals, accumulateUnbound } = params;
3858
3840
  const { conditions = {}, mangler = minimalMangle } = params;
3841
+ const { discoveryMode } = params;
3859
3842
  const packageName = rootAst.srcModule.modulePath.split("::")[0];
3860
- const validRootDecls = findValidRootDecls(rootAst.rootScope, conditions);
3861
- const { globalNames, knownDecls } = initRootDecls(validRootDecls);
3843
+ const rootDecls = discoveryMode ? findAllRootDecls(rootAst.rootScope) : findValidRootDecls(rootAst.rootScope, conditions);
3844
+ const { globalNames, knownDecls } = initRootDecls(rootDecls);
3862
3845
  const bindContext = {
3863
3846
  resolver,
3864
3847
  conditions,
@@ -3869,13 +3852,14 @@ function bindIdents(params) {
3869
3852
  foundScopes: /* @__PURE__ */ new Set(),
3870
3853
  globalNames,
3871
3854
  globalStatements: /* @__PURE__ */ new Map(),
3872
- unbound: accumulateUnbound ? [] : void 0
3855
+ unbound: accumulateUnbound ? [] : void 0,
3856
+ discoveryMode
3873
3857
  };
3874
3858
  const liveDecls = {
3875
- decls: new Map(validRootDecls.map((d) => [d.originalName, d])),
3859
+ decls: new Map(rootDecls.map((d) => [d.originalName, d])),
3876
3860
  parent: null
3877
3861
  };
3878
- const fromRootDecls = validRootDecls.flatMap((decl) => processDependentScope(decl, bindContext));
3862
+ const fromRootDecls = rootDecls.flatMap((decl) => processDependentScope(decl, bindContext));
3879
3863
  const fromRefs = bindIdentsRecursive(rootAst.rootScope, bindContext, liveDecls);
3880
3864
  const newStatements = [...bindContext.globalStatements.values()];
3881
3865
  return {
@@ -3911,17 +3895,11 @@ function* validItems(scope, conditions) {
3911
3895
  }
3912
3896
  /** Find all conditionally valid declarations at the root level. */
3913
3897
  function findValidRootDecls(rootScope, conditions) {
3914
- const found = [];
3915
- for (const item of validItems(rootScope, conditions)) if (item.kind === "decl") found.push(item);
3916
- else if (item.kind === "partial") collectDecls(item, found);
3917
- return found;
3898
+ return collectDecls(validItems(rootScope, conditions));
3918
3899
  }
3919
3900
  /** Find all declarations at the root level, ignoring conditions. */
3920
3901
  function findAllRootDecls(rootScope) {
3921
- const found = [];
3922
- for (const item of rootScope.contents) if (item.kind === "decl") found.push(item);
3923
- else if (item.kind === "partial") collectDecls(item, found);
3924
- return found;
3902
+ return collectDecls(rootScope.contents);
3925
3903
  }
3926
3904
  /** Find a public declaration with the given original name. */
3927
3905
  function publicDecl(scope, name, conditions) {
@@ -3989,10 +3967,11 @@ function handleRef(ident, liveDecls, bindContext) {
3989
3967
  }
3990
3968
  if (!bindContext.unbound) failIdent(ident, `unresolved identifier '${ident.originalName}'`);
3991
3969
  }
3970
+ /** Follow new global declarations into their dependent scopes. */
3992
3971
  function handleDecls(newGlobals, bindContext) {
3993
3972
  return newGlobals.flatMap((decl) => processDependentScope(decl, bindContext));
3994
3973
  }
3995
- /** If found declaration is new, mangle its name. Return if it's a global declaration. */
3974
+ /** If found declaration is new, mangle its name. @return the decl if it's global. */
3996
3975
  function handleNewDecl(refIdent, foundDecl, ctx) {
3997
3976
  const { decl, moduleAst } = foundDecl;
3998
3977
  const { knownDecls, globalNames, mangler, globalStatements } = ctx;
@@ -4019,7 +3998,8 @@ function findDeclInModule(ident, liveDecls) {
4019
3998
  /** Match a ref ident to a declaration in another module via import or qualified ident. */
4020
3999
  function findQualifiedImport(refIdent, ctx) {
4021
4000
  const { conditions, unbound, discoveryMode } = ctx;
4022
- const flatImps = flatImports(refIdent.ast, discoveryMode ? void 0 : conditions);
4001
+ const conds = discoveryMode ? void 0 : conditions;
4002
+ const flatImps = flatImports(refIdent.ast, conds);
4023
4003
  const identParts = refIdent.originalName.split("::");
4024
4004
  const pathParts = matchingImport(identParts, flatImps) ?? qualifiedIdent(identParts);
4025
4005
  if (!pathParts) {
@@ -4073,7 +4053,7 @@ function virtualModule(moduleName, ctx) {
4073
4053
  /** Get cached valid root declarations, computing on first access. */
4074
4054
  function getValidRootDecls(rootScope, conditions) {
4075
4055
  const lexScope = rootScope;
4076
- if (!lexScope._validRootDecls) lexScope._validRootDecls = findValidRootDecls(rootScope, conditions);
4056
+ lexScope._validRootDecls ??= findValidRootDecls(rootScope, conditions);
4077
4057
  return lexScope._validRootDecls;
4078
4058
  }
4079
4059
  /** Given a global declIdent, return the liveDecls for its root scope. */
@@ -4102,13 +4082,167 @@ function setMangledName(proposedName, decl, globalNames, srcModule, mangler) {
4102
4082
  function stdWgsl(name) {
4103
4083
  return stdType(name) || stdFn(name) || stdEnumerant(name);
4104
4084
  }
4085
+ /** @return identParts if it's a qualified path (has ::). */
4105
4086
  function qualifiedIdent(identParts) {
4106
4087
  if (identParts.length > 1) return identParts;
4107
4088
  }
4108
- /** Collect all declarations in a scope (used when scope is already validated). */
4109
- function collectDecls(scope, found) {
4110
- for (const item of scope.contents) if (item.kind === "decl") found.push(item);
4111
- else if (item.kind === "partial") collectDecls(item, found);
4089
+ /** Collect all declarations from scope items, recursing into partial scopes. */
4090
+ function collectDecls(items) {
4091
+ return [...items].flatMap((item) => {
4092
+ const { kind } = item;
4093
+ if (kind === "decl") return [item];
4094
+ if (kind === "partial") return collectDecls(item.contents);
4095
+ return [];
4096
+ });
4097
+ }
4098
+
4099
+ //#endregion
4100
+ //#region ../wesl/src/PathUtil.ts
4101
+ /** simplistic path manipulation utilities */
4102
+ /** return path with ./ and foo/.. elements removed */
4103
+ function normalize(path) {
4104
+ const noDots = path.split("/").filter((s) => s !== ".");
4105
+ const noDbl = [];
4106
+ noDots.forEach((s) => {
4107
+ if (s !== "") if (s === ".." && noDbl.length && noDbl[noDbl.length - 1] !== "..") noDbl.pop();
4108
+ else noDbl.push(s);
4109
+ });
4110
+ return noDbl.join("/");
4111
+ }
4112
+ /** return path w/o a suffix.
4113
+ * e.g. /foo/bar.wgsl => /foo/bar */
4114
+ function noSuffix(path) {
4115
+ const lastSlash = path.lastIndexOf("/");
4116
+ const lastStart = lastSlash === -1 ? 0 : lastSlash + 1;
4117
+ const suffix = path.indexOf(".", lastStart);
4118
+ const suffixStart = suffix === -1 ? path.length : suffix;
4119
+ return path.slice(0, suffixStart);
4120
+ }
4121
+
4122
+ //#endregion
4123
+ //#region ../wesl/src/ModuleResolver.ts
4124
+ const libRegex = /^lib\.w[eg]sl$/i;
4125
+ /** Module resolver for in-memory source records. Lazy by default. */
4126
+ var RecordResolver = class {
4127
+ astCache = /* @__PURE__ */ new Map();
4128
+ sources;
4129
+ packageName;
4130
+ debugWeslRoot;
4131
+ constructor(sources, options = {}) {
4132
+ const { packageName = "package", debugWeslRoot } = options;
4133
+ this.sources = sources;
4134
+ this.packageName = packageName;
4135
+ this.debugWeslRoot = normalizeDebugRoot(debugWeslRoot);
4136
+ }
4137
+ resolveModule(modulePath) {
4138
+ const cached = this.astCache.get(modulePath);
4139
+ if (cached) return cached;
4140
+ const source = this.findSource(modulePath);
4141
+ if (source === void 0) return void 0;
4142
+ const ast = parseSrcModule({
4143
+ modulePath,
4144
+ debugFilePath: this.modulePathToDebugPath(modulePath),
4145
+ src: source
4146
+ });
4147
+ this.astCache.set(modulePath, ast);
4148
+ return ast;
4149
+ }
4150
+ findSource(modulePath) {
4151
+ if (this.sources[modulePath] !== void 0) return this.sources[modulePath];
4152
+ const filePath = this.moduleToFilePath(modulePath);
4153
+ if (filePath === void 0) return void 0;
4154
+ return findInVariants(this.sources, filePath);
4155
+ }
4156
+ /** Convert module path to file path, or undefined if not local. */
4157
+ moduleToFilePath(modulePath) {
4158
+ return moduleToRelativePath(modulePath, this.packageName);
4159
+ }
4160
+ modulePathToDebugPath(modulePath) {
4161
+ const filePath = this.moduleToFilePath(modulePath) ?? modulePath;
4162
+ return this.debugWeslRoot + filePath + ".wesl";
4163
+ }
4164
+ /** Parse all modules and return entries. */
4165
+ allModules() {
4166
+ for (const filePath of Object.keys(this.sources)) {
4167
+ const treatLibAsRoot = this.packageName !== "package";
4168
+ const modulePath = fileToModulePath(filePath, this.packageName, treatLibAsRoot);
4169
+ this.resolveModule(modulePath);
4170
+ }
4171
+ return this.astCache.entries();
4172
+ }
4173
+ };
4174
+ /** Composite resolver that tries each resolver in order until one succeeds. */
4175
+ var CompositeResolver = class {
4176
+ resolvers;
4177
+ constructor(resolvers) {
4178
+ this.resolvers = resolvers;
4179
+ }
4180
+ resolveModule(modulePath) {
4181
+ for (const resolver of this.resolvers) {
4182
+ const ast = resolver.resolveModule(modulePath);
4183
+ if (ast) return ast;
4184
+ }
4185
+ }
4186
+ };
4187
+ /** Lazy resolver for WeslBundle library modules. */
4188
+ var BundleResolver = class {
4189
+ astCache = /* @__PURE__ */ new Map();
4190
+ sources;
4191
+ packageName;
4192
+ debugWeslRoot;
4193
+ constructor(bundle, debugWeslRoot) {
4194
+ this.sources = bundle.modules;
4195
+ this.packageName = bundle.name;
4196
+ this.debugWeslRoot = normalizeDebugRoot(debugWeslRoot);
4197
+ }
4198
+ resolveModule(modulePath) {
4199
+ const pkgPrefix = this.packageName + "::";
4200
+ if (modulePath !== this.packageName && !modulePath.startsWith(pkgPrefix)) return;
4201
+ const cached = this.astCache.get(modulePath);
4202
+ if (cached) return cached;
4203
+ const source = this.findSource(modulePath);
4204
+ if (!source) return void 0;
4205
+ const ast = parseSrcModule({
4206
+ modulePath,
4207
+ debugFilePath: this.modulePathToDebugPath(modulePath),
4208
+ src: source
4209
+ });
4210
+ this.astCache.set(modulePath, ast);
4211
+ return ast;
4212
+ }
4213
+ findSource(modulePath) {
4214
+ const filePath = this.moduleToFilePath(modulePath);
4215
+ if (modulePath === this.packageName) {
4216
+ const libSrc = findInVariants(this.sources, "lib", ["wesl", "wgsl"]);
4217
+ if (libSrc) return libSrc;
4218
+ }
4219
+ return findInVariants(this.sources, filePath);
4220
+ }
4221
+ moduleToFilePath(modulePath) {
4222
+ return moduleToRelativePath(modulePath, this.packageName) ?? modulePath;
4223
+ }
4224
+ modulePathToDebugPath(modulePath) {
4225
+ const filePath = this.moduleToFilePath(modulePath);
4226
+ return this.debugWeslRoot + this.packageName + "/" + filePath + ".wesl";
4227
+ }
4228
+ };
4229
+ /** Convert file path to module path (e.g., "foo/bar.wesl" to "package::foo::bar"). */
4230
+ function fileToModulePath(filePath, packageName, treatLibAsRoot) {
4231
+ if (filePath.includes("::")) return filePath;
4232
+ if (treatLibAsRoot && libRegex.test(filePath)) return packageName;
4233
+ const moduleSuffix = noSuffix(normalize(filePath)).replaceAll("/", "::");
4234
+ return packageName + "::" + moduleSuffix;
4235
+ }
4236
+ /** Try path variants with/without ./ prefix and extension suffixes. */
4237
+ function findInVariants(sources, basePath, extensions = ["wesl", "wgsl"]) {
4238
+ for (const prefix of ["", "./"]) {
4239
+ const path = prefix + basePath;
4240
+ if (sources[path] !== void 0) return sources[path];
4241
+ for (const ext of extensions) {
4242
+ const withExt = `${path}.${ext}`;
4243
+ if (sources[withExt] !== void 0) return sources[withExt];
4244
+ }
4245
+ }
4112
4246
  }
4113
4247
 
4114
4248
  //#endregion
@@ -4147,9 +4281,7 @@ function findUnboundRefs(resolver) {
4147
4281
  decls: new Map(rootDecls.map((d) => [d.originalName, d])),
4148
4282
  parent: null
4149
4283
  };
4150
- filterMap(rootDecls, (decl) => decl.dependentScope).forEach((s) => {
4151
- bindIdentsRecursive(s, bindContext, makeLiveDecls(liveDecls));
4152
- });
4284
+ for (const s of filterMap(rootDecls, (decl) => decl.dependentScope)) bindIdentsRecursive(s, bindContext, makeLiveDecls(liveDecls));
4153
4285
  bindIdentsRecursive(ast.rootScope, bindContext, liveDecls);
4154
4286
  }
4155
4287
  return bindContext.unbound;
@@ -4292,155 +4424,6 @@ function compilationInfoToErrorMessage(compilationInfo, shaderModule) {
4292
4424
  return result;
4293
4425
  }
4294
4426
 
4295
- //#endregion
4296
- //#region ../wesl/src/PathUtil.ts
4297
- /** simplistic path manipulation utilities */
4298
- /** return path with ./ and foo/.. elements removed */
4299
- function normalize(path) {
4300
- const noDots = path.split("/").filter((s) => s !== ".");
4301
- const noDbl = [];
4302
- noDots.forEach((s) => {
4303
- if (s !== "") if (s === ".." && noDbl.length && noDbl[noDbl.length - 1] !== "..") noDbl.pop();
4304
- else noDbl.push(s);
4305
- });
4306
- return noDbl.join("/");
4307
- }
4308
- /** return path w/o a suffix.
4309
- * e.g. /foo/bar.wgsl => /foo/bar */
4310
- function noSuffix(path) {
4311
- const lastSlash = path.lastIndexOf("/");
4312
- const lastStart = lastSlash === -1 ? 0 : lastSlash + 1;
4313
- const suffix = path.indexOf(".", lastStart);
4314
- const suffixStart = suffix === -1 ? path.length : suffix;
4315
- return path.slice(0, suffixStart);
4316
- }
4317
-
4318
- //#endregion
4319
- //#region ../wesl/src/ModuleResolver.ts
4320
- const libRegex = /^lib\.w[eg]sl$/i;
4321
- /** Module resolver for in-memory source records. Lazy by default. */
4322
- var RecordResolver = class {
4323
- astCache = /* @__PURE__ */ new Map();
4324
- sources;
4325
- packageName;
4326
- debugWeslRoot;
4327
- constructor(sources, options = {}) {
4328
- const { packageName = "package", debugWeslRoot } = options;
4329
- this.sources = sources;
4330
- this.packageName = packageName;
4331
- this.debugWeslRoot = normalizeDebugRoot(debugWeslRoot);
4332
- }
4333
- resolveModule(modulePath) {
4334
- const cached = this.astCache.get(modulePath);
4335
- if (cached) return cached;
4336
- const source = this.findSource(modulePath);
4337
- if (source === void 0) return void 0;
4338
- const ast = parseSrcModule({
4339
- modulePath,
4340
- debugFilePath: this.modulePathToDebugPath(modulePath),
4341
- src: source
4342
- });
4343
- this.astCache.set(modulePath, ast);
4344
- return ast;
4345
- }
4346
- findSource(modulePath) {
4347
- if (this.sources[modulePath] !== void 0) return this.sources[modulePath];
4348
- const filePath = this.moduleToFilePath(modulePath);
4349
- if (filePath === void 0) return void 0;
4350
- return findInVariants(this.sources, filePath);
4351
- }
4352
- /** Convert module path to file path, or undefined if not local. */
4353
- moduleToFilePath(modulePath) {
4354
- return moduleToRelativePath(modulePath, this.packageName);
4355
- }
4356
- modulePathToDebugPath(modulePath) {
4357
- const filePath = this.moduleToFilePath(modulePath) ?? modulePath;
4358
- return this.debugWeslRoot + filePath + ".wesl";
4359
- }
4360
- /** Parse all modules and return entries. */
4361
- allModules() {
4362
- for (const filePath of Object.keys(this.sources)) {
4363
- const treatLibAsRoot = this.packageName !== "package";
4364
- const modulePath = fileToModulePath(filePath, this.packageName, treatLibAsRoot);
4365
- this.resolveModule(modulePath);
4366
- }
4367
- return this.astCache.entries();
4368
- }
4369
- };
4370
- /** Composite resolver that tries each resolver in order until one succeeds. */
4371
- var CompositeResolver = class {
4372
- resolvers;
4373
- constructor(resolvers) {
4374
- this.resolvers = resolvers;
4375
- }
4376
- resolveModule(modulePath) {
4377
- for (const resolver of this.resolvers) {
4378
- const ast = resolver.resolveModule(modulePath);
4379
- if (ast) return ast;
4380
- }
4381
- }
4382
- };
4383
- /** Lazy resolver for WeslBundle library modules. */
4384
- var BundleResolver = class {
4385
- astCache = /* @__PURE__ */ new Map();
4386
- sources;
4387
- packageName;
4388
- debugWeslRoot;
4389
- constructor(bundle, debugWeslRoot) {
4390
- this.sources = bundle.modules;
4391
- this.packageName = bundle.name;
4392
- this.debugWeslRoot = normalizeDebugRoot(debugWeslRoot);
4393
- }
4394
- resolveModule(modulePath) {
4395
- const pkgPrefix = this.packageName + "::";
4396
- if (modulePath !== this.packageName && !modulePath.startsWith(pkgPrefix)) return;
4397
- const cached = this.astCache.get(modulePath);
4398
- if (cached) return cached;
4399
- const source = this.findSource(modulePath);
4400
- if (!source) return void 0;
4401
- const ast = parseSrcModule({
4402
- modulePath,
4403
- debugFilePath: this.modulePathToDebugPath(modulePath),
4404
- src: source
4405
- });
4406
- this.astCache.set(modulePath, ast);
4407
- return ast;
4408
- }
4409
- findSource(modulePath) {
4410
- const filePath = this.moduleToFilePath(modulePath);
4411
- if (modulePath === this.packageName) {
4412
- const libSrc = findInVariants(this.sources, "lib", ["wesl", "wgsl"]);
4413
- if (libSrc) return libSrc;
4414
- }
4415
- return findInVariants(this.sources, filePath);
4416
- }
4417
- moduleToFilePath(modulePath) {
4418
- return moduleToRelativePath(modulePath, this.packageName) ?? modulePath;
4419
- }
4420
- modulePathToDebugPath(modulePath) {
4421
- const filePath = this.moduleToFilePath(modulePath);
4422
- return this.debugWeslRoot + this.packageName + "/" + filePath + ".wesl";
4423
- }
4424
- };
4425
- /** Convert file path to module path (e.g., "foo/bar.wesl" to "package::foo::bar"). */
4426
- function fileToModulePath(filePath, packageName, treatLibAsRoot) {
4427
- if (filePath.includes("::")) return filePath;
4428
- if (treatLibAsRoot && libRegex.test(filePath)) return packageName;
4429
- const moduleSuffix = noSuffix(normalize(filePath)).replaceAll("/", "::");
4430
- return packageName + "::" + moduleSuffix;
4431
- }
4432
- /** Try path variants with/without ./ prefix and extension suffixes. */
4433
- function findInVariants(sources, basePath, extensions = ["wesl", "wgsl"]) {
4434
- for (const prefix of ["", "./"]) {
4435
- const path = prefix + basePath;
4436
- if (sources[path] !== void 0) return sources[path];
4437
- for (const ext of extensions) {
4438
- const withExt = `${path}.${ext}`;
4439
- if (sources[withExt] !== void 0) return sources[withExt];
4440
- }
4441
- }
4442
- }
4443
-
4444
4427
  //#endregion
4445
4428
  //#region ../wesl/src/SrcMap.ts
4446
4429
  /** map text ranges in multiple src texts to a single dest text */
@@ -5116,7 +5099,7 @@ function normalizeUrl(url) {
5116
5099
 
5117
5100
  //#endregion
5118
5101
  //#region ../wesl-fetch/src/PackageLoader.ts
5119
- const virtualModules = ["constants", "test"];
5102
+ const virtualModules = ["constants", "env"];
5120
5103
  /** Resolve dependencies: internal modules via HTTP, external packages from npm. */
5121
5104
  async function fetchDependencies(rootModuleSource, options) {
5122
5105
  const shaderRoot = options?.shaderRoot ?? "/shaders";
@@ -5400,18 +5383,16 @@ function updateRenderUniforms(buffer, device, resolution, time, mouse = [0, 0])
5400
5383
  ]);
5401
5384
  device.queue.writeBuffer(buffer, 0, data);
5402
5385
  }
5403
- /**
5404
- * return the WGSL struct for use in shaders as test::Uniforms.
5405
- *
5406
- * @returns virtual library object for passing to compileShader()
5407
- */
5386
+ /** @returns virtual library providing env::u and env::Uniforms for shaders */
5408
5387
  function createUniformsVirtualLib() {
5409
- return { test: () => `
5388
+ return { env: () => `
5410
5389
  struct Uniforms {
5411
5390
  resolution: vec2f, // Output viewport dimensions
5412
5391
  time: f32, // Elapsed time in seconds
5413
5392
  mouse: vec2f, // Mouse position [0,1] normalized coords
5414
5393
  }
5394
+
5395
+ @group(0) @binding(0) var<uniform> u: Uniforms;
5415
5396
  ` };
5416
5397
  }
5417
5398
 
@@ -5659,26 +5640,13 @@ async function initWebGPU(canvas, alphaMode = "opaque") {
5659
5640
  }
5660
5641
  /** Compile WESL fragment shader and create render pipeline. */
5661
5642
  async function createPipeline(state, fragmentSource, options) {
5662
- const { weslSrc, libs = [], conditions, constants } = options ?? {};
5663
- const { packageName, rootModuleName } = options ?? {};
5664
- let resolver;
5665
- if (weslSrc || libs.length > 0) {
5666
- const resolvers = [];
5667
- if (weslSrc) resolvers.push(new RecordResolver(weslSrc, { packageName }));
5668
- for (const bundle of libs) resolvers.push(new BundleResolver(bundle));
5669
- resolver = resolvers.length === 1 ? resolvers[0] : new CompositeResolver(resolvers);
5670
- }
5671
5643
  state.device.pushErrorScope("validation");
5672
5644
  const pipeline = await linkAndCreatePipeline({
5673
5645
  device: state.device,
5674
5646
  fragmentSource,
5675
- resolver,
5676
5647
  format: state.presentationFormat,
5677
5648
  layout: state.pipelineLayout,
5678
- conditions,
5679
- constants,
5680
- packageName,
5681
- rootModuleName
5649
+ ...options
5682
5650
  });
5683
5651
  const gpuError = await state.device.popErrorScope();
5684
5652
  if (gpuError) {
@@ -5872,7 +5840,7 @@ var WgslPlay = class extends HTMLElement {
5872
5840
  this._weslSrc = Object.fromEntries(entries);
5873
5841
  this._rootModuleName = rootModuleName ? toModulePath(rootModuleName) : "package::main";
5874
5842
  this._fromFullProject = true;
5875
- this.discoverAndRebuild();
5843
+ this.rebuildPipeline();
5876
5844
  }
5877
5845
  /** Whether to auto-fetch missing library packages from npm (default: true). */
5878
5846
  get fetchLibs() {
@@ -6003,15 +5971,26 @@ var WgslPlay = class extends HTMLElement {
6003
5971
  return { [this._rootModuleName]: source };
6004
5972
  };
6005
5973
  const conditions = el.conditions;
6006
- this.project = {
6007
- weslSrc: getSources(),
5974
+ const rootModuleName = el.rootModuleName;
5975
+ const libs = el.libs;
5976
+ const weslSrc = getSources();
5977
+ const entries = Object.entries(weslSrc).map(([k, v]) => [toModulePath(k), v]);
5978
+ this._weslSrc = Object.fromEntries(entries);
5979
+ this._rootModuleName = rootModuleName ? toModulePath(rootModuleName) : "package::main";
5980
+ if (conditions) this._linkOptions = {
5981
+ ...this._linkOptions,
6008
5982
  conditions
6009
5983
  };
5984
+ if (libs) this._libs = libs;
5985
+ this._fromFullProject = false;
5986
+ await this.discoverAndRebuild();
5987
+ this._fromFullProject = true;
6010
5988
  this._sourceListener = (e) => {
6011
5989
  const detail = e.detail;
6012
5990
  const fallback = { [this._rootModuleName]: detail?.source ?? "" };
6013
5991
  this.project = {
6014
5992
  weslSrc: detail?.sources ?? fallback,
5993
+ rootModuleName: detail?.rootModuleName,
6015
5994
  conditions: detail?.conditions
6016
5995
  };
6017
5996
  };
@@ -6028,7 +6007,10 @@ var WgslPlay = class extends HTMLElement {
6028
6007
  this._fromFullProject = false;
6029
6008
  if (rootModuleName) this._rootModuleName = rootModuleName;
6030
6009
  const mainSource = weslSrc[this._rootModuleName];
6031
- if (!mainSource) return;
6010
+ if (!mainSource) {
6011
+ console.warn(`wgsl-play: root module "${this._rootModuleName}" not found in sources:`, Object.keys(weslSrc));
6012
+ return;
6013
+ }
6032
6014
  await createPipeline(this.renderState, mainSource, {
6033
6015
  ...this._linkOptions,
6034
6016
  weslSrc,
@@ -6044,7 +6026,10 @@ var WgslPlay = class extends HTMLElement {
6044
6026
  async rebuildPipeline() {
6045
6027
  if (!await this.initialize()) return;
6046
6028
  const mainSource = this._weslSrc[this._rootModuleName];
6047
- if (!mainSource) return;
6029
+ if (!mainSource) {
6030
+ console.warn(`wgsl-play: root module "${this._rootModuleName}" not found in sources:`, Object.keys(this._weslSrc));
6031
+ return;
6032
+ }
6048
6033
  try {
6049
6034
  this.errorOverlay.hide();
6050
6035
  await createPipeline(this.renderState, mainSource, {
@@ -6062,7 +6047,10 @@ var WgslPlay = class extends HTMLElement {
6062
6047
  async discoverAndRebuild() {
6063
6048
  if (!await this.initialize()) return;
6064
6049
  const mainSource = this._weslSrc[this._rootModuleName];
6065
- if (!mainSource) return;
6050
+ if (!mainSource) {
6051
+ console.warn(`wgsl-play: root module "${this._rootModuleName}" not found in sources:`, Object.keys(this._weslSrc));
6052
+ return;
6053
+ }
6066
6054
  try {
6067
6055
  this.errorOverlay.hide();
6068
6056
  const { weslSrc, libs } = await fetchDependencies(mainSource, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wgsl-play",
3
- "version": "0.0.32",
3
+ "version": "0.0.33",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -22,9 +22,9 @@
22
22
  }
23
23
  },
24
24
  "dependencies": {
25
- "wesl": "0.7.22",
26
- "wesl-fetch": "0.0.10",
27
- "wesl-gpu": "0.1.24"
25
+ "wesl": "0.7.23",
26
+ "wesl-fetch": "0.0.11",
27
+ "wesl-gpu": "0.1.25"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@playwright/test": "^1.53.2",
package/src/Renderer.ts CHANGED
@@ -1,10 +1,4 @@
1
- import type { ModuleResolver } from "wesl";
2
- import {
3
- BundleResolver,
4
- CompositeResolver,
5
- RecordResolver,
6
- requestWeslDevice,
7
- } from "wesl";
1
+ import { requestWeslDevice } from "wesl";
8
2
  import {
9
3
  linkAndCreatePipeline,
10
4
  renderFrame,
@@ -89,30 +83,13 @@ export async function createPipeline(
89
83
  fragmentSource: string,
90
84
  options?: LinkOptions,
91
85
  ): Promise<void> {
92
- const { weslSrc, libs = [], conditions, constants } = options ?? {};
93
- const { packageName, rootModuleName } = options ?? {};
94
-
95
- // Build resolver from weslSrc/libs if provided
96
- let resolver: ModuleResolver | undefined;
97
- if (weslSrc || libs.length > 0) {
98
- const resolvers: ModuleResolver[] = [];
99
- if (weslSrc) resolvers.push(new RecordResolver(weslSrc, { packageName }));
100
- for (const bundle of libs) resolvers.push(new BundleResolver(bundle));
101
- resolver =
102
- resolvers.length === 1 ? resolvers[0] : new CompositeResolver(resolvers);
103
- }
104
-
105
86
  state.device.pushErrorScope("validation");
106
87
  const pipeline = await linkAndCreatePipeline({
107
88
  device: state.device,
108
89
  fragmentSource,
109
- resolver,
110
90
  format: state.presentationFormat,
111
91
  layout: state.pipelineLayout,
112
- conditions,
113
- constants,
114
- packageName,
115
- rootModuleName,
92
+ ...options,
116
93
  });
117
94
  const gpuError = await state.device.popErrorScope();
118
95
  if (gpuError) {
package/src/WgslPlay.ts CHANGED
@@ -251,7 +251,7 @@ export class WgslPlay extends HTMLElement {
251
251
  ? toModulePath(rootModuleName)
252
252
  : "package::main";
253
253
  this._fromFullProject = true;
254
- this.discoverAndRebuild();
254
+ this.rebuildPipeline();
255
255
  }
256
256
 
257
257
  /** Whether to auto-fetch missing library packages from npm (default: true). */
@@ -425,16 +425,33 @@ export class WgslPlay extends HTMLElement {
425
425
  return { [this._rootModuleName]: source };
426
426
  };
427
427
 
428
- // Load initial sources and conditions
428
+ // Load initial sources, conditions, and libs from source element.
429
+ // Use discoverAndRebuild (not project setter) so external deps are fetched.
429
430
  const conditions = (el as any).conditions;
430
- this.project = { weslSrc: getSources(), conditions };
431
+ const rootModuleName = (el as any).rootModuleName;
432
+ const libs = (el as any).libs;
433
+ const weslSrc = getSources();
434
+ const entries = Object.entries(weslSrc).map(([k, v]) => [
435
+ toModulePath(k),
436
+ v,
437
+ ]);
438
+ this._weslSrc = Object.fromEntries(entries);
439
+ this._rootModuleName = rootModuleName
440
+ ? toModulePath(rootModuleName)
441
+ : "package::main";
442
+ if (conditions) this._linkOptions = { ...this._linkOptions, conditions };
443
+ if (libs) this._libs = libs;
444
+ this._fromFullProject = false;
445
+ await this.discoverAndRebuild();
446
+ this._fromFullProject = true; // fast rebuilds on subsequent edits
431
447
 
432
- // Listen for changes - handle both single and multi-file, plus conditions
448
+ // Listen for changes - rebuild with cached libs
433
449
  this._sourceListener = (e: Event) => {
434
450
  const detail = (e as CustomEvent).detail;
435
451
  const fallback = { [this._rootModuleName]: detail?.source ?? "" };
436
452
  this.project = {
437
453
  weslSrc: detail?.sources ?? fallback,
454
+ rootModuleName: detail?.rootModuleName,
438
455
  conditions: detail?.conditions,
439
456
  };
440
457
  };
@@ -457,7 +474,13 @@ export class WgslPlay extends HTMLElement {
457
474
  if (rootModuleName) this._rootModuleName = rootModuleName;
458
475
 
459
476
  const mainSource = weslSrc[this._rootModuleName];
460
- if (!mainSource) return;
477
+ if (!mainSource) {
478
+ console.warn(
479
+ `wgsl-play: root module "${this._rootModuleName}" not found in sources:`,
480
+ Object.keys(weslSrc),
481
+ );
482
+ return;
483
+ }
461
484
 
462
485
  await createPipeline(this.renderState, mainSource, {
463
486
  ...this._linkOptions,
@@ -476,7 +499,13 @@ export class WgslPlay extends HTMLElement {
476
499
  if (!(await this.initialize())) return;
477
500
 
478
501
  const mainSource = this._weslSrc[this._rootModuleName];
479
- if (!mainSource) return;
502
+ if (!mainSource) {
503
+ console.warn(
504
+ `wgsl-play: root module "${this._rootModuleName}" not found in sources:`,
505
+ Object.keys(this._weslSrc),
506
+ );
507
+ return;
508
+ }
480
509
 
481
510
  try {
482
511
  this.errorOverlay.hide();
@@ -497,7 +526,13 @@ export class WgslPlay extends HTMLElement {
497
526
  if (!(await this.initialize())) return;
498
527
 
499
528
  const mainSource = this._weslSrc[this._rootModuleName];
500
- if (!mainSource) return;
529
+ if (!mainSource) {
530
+ console.warn(
531
+ `wgsl-play: root module "${this._rootModuleName}" not found in sources:`,
532
+ Object.keys(this._weslSrc),
533
+ );
534
+ return;
535
+ }
501
536
 
502
537
  try {
503
538
  this.errorOverlay.hide();
@@ -185,6 +185,61 @@ test("project.conditions - shows green initially, red after setting RED", async
185
185
  await expectCanvasSnapshot(page, "#player7", "conditions-after-red.png");
186
186
  });
187
187
 
188
+ test("connectToSource - multi-file from wgsl-edit renders", async ({
189
+ page,
190
+ }) => {
191
+ await page.goto("/");
192
+ await waitForFrame(page, "#player9");
193
+ await expectCanvasSnapshot(page, "#player9", "connect-source-multifile.png");
194
+ });
195
+
196
+ test("connectToSource - condition toggle re-renders", async ({ page }) => {
197
+ await page.goto("/");
198
+ await waitForFrame(page, "#player10");
199
+
200
+ // Initial: green (@else branch)
201
+ await expectCanvasSnapshot(page, "#player10", "connect-conditions-green.png");
202
+
203
+ // Toggle RED condition via editor
204
+ await page.click("#toggle-condition10");
205
+ await waitForNewFrame(page, "#player10");
206
+
207
+ // After: red (@if branch)
208
+ await expectCanvasSnapshot(page, "#player10", "connect-conditions-red.png");
209
+ });
210
+
211
+ test("connectToSource - external deps are fetched", async ({ page }) => {
212
+ await page.goto("/");
213
+ await page.waitForLoadState("networkidle");
214
+ await waitForFrame(page, "#player11");
215
+
216
+ // If discoverAndRebuild wasn't called, this would show an error overlay
217
+ const hasError = await page.evaluate(
218
+ () => (document.querySelector("#player11") as any)?.hasError ?? true,
219
+ );
220
+ expect(hasError).toBe(false);
221
+ await expectCanvasSnapshot(page, "#player11", "connect-source-external.png");
222
+ });
223
+
224
+ test("editor.link() with virtualLibs resolves env:: module", async ({
225
+ page,
226
+ }) => {
227
+ await page.goto("/");
228
+ await waitForWgslPlay(page);
229
+
230
+ await page.click("#link-btn12");
231
+ await page.waitForFunction(
232
+ () =>
233
+ document.querySelector("#link-output12")?.textContent !==
234
+ "Not linked yet",
235
+ );
236
+
237
+ const output = await page.textContent("#link-output12");
238
+ expect(output).toContain("var<uniform>");
239
+ expect(output).toContain("fs_main");
240
+ expect(output).not.toContain("Error");
241
+ });
242
+
188
243
  test("no critical console errors on basic load", async ({ page }) => {
189
244
  const errors: string[] = [];
190
245
  page.on("console", msg => {