@motion-core/motion-gpu 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +35 -2
  2. package/dist/core/compute-bindgroup-cache.d.ts +13 -0
  3. package/dist/core/compute-bindgroup-cache.d.ts.map +1 -0
  4. package/dist/core/compute-bindgroup-cache.js +45 -0
  5. package/dist/core/compute-bindgroup-cache.js.map +1 -0
  6. package/dist/core/compute-shader.d.ts +48 -0
  7. package/dist/core/compute-shader.d.ts.map +1 -1
  8. package/dist/core/compute-shader.js +34 -1
  9. package/dist/core/compute-shader.js.map +1 -1
  10. package/dist/core/error-diagnostics.d.ts +8 -1
  11. package/dist/core/error-diagnostics.d.ts.map +1 -1
  12. package/dist/core/error-diagnostics.js +7 -3
  13. package/dist/core/error-diagnostics.js.map +1 -1
  14. package/dist/core/error-report.d.ts.map +1 -1
  15. package/dist/core/error-report.js +19 -1
  16. package/dist/core/error-report.js.map +1 -1
  17. package/dist/core/material.d.ts.map +1 -1
  18. package/dist/core/material.js +2 -1
  19. package/dist/core/material.js.map +1 -1
  20. package/dist/core/pointer.d.ts +96 -0
  21. package/dist/core/pointer.d.ts.map +1 -0
  22. package/dist/core/pointer.js +71 -0
  23. package/dist/core/pointer.js.map +1 -0
  24. package/dist/core/renderer.d.ts.map +1 -1
  25. package/dist/core/renderer.js +150 -85
  26. package/dist/core/renderer.js.map +1 -1
  27. package/dist/core/runtime-loop.d.ts.map +1 -1
  28. package/dist/core/runtime-loop.js +26 -14
  29. package/dist/core/runtime-loop.js.map +1 -1
  30. package/dist/core/shader.d.ts +7 -2
  31. package/dist/core/shader.d.ts.map +1 -1
  32. package/dist/core/shader.js +1 -0
  33. package/dist/core/shader.js.map +1 -1
  34. package/dist/core/textures.d.ts +4 -0
  35. package/dist/core/textures.d.ts.map +1 -1
  36. package/dist/core/textures.js +2 -1
  37. package/dist/core/textures.js.map +1 -1
  38. package/dist/core/types.d.ts +1 -1
  39. package/dist/core/types.d.ts.map +1 -1
  40. package/dist/react/advanced.js +2 -1
  41. package/dist/react/index.d.ts +2 -0
  42. package/dist/react/index.d.ts.map +1 -1
  43. package/dist/react/index.js +2 -1
  44. package/dist/react/use-pointer.d.ts +94 -0
  45. package/dist/react/use-pointer.d.ts.map +1 -0
  46. package/dist/react/use-pointer.js +285 -0
  47. package/dist/react/use-pointer.js.map +1 -0
  48. package/dist/svelte/advanced.js +2 -1
  49. package/dist/svelte/index.d.ts +2 -0
  50. package/dist/svelte/index.d.ts.map +1 -1
  51. package/dist/svelte/index.js +2 -1
  52. package/dist/svelte/use-pointer.d.ts +94 -0
  53. package/dist/svelte/use-pointer.d.ts.map +1 -0
  54. package/dist/svelte/use-pointer.js +292 -0
  55. package/dist/svelte/use-pointer.js.map +1 -0
  56. package/package.json +1 -1
  57. package/src/lib/core/compute-bindgroup-cache.ts +73 -0
  58. package/src/lib/core/compute-shader.ts +86 -0
  59. package/src/lib/core/error-diagnostics.ts +29 -4
  60. package/src/lib/core/error-report.ts +26 -1
  61. package/src/lib/core/material.ts +2 -1
  62. package/src/lib/core/pointer.ts +177 -0
  63. package/src/lib/core/renderer.ts +198 -92
  64. package/src/lib/core/runtime-loop.ts +37 -16
  65. package/src/lib/core/shader.ts +13 -2
  66. package/src/lib/core/textures.ts +6 -1
  67. package/src/lib/core/types.ts +1 -1
  68. package/src/lib/react/index.ts +10 -0
  69. package/src/lib/react/use-pointer.ts +515 -0
  70. package/src/lib/svelte/index.ts +10 -0
  71. package/src/lib/svelte/use-pointer.ts +507 -0
@@ -5,7 +5,8 @@ import { attachShaderCompilationDiagnostics } from "./error-diagnostics.js";
5
5
  import { buildShaderSourceWithMap, formatShaderSourceLocation } from "./shader.js";
6
6
  import { buildRenderTargetSignature, resolveRenderTargetDefinitions } from "./render-targets.js";
7
7
  import { planRenderGraph } from "./render-graph.js";
8
- import { buildComputeShaderSource, buildPingPongComputeShaderSource, extractWorkgroupSize, storageTextureSampleScalarType } from "./compute-shader.js";
8
+ import { buildComputeShaderSourceWithMap, buildPingPongComputeShaderSourceWithMap, extractWorkgroupSize, storageTextureSampleScalarType } from "./compute-shader.js";
9
+ import { createComputeStorageBindGroupCache } from "./compute-bindgroup-cache.js";
9
10
  //#region src/lib/core/renderer.ts
10
11
  /**
11
12
  * Binding index for frame uniforms (`time`, `delta`, `resolution`).
@@ -74,10 +75,13 @@ async function assertCompilation(module, options) {
74
75
  if (contextLabel.length === 0) return diagnostic.message;
75
76
  return `[${contextLabel.join(" | ")}] ${diagnostic.message}`;
76
77
  }).join("\n");
77
- throw attachShaderCompilationDiagnostics(/* @__PURE__ */ new Error(`WGSL compilation failed:\n${summary}`), {
78
+ const prefix = options?.errorPrefix ?? "WGSL compilation failed";
79
+ throw attachShaderCompilationDiagnostics(/* @__PURE__ */ new Error(`${prefix}:\n${summary}`), {
78
80
  kind: "shader-compilation",
81
+ ...options?.shaderStage !== void 0 ? { shaderStage: options.shaderStage } : {},
79
82
  diagnostics,
80
83
  fragmentSource: options?.fragmentSource ?? "",
84
+ ...options?.computeSource !== void 0 ? { computeSource: options.computeSource } : {},
81
85
  includeSources: options?.includeSources ?? {},
82
86
  ...options?.defineBlockSource !== void 0 ? { defineBlockSource: options.defineBlockSource } : {},
83
87
  materialSource: options?.materialSource ?? null,
@@ -87,6 +91,41 @@ async function assertCompilation(module, options) {
87
91
  function toSortedUniqueStrings(values) {
88
92
  return Array.from(new Set(values)).sort((a, b) => a.localeCompare(b));
89
93
  }
94
+ function extractGeneratedLineFromComputeError(message) {
95
+ const lineMatch = message.match(/\bline\s+(\d+)\b/i);
96
+ if (lineMatch) {
97
+ const parsed = Number.parseInt(lineMatch[1] ?? "", 10);
98
+ if (Number.isFinite(parsed) && parsed > 0) return parsed;
99
+ }
100
+ const colonMatch = message.match(/:(\d+):\d+/);
101
+ if (colonMatch) {
102
+ const parsed = Number.parseInt(colonMatch[1] ?? "", 10);
103
+ if (Number.isFinite(parsed) && parsed > 0) return parsed;
104
+ }
105
+ return null;
106
+ }
107
+ function toComputeCompilationError(input) {
108
+ const baseError = input.error instanceof Error ? input.error : new Error(String(input.error ?? "Unknown error"));
109
+ const generatedLine = extractGeneratedLineFromComputeError(baseError.message) ?? 0;
110
+ const sourceLocation = generatedLine > 0 ? input.lineMap[generatedLine] ?? null : null;
111
+ const diagnostics = [{
112
+ generatedLine,
113
+ message: baseError.message,
114
+ sourceLocation
115
+ }];
116
+ const contextLabel = [formatShaderSourceLocation(sourceLocation), generatedLine > 0 ? `generated WGSL line ${generatedLine}` : null].filter((value) => Boolean(value));
117
+ const summary = contextLabel.length > 0 ? `[${contextLabel.join(" | ")}] ${baseError.message}` : baseError.message;
118
+ return attachShaderCompilationDiagnostics(/* @__PURE__ */ new Error(`Compute shader compilation failed:\n${summary}`), {
119
+ kind: "shader-compilation",
120
+ shaderStage: "compute",
121
+ diagnostics,
122
+ fragmentSource: "",
123
+ computeSource: input.computeSource,
124
+ includeSources: {},
125
+ materialSource: null,
126
+ runtimeContext: input.runtimeContext
127
+ });
128
+ }
90
129
  function buildPassGraphSnapshot(passes) {
91
130
  const declaredPasses = passes ?? [];
92
131
  let enabledPassCount = 0;
@@ -397,7 +436,8 @@ async function createRenderer(options) {
397
436
  try {
398
437
  const runtimeContext = buildShaderCompilationRuntimeContext(options);
399
438
  const convertLinearToSrgb = shouldConvertLinearToSrgb(options.outputColorSpace, format);
400
- const builtShader = buildShaderSourceWithMap(options.fragmentWgsl, options.uniformLayout, options.textureKeys, {
439
+ const fragmentTextureKeys = options.textureKeys.filter((key) => options.textureDefinitions[key]?.fragmentVisible !== false);
440
+ const builtShader = buildShaderSourceWithMap(options.fragmentWgsl, options.uniformLayout, fragmentTextureKeys, {
401
441
  convertLinearToSrgb,
402
442
  fragmentLineMap: options.fragmentLineMap,
403
443
  ...options.storageBufferKeys !== void 0 ? { storageBufferKeys: options.storageBufferKeys } : {},
@@ -417,10 +457,13 @@ async function createRenderer(options) {
417
457
  const storageBufferDefinitions = options.storageBufferDefinitions ?? {};
418
458
  const storageTextureKeys = options.storageTextureKeys ?? [];
419
459
  const storageTextureKeySet = new Set(storageTextureKeys);
420
- const textureBindings = options.textureKeys.map((key, index) => {
460
+ const fragmentTextureIndexByKey = new Map(fragmentTextureKeys.map((key, index) => [key, index]));
461
+ const textureBindings = options.textureKeys.map((key) => {
421
462
  const config = normalizedTextureDefinitions[key];
422
463
  if (!config) throw new Error(`Missing texture definition for "${key}"`);
423
- const { samplerBinding, textureBinding } = getTextureBindings(index);
464
+ const fragmentTextureIndex = fragmentTextureIndexByKey.get(key);
465
+ const fragmentVisible = fragmentTextureIndex !== void 0;
466
+ const { samplerBinding, textureBinding } = getTextureBindings(fragmentTextureIndex ?? 0);
424
467
  const sampler = device.createSampler({
425
468
  magFilter: config.filter,
426
469
  minFilter: config.filter,
@@ -438,6 +481,7 @@ async function createRenderer(options) {
438
481
  key,
439
482
  samplerBinding,
440
483
  textureBinding,
484
+ fragmentVisible,
441
485
  sampler,
442
486
  fallbackTexture,
443
487
  fallbackView,
@@ -481,7 +525,33 @@ async function createRenderer(options) {
481
525
  }
482
526
  return runtimeBinding;
483
527
  });
484
- const bindGroupLayout = device.createBindGroupLayout({ entries: createBindGroupLayoutEntries(textureBindings) });
528
+ const textureBindingByKey = new Map(textureBindings.map((binding) => [binding.key, binding]));
529
+ const fragmentTextureBindings = textureBindings.filter((binding) => binding.fragmentVisible);
530
+ const computeStorageBufferLayoutEntries = storageBufferKeys.map((key, index) => {
531
+ const bufferType = (storageBufferDefinitions[key]?.access ?? "read-write") === "read" ? "read-only-storage" : "storage";
532
+ return {
533
+ binding: index,
534
+ visibility: GPUShaderStage.COMPUTE,
535
+ buffer: { type: bufferType }
536
+ };
537
+ });
538
+ const computeStorageBufferTopologyKey = storageBufferKeys.map((key) => `${key}:${storageBufferDefinitions[key]?.access ?? "read-write"}`).join("|");
539
+ const computeStorageTextureLayoutEntries = storageTextureKeys.map((key, index) => {
540
+ const config = normalizedTextureDefinitions[key];
541
+ return {
542
+ binding: index,
543
+ visibility: GPUShaderStage.COMPUTE,
544
+ storageTexture: {
545
+ access: "write-only",
546
+ format: config?.format ?? "rgba8unorm",
547
+ viewDimension: "2d"
548
+ }
549
+ };
550
+ });
551
+ const computeStorageTextureTopologyKey = storageTextureKeys.map((key) => `${key}:${normalizedTextureDefinitions[key]?.format ?? "rgba8unorm"}`).join("|");
552
+ const computeStorageBufferBindGroupCache = createComputeStorageBindGroupCache(device);
553
+ const computeStorageTextureBindGroupCache = createComputeStorageBindGroupCache(device);
554
+ const bindGroupLayout = device.createBindGroupLayout({ entries: createBindGroupLayoutEntries(fragmentTextureBindings) });
485
555
  const fragmentStorageBindGroupLayout = storageBufferKeys.length > 0 ? device.createBindGroupLayout({ entries: storageBufferKeys.map((_, index) => ({
486
556
  binding: index,
487
557
  visibility: GPUShaderStage.FRAGMENT,
@@ -625,7 +695,9 @@ async function createRenderer(options) {
625
695
  viewA: textureA.createView(),
626
696
  textureB,
627
697
  viewB: textureB.createView(),
628
- bindGroupLayout
698
+ bindGroupLayout,
699
+ readAWriteBBindGroup: null,
700
+ readBWriteABindGroup: null
629
701
  };
630
702
  pingPongTexturePairs.set(target, pair);
631
703
  return pair;
@@ -652,14 +724,14 @@ async function createRenderer(options) {
652
724
  if (texDef?.format) storageTextureDefs[key] = { format: texDef.format };
653
725
  }
654
726
  const isPingPongPipeline = Boolean(buildOptions.pingPongTarget && buildOptions.pingPongFormat);
655
- const shaderCode = isPingPongPipeline ? buildPingPongComputeShaderSource({
727
+ const builtComputeShader = isPingPongPipeline ? buildPingPongComputeShaderSourceWithMap({
656
728
  compute: buildOptions.computeSource,
657
729
  uniformLayout: options.uniformLayout,
658
730
  storageBufferKeys,
659
731
  storageBufferDefinitions: storageBufferDefs,
660
732
  target: buildOptions.pingPongTarget,
661
733
  targetFormat: buildOptions.pingPongFormat
662
- }) : buildComputeShaderSource({
734
+ }) : buildComputeShaderSourceWithMap({
663
735
  compute: buildOptions.computeSource,
664
736
  uniformLayout: options.uniformLayout,
665
737
  storageBufferKeys,
@@ -667,7 +739,7 @@ async function createRenderer(options) {
667
739
  storageTextureKeys,
668
740
  storageTextureDefinitions: storageTextureDefs
669
741
  });
670
- const computeShaderModule = device.createShaderModule({ code: shaderCode });
742
+ const computeShaderModule = device.createShaderModule({ code: builtComputeShader.code });
671
743
  const workgroupSize = extractWorkgroupSize(buildOptions.computeSource);
672
744
  const computeUniformBGL = device.createBindGroupLayout({ entries: [{
673
745
  binding: FRAME_BINDING,
@@ -681,15 +753,7 @@ async function createRenderer(options) {
681
753
  visibility: GPUShaderStage.COMPUTE,
682
754
  buffer: { type: "uniform" }
683
755
  }] });
684
- const storageBGLEntries = storageBufferKeys.map((key, index) => {
685
- const bufferType = (storageBufferDefinitions[key]?.access ?? "read-write") === "read" ? "read-only-storage" : "storage";
686
- return {
687
- binding: index,
688
- visibility: GPUShaderStage.COMPUTE,
689
- buffer: { type: bufferType }
690
- };
691
- });
692
- const storageBGL = storageBGLEntries.length > 0 ? device.createBindGroupLayout({ entries: storageBGLEntries }) : null;
756
+ const storageBGL = computeStorageBufferLayoutEntries.length > 0 ? device.createBindGroupLayout({ entries: computeStorageBufferLayoutEntries }) : null;
693
757
  const storageTextureBGLEntries = isPingPongPipeline ? [{
694
758
  binding: 0,
695
759
  visibility: GPUShaderStage.COMPUTE,
@@ -706,41 +770,42 @@ async function createRenderer(options) {
706
770
  format: buildOptions.pingPongFormat,
707
771
  viewDimension: "2d"
708
772
  }
709
- }] : storageTextureKeys.map((key, index) => {
710
- const texDef = options.textureDefinitions[key];
711
- return {
712
- binding: index,
713
- visibility: GPUShaderStage.COMPUTE,
714
- storageTexture: {
715
- access: "write-only",
716
- format: texDef?.format ?? "rgba8unorm",
717
- viewDimension: "2d"
718
- }
719
- };
720
- });
773
+ }] : computeStorageTextureLayoutEntries;
721
774
  const storageTextureBGL = storageTextureBGLEntries.length > 0 ? device.createBindGroupLayout({ entries: storageTextureBGLEntries }) : null;
722
775
  const bindGroupLayouts = [computeUniformBGL];
723
776
  if (storageBGL || storageTextureBGL) bindGroupLayouts.push(storageBGL ?? device.createBindGroupLayout({ entries: [] }));
724
777
  if (storageTextureBGL) bindGroupLayouts.push(storageTextureBGL);
725
778
  const computePipelineLayout = device.createPipelineLayout({ bindGroupLayouts });
726
- const entry = {
727
- pipeline: device.createComputePipeline({
779
+ let pipeline;
780
+ try {
781
+ pipeline = device.createComputePipeline({
728
782
  layout: computePipelineLayout,
729
783
  compute: {
730
784
  module: computeShaderModule,
731
785
  entryPoint: "compute"
732
786
  }
733
- }),
734
- bindGroup: device.createBindGroup({
735
- layout: computeUniformBGL,
736
- entries: [{
737
- binding: FRAME_BINDING,
738
- resource: { buffer: frameBuffer }
739
- }, {
740
- binding: UNIFORM_BINDING,
741
- resource: { buffer: uniformBuffer }
742
- }]
743
- }),
787
+ });
788
+ } catch (error) {
789
+ throw toComputeCompilationError({
790
+ error,
791
+ lineMap: builtComputeShader.lineMap,
792
+ computeSource: buildOptions.computeSource,
793
+ runtimeContext
794
+ });
795
+ }
796
+ const computeUniformBindGroup = device.createBindGroup({
797
+ layout: computeUniformBGL,
798
+ entries: [{
799
+ binding: FRAME_BINDING,
800
+ resource: { buffer: frameBuffer }
801
+ }, {
802
+ binding: UNIFORM_BINDING,
803
+ resource: { buffer: uniformBuffer }
804
+ }]
805
+ });
806
+ const entry = {
807
+ pipeline,
808
+ bindGroup: computeUniformBindGroup,
744
809
  workgroupSize,
745
810
  computeSource: buildOptions.computeSource
746
811
  };
@@ -748,71 +813,71 @@ async function createRenderer(options) {
748
813
  return entry;
749
814
  };
750
815
  const getComputeStorageBindGroup = () => {
751
- if (storageBufferKeys.length === 0) return null;
752
- const storageBGLEntries = storageBufferKeys.map((key, index) => {
753
- const bufferType = (storageBufferDefinitions[key]?.access ?? "read-write") === "read" ? "read-only-storage" : "storage";
754
- return {
755
- binding: index,
756
- visibility: GPUShaderStage.COMPUTE,
757
- buffer: { type: bufferType }
758
- };
759
- });
760
- const storageBGL = device.createBindGroupLayout({ entries: storageBGLEntries });
761
- const storageEntries = storageBufferKeys.map((key, index) => {
816
+ if (computeStorageBufferLayoutEntries.length === 0) return null;
817
+ const resources = storageBufferKeys.map((key) => {
762
818
  const buffer = storageBufferMap.get(key);
763
819
  if (!buffer) throw new Error(`Storage buffer "${key}" not allocated.`);
820
+ return buffer;
821
+ });
822
+ const storageEntries = resources.map((buffer, index) => {
764
823
  return {
765
824
  binding: index,
766
825
  resource: { buffer }
767
826
  };
768
827
  });
769
- return device.createBindGroup({
770
- layout: storageBGL,
771
- entries: storageEntries
828
+ return computeStorageBufferBindGroupCache.getOrCreate({
829
+ topologyKey: computeStorageBufferTopologyKey,
830
+ layoutEntries: computeStorageBufferLayoutEntries,
831
+ bindGroupEntries: storageEntries,
832
+ resourceRefs: resources
772
833
  });
773
834
  };
774
835
  const getComputeStorageTextureBindGroup = () => {
775
- if (storageTextureKeys.length === 0) return null;
776
- const entries = storageTextureKeys.map((key, index) => {
777
- const texDef = options.textureDefinitions[key];
778
- return {
779
- binding: index,
780
- visibility: GPUShaderStage.COMPUTE,
781
- storageTexture: {
782
- access: "write-only",
783
- format: texDef?.format ?? "rgba8unorm",
784
- viewDimension: "2d"
785
- }
786
- };
787
- });
788
- const bgl = device.createBindGroupLayout({ entries });
789
- const bgEntries = storageTextureKeys.map((key, index) => {
790
- const binding = textureBindings.find((b) => b.key === key);
836
+ if (computeStorageTextureLayoutEntries.length === 0) return null;
837
+ const resources = storageTextureKeys.map((key) => {
838
+ const binding = textureBindingByKey.get(key);
791
839
  if (!binding || !binding.texture) throw new Error(`Storage texture "${key}" not allocated.`);
840
+ return binding.view;
841
+ });
842
+ const bgEntries = resources.map((view, index) => {
792
843
  return {
793
844
  binding: index,
794
- resource: binding.view
845
+ resource: view
795
846
  };
796
847
  });
797
- return device.createBindGroup({
798
- layout: bgl,
799
- entries: bgEntries
848
+ return computeStorageTextureBindGroupCache.getOrCreate({
849
+ topologyKey: computeStorageTextureTopologyKey,
850
+ layoutEntries: computeStorageTextureLayoutEntries,
851
+ bindGroupEntries: bgEntries,
852
+ resourceRefs: resources
800
853
  });
801
854
  };
802
855
  const getPingPongStorageTextureBindGroup = (target, readFromA) => {
803
856
  const pair = ensurePingPongTexturePair(target);
804
- const readView = readFromA ? pair.viewA : pair.viewB;
805
- const writeView = readFromA ? pair.viewB : pair.viewA;
806
- return device.createBindGroup({
857
+ if (readFromA) {
858
+ if (!pair.readAWriteBBindGroup) pair.readAWriteBBindGroup = device.createBindGroup({
859
+ layout: pair.bindGroupLayout,
860
+ entries: [{
861
+ binding: 0,
862
+ resource: pair.viewA
863
+ }, {
864
+ binding: 1,
865
+ resource: pair.viewB
866
+ }]
867
+ });
868
+ return pair.readAWriteBBindGroup;
869
+ }
870
+ if (!pair.readBWriteABindGroup) pair.readBWriteABindGroup = device.createBindGroup({
807
871
  layout: pair.bindGroupLayout,
808
872
  entries: [{
809
873
  binding: 0,
810
- resource: readView
874
+ resource: pair.viewB
811
875
  }, {
812
876
  binding: 1,
813
- resource: writeView
877
+ resource: pair.viewA
814
878
  }]
815
879
  });
880
+ return pair.readBWriteABindGroup;
816
881
  };
817
882
  const frameBuffer = device.createBuffer({
818
883
  size: 16,
@@ -843,7 +908,7 @@ async function createRenderer(options) {
843
908
  binding: UNIFORM_BINDING,
844
909
  resource: { buffer: uniformBuffer }
845
910
  }];
846
- for (const binding of textureBindings) {
911
+ for (const binding of fragmentTextureBindings) {
847
912
  entries.push({
848
913
  binding: binding.samplerBinding,
849
914
  resource: binding.sampler
@@ -1211,7 +1276,7 @@ async function createRenderer(options) {
1211
1276
  let bindGroupDirty = false;
1212
1277
  for (const binding of textureBindings) {
1213
1278
  if (storageTextureKeySet.has(binding.key)) continue;
1214
- if (updateTextureBinding(binding, textures[binding.key] ?? normalizedTextureDefinitions[binding.key]?.source ?? null, renderMode)) bindGroupDirty = true;
1279
+ if (updateTextureBinding(binding, textures[binding.key] ?? normalizedTextureDefinitions[binding.key]?.source ?? null, renderMode) && binding.fragmentVisible) bindGroupDirty = true;
1215
1280
  }
1216
1281
  if (bindGroupDirty) bindGroup = createBindGroup();
1217
1282
  if (pendingStorageWrites) for (const write of pendingStorageWrites) {