typegpu 0.12.6 → 0.12.8

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 (87) hide show
  1. package/common/writeSoA.js +16 -0
  2. package/core/buffer/buffer.js +47 -11
  3. package/core/commandEncoder/attachments.js +5 -2
  4. package/core/function/autoIO.js +5 -3
  5. package/core/function/comptime.js +7 -5
  6. package/core/function/createCallableSchema.js +4 -3
  7. package/core/function/dualImpl.js +20 -19
  8. package/core/function/ioSchema.d.ts +17 -2
  9. package/core/function/ioSchema.js +74 -27
  10. package/core/function/tgpuFn.d.ts +11 -1
  11. package/core/function/tgpuFn.js +9 -2
  12. package/core/function/tgpuFragmentFn.d.ts +1 -1
  13. package/core/function/tgpuFragmentFn.js +2 -1
  14. package/core/function/tgpuVertexFn.js +2 -1
  15. package/core/pipeline/connectAttachmentToShader.d.ts +2 -2
  16. package/core/pipeline/connectAttachmentToShader.js +16 -21
  17. package/core/pipeline/connectTargetsToShader.d.ts +5 -1
  18. package/core/pipeline/connectTargetsToShader.js +23 -18
  19. package/core/pipeline/renderPipeline.d.ts +9 -6
  20. package/core/pipeline/renderPipeline.js +22 -22
  21. package/core/pipeline/timeable.js +15 -2
  22. package/core/resolve/namespace.js +2 -2
  23. package/core/root/configurableImpl.js +1 -2
  24. package/core/root/rootTypes.d.ts +6 -2
  25. package/core/slot/accessor.d.ts +1 -1
  26. package/core/slot/accessor.js +72 -5
  27. package/core/texture/externalTexture.d.ts +1 -1
  28. package/core/texture/externalTexture.js +3 -1
  29. package/core/texture/texture.d.ts +1 -1
  30. package/core/texture/texture.js +12 -5
  31. package/core/unroll/tgpuUnroll.d.ts +4 -3
  32. package/core/unroll/tgpuUnroll.js +7 -1
  33. package/core/valueProxyUtils.d.ts +4 -2
  34. package/core/valueProxyUtils.js +4 -2
  35. package/data/autoStruct.d.ts +1 -1
  36. package/data/autoStruct.js +8 -4
  37. package/data/compiledIO.js +12 -9
  38. package/data/dataIO.d.ts +10 -6
  39. package/data/dataIO.js +19 -4
  40. package/data/dataTypes.d.ts +0 -5
  41. package/data/dataTypes.js +0 -6
  42. package/data/deepEqual.js +19 -9
  43. package/data/matrix.js +10 -3
  44. package/data/matrixColumnsAccess.d.ts +8 -0
  45. package/data/matrixColumnsAccess.js +15 -0
  46. package/data/offsetUtils.js +181 -102
  47. package/data/partialIO.d.ts +8 -1
  48. package/data/partialIO.js +36 -19
  49. package/data/ref.d.ts +12 -5
  50. package/data/ref.js +51 -24
  51. package/data/snippet.d.ts +13 -4
  52. package/data/snippet.js +17 -4
  53. package/data/struct.js +3 -0
  54. package/data/wgslTypes.d.ts +6 -3
  55. package/internal.d.ts +1 -0
  56. package/nameUtils.d.ts +7 -0
  57. package/nameUtils.js +60 -0
  58. package/package.json +1 -1
  59. package/resolutionCtx.js +1 -1
  60. package/shared/meta.js +1 -1
  61. package/shared/symbols.d.ts +5 -0
  62. package/shared/symbols.js +6 -1
  63. package/shared/tseynit.js +54 -50
  64. package/std/boolean.js +9 -10
  65. package/std/numeric.js +1 -4
  66. package/std/operators.js +9 -4
  67. package/std/packing.js +10 -10
  68. package/std/texture.d.ts +2 -2
  69. package/tgpuLogger.d.ts +1 -1
  70. package/tgpuLogger.js +0 -2
  71. package/tgsl/accessIndex.js +37 -38
  72. package/tgsl/accessProp.js +10 -7
  73. package/tgsl/conversion.js +3 -2
  74. package/tgsl/forOfUtils.js +8 -8
  75. package/tgsl/generationHelpers.d.ts +4 -3
  76. package/tgsl/generationHelpers.js +18 -5
  77. package/tgsl/infixDispatch.d.ts +4 -1
  78. package/tgsl/infixDispatch.js +13 -1
  79. package/tgsl/makeDereferenceable.d.ts +1 -1
  80. package/tgsl/makeDereferenceable.js +3 -1
  81. package/tgsl/shellless.js +5 -9
  82. package/tgsl/stringifySnippet.d.ts +9 -0
  83. package/tgsl/stringifySnippet.js +24 -0
  84. package/tgsl/wgslGenerator.d.ts +1 -0
  85. package/tgsl/wgslGenerator.js +180 -86
  86. package/types.d.ts +23 -2
  87. package/types.js +30 -5
@@ -1,29 +1,24 @@
1
- import { isBuiltin } from "../../data/attributes.js";
2
- import { isVoid, isWgslStruct } from "../../data/wgslTypes.js";
3
1
  function isColorAttachment(value) {
4
2
  return !!value?.view;
5
3
  }
6
- export function connectAttachmentToShader(fragmentOut, attachment) {
7
- if (isVoid(fragmentOut) || isBuiltin(fragmentOut)) {
4
+ export function connectAttachmentToShader(connectedTargets, attachment) {
5
+ if (!connectedTargets) {
8
6
  return [];
9
7
  }
10
- if (isWgslStruct(fragmentOut)) {
11
- const result = [];
12
- for (const key of Object.keys(fragmentOut.propTypes)) {
13
- const outputValue = fragmentOut.propTypes[key];
14
- if (isBuiltin(outputValue)) {
15
- continue;
16
- }
17
- const matching = attachment[key];
18
- if (!matching) {
19
- throw new Error(`A color attachment by the name of '${key}' was not provided to the shader.`);
8
+ return connectedTargets.map((target) => {
9
+ if (!target) {
10
+ return null;
11
+ }
12
+ if (target.key === undefined) {
13
+ if (!isColorAttachment(attachment)) {
14
+ throw new Error('Expected a single color attachment, not a record.');
20
15
  }
21
- result.push(matching);
16
+ return attachment;
22
17
  }
23
- return result;
24
- }
25
- if (!isColorAttachment(attachment)) {
26
- throw new Error('Expected a single color attachment, not a record.');
27
- }
28
- return [attachment];
18
+ const matching = attachment[target.key];
19
+ if (!matching) {
20
+ throw new Error(`A color attachment by the name of '${target.key}' was not provided to the shader.`);
21
+ }
22
+ return matching;
23
+ });
29
24
  }
@@ -1,3 +1,7 @@
1
1
  import { type BaseData } from '../../data/wgslTypes.ts';
2
2
  import type { AnyFragmentTargets } from './renderPipeline.ts';
3
- export declare function connectTargetsToShader(fragmentOut: BaseData, targets: AnyFragmentTargets): (GPUColorTargetState | null)[];
3
+ export interface ConnectedTarget {
4
+ key: string | undefined;
5
+ target: GPUColorTargetState;
6
+ }
7
+ export declare function connectTargetsToShader(fragmentOut: BaseData, targets: AnyFragmentTargets): (ConnectedTarget | null)[];
@@ -1,31 +1,36 @@
1
1
  import { isBuiltin } from "../../data/attributes.js";
2
2
  import { isVoid, isWgslStruct } from "../../data/wgslTypes.js";
3
+ import { invariant } from "../../errors.js";
4
+ import { getCustomLocation } from "../../data/dataTypes.js";
3
5
  export function connectTargetsToShader(fragmentOut, targets) {
4
- let presentationFormat;
5
6
  if (isVoid(fragmentOut) || isBuiltin(fragmentOut)) {
6
7
  return [];
7
8
  }
9
+ const result = [];
10
+ function putInResult(index, key, target) {
11
+ while (index > result.length) {
12
+ result.push(null);
13
+ }
14
+ result[index] = { key, target };
15
+ }
8
16
  if (isWgslStruct(fragmentOut)) {
9
- const result = [];
10
- for (const key of Object.keys(fragmentOut.propTypes)) {
11
- const outputValue = fragmentOut.propTypes[key];
12
- if (isBuiltin(outputValue)) {
13
- continue;
14
- }
15
- const matchingTarget = targets[key];
16
- result.push({
17
+ const varyings = Object.entries(fragmentOut.propTypes).filter(([, value]) => !isBuiltin(value));
18
+ for (const [key, outputValue] of varyings) {
19
+ const matchingTarget = targets?.[key];
20
+ const location = getCustomLocation(outputValue);
21
+ invariant(location !== undefined, `'withLocations' failed or was not called.`);
22
+ putInResult(location, key, {
17
23
  ...matchingTarget,
18
- format: matchingTarget?.format ??
19
- (presentationFormat ??= navigator.gpu.getPreferredCanvasFormat()),
24
+ format: matchingTarget?.format ?? navigator.gpu.getPreferredCanvasFormat(),
20
25
  });
21
26
  }
22
- return result;
23
27
  }
24
- const singleTarget = targets;
25
- return [
26
- {
28
+ else {
29
+ const singleTarget = targets;
30
+ putInResult(getCustomLocation(fragmentOut) ?? 0, undefined, {
27
31
  ...singleTarget,
28
- format: singleTarget?.format ?? (presentationFormat ??= navigator.gpu.getPreferredCanvasFormat()),
29
- },
30
- ];
32
+ format: singleTarget?.format ?? navigator.gpu.getPreferredCanvasFormat(),
33
+ });
34
+ }
35
+ return result;
31
36
  }
@@ -3,7 +3,7 @@ import type { IndexFlag, IndirectFlag, TgpuBuffer, VertexFlag } from '../buffer/
3
3
  import type { TgpuQuerySet } from '../querySet/querySet.ts';
4
4
  import { type Disarray, type UndecorateRecord } from '../../data/dataTypes.ts';
5
5
  import { type ResolvedSnippet } from '../../data/snippet.ts';
6
- import { type AnyVecInstance, type AnyWgslData, type BaseData, type U16, type U32, type v4f, Void, type WgslArray, type WgslStruct } from '../../data/wgslTypes.ts';
6
+ import { type AnyVecInstance, type AnyWgslData, type BaseData, type U16, type U32, type v4f, Void, type WgslArray } from '../../data/wgslTypes.ts';
7
7
  import type { TgpuNamable } from '../../shared/meta.ts';
8
8
  import type { InferInput } from '../../shared/repr.ts';
9
9
  import type { TgpuDeviceOwningSoul } from '../../shared/soul.ts';
@@ -18,6 +18,7 @@ import type { TgpuVertexFn } from '../function/tgpuVertexFn.ts';
18
18
  import type { ExperimentalTgpuRoot } from '../root/rootTypes.ts';
19
19
  import type { TgpuSlot } from '../slot/slotTypes.ts';
20
20
  import { type TgpuVertexLayout } from '../vertexLayout/vertexLayout.ts';
21
+ import { type ConnectedTarget } from './connectTargetsToShader.ts';
21
22
  import { type TgpuCommandEncoder } from '../commandEncoder/commandEncoder.ts';
22
23
  import type { ColorAttachment, DepthStencilAttachment } from '../commandEncoder/attachments.ts';
23
24
  import { type ImmediateSnapshot, type TgpuImmediateVar } from '../immediate/immediateVar.ts';
@@ -34,7 +35,7 @@ export interface RenderPipelineInternals {
34
35
  export interface TgpuRenderPipelineSoul extends TgpuDeviceOwningSoul<'render-pipeline', GPURenderPipeline> {
35
36
  usedBindGroupLayouts?: TgpuBindGroupLayout[] | undefined;
36
37
  usedVertexLayouts?: TgpuVertexLayout[] | undefined;
37
- fragmentOut?: BaseData | undefined;
38
+ connectedTargets?: (ConnectedTarget | null)[] | undefined;
38
39
  bindGroups?: [TgpuBindGroupLayout, TgpuBindGroup | GPUBindGroup][] | undefined;
39
40
  usedImmediate?: TgpuImmediateVar | undefined;
40
41
  immediates?: [TgpuImmediateVar, ImmediateSnapshot][] | undefined;
@@ -221,7 +222,7 @@ type Memo = {
221
222
  catchall: [number, TgpuBindGroup] | undefined;
222
223
  logResources: LogResources | undefined;
223
224
  usedVertexLayouts: TgpuVertexLayout[];
224
- fragmentOut: BaseData | undefined;
225
+ connectedTargets: (ConnectedTarget | null)[] | undefined;
225
226
  usedImmediate: TgpuImmediateVar | undefined;
226
227
  };
227
228
  declare class TgpuRenderPipelineImpl implements TgpuRenderPipeline {
@@ -280,13 +281,15 @@ declare class RenderPipelineCore implements SelfResolvable {
280
281
  resolveAndCreateShaderModule(): {
281
282
  resolutionResult: import("../../resolutionCtx.ts").ResolutionResult;
282
283
  descriptor: GPURenderPipelineDescriptor;
284
+ connectedTargets: (ConnectedTarget | null)[];
283
285
  connectedAttribs: import("../vertexLayout/connectAttributesToShader.ts").ConnectAttributesToShaderResult;
284
- fragmentOut: WgslStruct<import("../function/ioSchema.ts").WithLocations<Record<string, BaseData>>>;
285
286
  };
286
287
  }
287
288
  /**
288
- * Assumes vertexOut and fragmentIn are matching when it comes to the keys, that is fragmentIn's keyset is a subset of vertexOut's
289
- * Logs a warning, when they don't match in terms of custom locations
289
+ * Throws, when vertexOut and fragmentIn don't match in terms of custom locations.
290
+ *
291
+ * @privateRemarks
292
+ * Note that vertexOut is undefined in shellless functions.
290
293
  */
291
294
  export declare function matchUpVaryingLocations(vertexOut: TgpuVertexFn.Out | undefined, fragmentIn: TgpuFragmentFn.In | undefined, vertexFnName: string, fragmentFnName: string): Record<string, number>;
292
295
  export {};
@@ -42,7 +42,7 @@ export function INTERNAL_restoreRenderPipeline(soul, ctx) {
42
42
  catchall: undefined,
43
43
  logResources: undefined,
44
44
  usedVertexLayouts: soul.usedVertexLayouts ?? [],
45
- fragmentOut: soul.fragmentOut,
45
+ connectedTargets: soul.connectedTargets,
46
46
  usedImmediate: soul.usedImmediate,
47
47
  });
48
48
  const pipeline = new TgpuRenderPipelineImpl(core, {
@@ -77,7 +77,7 @@ class TgpuRenderPipelineImpl {
77
77
  soul.raw = memo.pipeline;
78
78
  soul.usedBindGroupLayouts = memo.usedBindGroupLayouts;
79
79
  soul.usedVertexLayouts = memo.usedVertexLayouts;
80
- soul.fragmentOut = memo.fragmentOut;
80
+ soul.connectedTargets = memo.connectedTargets;
81
81
  soul.bindGroups = collectBindGroupPairs(memo.usedBindGroupLayouts, memo.catchall, priors.bindGroupLayoutMap);
82
82
  soul.usedImmediate = memo.usedImmediate;
83
83
  soul.immediates = [...(priors.immediatesMap ?? [])];
@@ -181,7 +181,7 @@ class TgpuRenderPipelineImpl {
181
181
  withStencilReference(reference) {
182
182
  return this.#withPriors({ stencilReference: reference });
183
183
  }
184
- withIndexBuffer(buffer, indexFormatOrOffset, offsetElementsOrSizeBytes, sizeElementsOrUndefined) {
184
+ withIndexBuffer(buffer, indexFormatOrOffset, offsetBytesOrSizeElements, sizeBytesOrUndefined) {
185
185
  if (isGPUBuffer(buffer)) {
186
186
  if (typeof indexFormatOrOffset !== 'string') {
187
187
  throw new Error('If a GPUBuffer is passed, indexFormat must be provided.');
@@ -190,8 +190,8 @@ class TgpuRenderPipelineImpl {
190
190
  indexBuffer: {
191
191
  buffer,
192
192
  indexFormat: indexFormatOrOffset,
193
- offsetBytes: offsetElementsOrSizeBytes,
194
- sizeBytes: sizeElementsOrUndefined,
193
+ offsetBytes: offsetBytesOrSizeElements,
194
+ sizeBytes: sizeBytesOrUndefined,
195
195
  },
196
196
  });
197
197
  }
@@ -207,8 +207,8 @@ class TgpuRenderPipelineImpl {
207
207
  offsetBytes: indexFormatOrOffset !== undefined
208
208
  ? indexFormatOrOffset * sizeOf(elementType)
209
209
  : undefined,
210
- sizeBytes: sizeElementsOrUndefined !== undefined
211
- ? sizeElementsOrUndefined * sizeOf(elementType)
210
+ sizeBytes: offsetBytesOrSizeElements !== undefined
211
+ ? offsetBytesOrSizeElements * sizeOf(elementType)
212
212
  : undefined,
213
213
  },
214
214
  });
@@ -224,12 +224,10 @@ class TgpuRenderPipelineImpl {
224
224
  }
225
225
  #ownPassDescriptor() {
226
226
  const { core, priors } = this[$internal];
227
- const { fragmentOut } = core.unwrap();
227
+ const { connectedTargets } = core.unwrap();
228
228
  return {
229
229
  label: getName(core) ?? '<unnamed>',
230
- colorAttachments: fragmentOut
231
- ? connectAttachmentToShader(fragmentOut, priors.colorAttachment ?? {})
232
- : [],
230
+ colorAttachments: connectAttachmentToShader(connectedTargets, priors.colorAttachment ?? {}),
233
231
  depthStencilAttachment: priors.depthStencilAttachment,
234
232
  timestampWrites: priors.timestampWrites,
235
233
  };
@@ -345,7 +343,7 @@ class RenderPipelineCore {
345
343
  if (this.#initAsyncPromise === undefined) {
346
344
  // the pipeline did not start resolution & compilation
347
345
  const device = this.options.root.device;
348
- const { resolutionResult, descriptor, connectedAttribs, fragmentOut } = this.resolveAndCreateShaderModule();
346
+ const { resolutionResult, descriptor, connectedTargets, connectedAttribs } = this.resolveAndCreateShaderModule();
349
347
  const { usedBindGroupLayouts, catchall, logResources } = resolutionResult;
350
348
  this.#initAsyncPromise = device
351
349
  .createRenderPipelineAsync(descriptor)
@@ -356,7 +354,7 @@ class RenderPipelineCore {
356
354
  catchall,
357
355
  logResources,
358
356
  usedVertexLayouts: connectedAttribs.usedVertexLayouts,
359
- fragmentOut,
357
+ connectedTargets,
360
358
  usedImmediate: resolutionResult.usedImmediate,
361
359
  };
362
360
  this.#performanceTracker.measureCompile(device);
@@ -375,7 +373,7 @@ class RenderPipelineCore {
375
373
  throw new Error("'pipeline.initAsync()' was called and is not yet resolved.");
376
374
  }
377
375
  const device = this.options.root.device;
378
- const { resolutionResult, descriptor, connectedAttribs, fragmentOut } = this.resolveAndCreateShaderModule();
376
+ const { resolutionResult, descriptor, connectedTargets, connectedAttribs } = this.resolveAndCreateShaderModule();
379
377
  const { usedBindGroupLayouts, catchall, logResources } = resolutionResult;
380
378
  this.#memo = {
381
379
  pipeline: device.createRenderPipeline(descriptor),
@@ -383,7 +381,7 @@ class RenderPipelineCore {
383
381
  catchall,
384
382
  logResources,
385
383
  usedVertexLayouts: connectedAttribs.usedVertexLayouts,
386
- fragmentOut,
384
+ connectedTargets,
387
385
  usedImmediate: resolutionResult.usedImmediate,
388
386
  };
389
387
  this.#performanceTracker.measureCompile(device);
@@ -444,7 +442,7 @@ class RenderPipelineCore {
444
442
  if (tgpuDescriptor.fragment) {
445
443
  descriptor.fragment = {
446
444
  module,
447
- targets: connectedTargets,
445
+ targets: connectedTargets.map((target) => (target ? target.target : null)),
448
446
  };
449
447
  }
450
448
  if (tgpuDescriptor.primitive) {
@@ -467,12 +465,14 @@ class RenderPipelineCore {
467
465
  if (tgpuDescriptor.multisample) {
468
466
  descriptor.multisample = tgpuDescriptor.multisample;
469
467
  }
470
- return { resolutionResult, descriptor, connectedAttribs, fragmentOut };
468
+ return { resolutionResult, descriptor, connectedTargets, connectedAttribs };
471
469
  }
472
470
  }
473
471
  /**
474
- * Assumes vertexOut and fragmentIn are matching when it comes to the keys, that is fragmentIn's keyset is a subset of vertexOut's
475
- * Logs a warning, when they don't match in terms of custom locations
472
+ * Throws, when vertexOut and fragmentIn don't match in terms of custom locations.
473
+ *
474
+ * @privateRemarks
475
+ * Note that vertexOut is undefined in shellless functions.
476
476
  */
477
477
  export function matchUpVaryingLocations(vertexOut = {}, fragmentIn = {}, vertexFnName, fragmentFnName) {
478
478
  const locations = {};
@@ -497,13 +497,13 @@ export function matchUpVaryingLocations(vertexOut = {}, fragmentIn = {}, vertexF
497
497
  saveLocation(key, customLocation);
498
498
  }
499
499
  else if (locations[key] !== customLocation) {
500
- logger.warn('locations-mismatched', `Mismatched location between vertexFn (${vertexFnName}) output (${locations[key]}) and fragmentFn (${fragmentFnName}) input (${customLocation}) for the key "${key}", using the location set on vertex output.`);
500
+ throw new Error(`Mismatched location between vertexFn (${vertexFnName}) output (${locations[key]}) and fragmentFn (${fragmentFnName}) input (${customLocation}) for the key "${key}".`);
501
501
  }
502
502
  }
503
503
  // automatically assign remaining locations to the rest
504
504
  let nextLocation = 0;
505
- for (const key of Object.keys(vertexOut ?? {})) {
506
- if (isBuiltin(vertexOut[key]) || locations[key] !== undefined) {
505
+ for (const [key, value] of [...Object.entries(vertexOut), ...Object.entries(fragmentIn)]) {
506
+ if (isBuiltin(value) || locations[key] !== undefined) {
507
507
  continue;
508
508
  }
509
509
  while (usedLocations.has(nextLocation)) {
@@ -41,14 +41,27 @@ async function readTimestamps(querySet, registrations) {
41
41
  if (!querySet.available) {
42
42
  return;
43
43
  }
44
- const result = await querySet.read();
44
+ let result;
45
+ try {
46
+ result = await querySet.read();
47
+ }
48
+ catch {
49
+ // The read was cancelled or failed (e.g. the device was destroyed or lost mid-read).
50
+ // Genuine validation errors are already reported through the device's error handling.
51
+ return;
52
+ }
45
53
  for (const { priors, callback } of registrations) {
46
54
  const start = result[priors.timestampWrites?.beginningOfPassWriteIndex ?? 0];
47
55
  const end = result[priors.timestampWrites?.endOfPassWriteIndex ?? 1];
48
56
  if (start === undefined || end === undefined) {
49
57
  throw new Error('QuerySet did not return valid timestamps.');
50
58
  }
51
- await callback(start, end);
59
+ try {
60
+ await callback(start, end);
61
+ }
62
+ catch (err) {
63
+ console.error('An error was thrown from a performance callback (withPerformanceCallback). Errors thrown here cannot be caught by the caller, so handle them within the callback itself.', err);
64
+ }
52
65
  }
53
66
  }
54
67
  /** Returns false when the encoder is one we cannot defer work to, meaning the callback never fires */
@@ -1,4 +1,4 @@
1
- import { bannedTokens, builtins } from "../../nameUtils.js";
1
+ import { bannedTokens, builtins, templateEnumerants } from "../../nameUtils.js";
2
2
  import { $internal } from "../../shared/symbols.js";
3
3
  import { ShelllessRepository } from "../../tgsl/shellless.js";
4
4
  class NamespaceImpl {
@@ -6,7 +6,7 @@ class NamespaceImpl {
6
6
  constructor(strategy) {
7
7
  this[$internal] = {
8
8
  strategy,
9
- takenGlobalIdentifiers: new Set([...bannedTokens, ...builtins]),
9
+ takenGlobalIdentifiers: new Set([...bannedTokens, ...builtins, ...templateEnumerants]),
10
10
  shelllessRepo: new ShelllessRepository(),
11
11
  memoizedResolves: new WeakMap(),
12
12
  memoizedLazy: new WeakMap(),
@@ -11,7 +11,6 @@ export class ConfigurableImpl {
11
11
  ]);
12
12
  }
13
13
  pipe(transform) {
14
- const newCfg = transform(this);
15
- return new ConfigurableImpl([...this.bindings, ...newCfg.bindings]);
14
+ return transform(this);
16
15
  }
17
16
  }
@@ -430,9 +430,13 @@ export interface TgpuRoot extends Unwrapper, WithBinding {
430
430
  */
431
431
  get enabledWgslLanguageFeatures(): ReadonlySet<string>;
432
432
  /**
433
- * Destroys all underlying resources (i.e. buffers...) created through this root object.
433
+ * Releases internal resource caches (e.g. utility texture pipelines),
434
+ * letting the GC reclaim them.
435
+ *
436
+ * Buffers, textures and other resources created through the root are NOT destroyed.
437
+ *
434
438
  * If the object is created via `tgpu.init` instead of `tgpu.initFromDevice`,
435
- * then the inner GPU device is destroyed as well.
439
+ * destroys the underlying WebGPU device as well.
436
440
  */
437
441
  destroy(): void;
438
442
  '~unstable': Pick<ExperimentalTgpuRoot, 'createCommandEncoder' | 'createComparisonSampler' | 'createGuardedComputePipeline' | 'createRenderBundleEncoder' | 'createSampler' | 'createTexture' | 'nameRegistrySetting' | 'shaderGeneratorClass' | 'pipe' | 'with'>;
@@ -1,5 +1,5 @@
1
1
  import { type AnyData } from '../../data/dataTypes.ts';
2
- import type { BaseData } from '../../data/wgslTypes.ts';
2
+ import { type BaseData } from '../../data/wgslTypes.ts';
3
3
  import type { InferGPU } from '../../shared/repr.ts';
4
4
  import { $getNameForward, $gpuValueOf, $internal, $resolve, $soul } from '../../shared/symbols.ts';
5
5
  import type { UnwrapRuntimeConstructor } from '../../tgpuBindGroupLayout.ts';
@@ -1,6 +1,10 @@
1
- import { isData } from "../../data/dataTypes.js";
1
+ import { isData, undecorate } from "../../data/dataTypes.js";
2
2
  import { schemaCallWrapper } from "../../data/schemaCallWrapper.js";
3
3
  import { isSnippet, snip } from "../../data/snippet.js";
4
+ import { deepEqual } from "../../data/deepEqual.js";
5
+ import { arrayOf } from "../../data/array.js";
6
+ import { UnknownData } from "../../data/dataTypes.js";
7
+ import { isAbstract, isWgslArray } from "../../data/wgslTypes.js";
4
8
  import { getResolutionCtx } from "../../execMode.js";
5
9
  import { makeDereferenceable } from "../../tgsl/makeDereferenceable.js";
6
10
  import { makeResolvable } from "../../tgsl/makeResolvable.js";
@@ -19,10 +23,65 @@ export function mutableAccessor(schemaOrConstructor, defaultValue) {
19
23
  // --------------
20
24
  // Implementation
21
25
  // --------------
26
+ /**
27
+ * Accessors typed with a runtime-sized array schema (e.g. `d.arrayOf(d.f32)`) accept
28
+ * any array of that element type. If the provided JS value has a concrete length, we
29
+ * use a statically-sized schema so that the generated code can take advantage of it.
30
+ */
31
+ function concretizeSchema(accessorSchema, value) {
32
+ if (isWgslArray(accessorSchema) && accessorSchema.elementCount === 0 && Array.isArray(value)) {
33
+ if (value.length === 0) {
34
+ throw new Error(`Cannot use empty array as an accessor value. Empty arrays aren't representable in shader code.`);
35
+ }
36
+ return arrayOf(accessorSchema.elementType, value.length);
37
+ }
38
+ return accessorSchema;
39
+ }
40
+ /**
41
+ * Origins of values that can be assigned to (or are handles, like storage textures).
42
+ * Accessors can only refer to module-scope resources, so function-scope origins
43
+ * ('function', 'local-def') are never provided to them.
44
+ */
45
+ const mutableOrigins = new Set(['mutable', 'workgroup', 'private', 'handle']);
46
+ /**
47
+ * Whether a value of type `providedType` can be used in place of `accessorSchema` without
48
+ * any conversion. Runtime-sized array schemas accept arrays of any length with a matching element type.
49
+ */
50
+ function matchesSchema(accessorSchema, providedType) {
51
+ if (providedType === UnknownData) {
52
+ return false;
53
+ }
54
+ const expected = undecorate(accessorSchema);
55
+ const provided = undecorate(providedType);
56
+ if (isWgslArray(expected) && expected.elementCount === 0 && isWgslArray(provided)) {
57
+ return deepEqual(expected.elementType, provided.elementType);
58
+ }
59
+ return deepEqual(expected, provided);
60
+ }
61
+ /**
62
+ * Values with a concrete WGSL type (buffers, variables, GPU functions, ...) are used as-is,
63
+ * so they have to match the accessor's schema exactly. Mutable accessors are also assigned to,
64
+ * so the provided value has to be mutable.
65
+ */
66
+ function validateAccessorSnippet(accessor, snippet) {
67
+ const isMutable = accessor.resourceType === 'mutable-accessor';
68
+ const description = `${isMutable ? 'mutable accessor' : 'accessor'} '${getName(accessor) ?? '<unnamed>'}'`;
69
+ if (isMutable && !mutableOrigins.has(snippet.origin)) {
70
+ throw new Error(`Values provided to ${description} must be mutable (e.g. mutable buffers, private or workgroup variables), got a value with origin '${snippet.origin}'. Use tgpu.accessor for read-only access.`);
71
+ }
72
+ if (!matchesSchema(accessor.schema, snippet.dataType)) {
73
+ throw new Error(`Value of type '${String(snippet.dataType)}' does not match the schema of ${description}: '${String(accessor.schema)}'.`);
74
+ }
75
+ }
22
76
  /**
23
77
  * @returns A snippet representing the accessor.
24
78
  */
25
79
  function createAccessorSnippet(accessor) {
80
+ const snippet = createUnvalidatedAccessorSnippet(accessor);
81
+ validateAccessorSnippet(accessor, snippet);
82
+ return snippet;
83
+ }
84
+ function createUnvalidatedAccessorSnippet(accessor) {
26
85
  // oxlint-disable-next-line typescript/no-non-null-assertion -- it's there
27
86
  const ctx = getResolutionCtx();
28
87
  let value = getGpuValueRecursively(ctx.unwrap(accessor.slot));
@@ -35,23 +94,31 @@ function createAccessorSnippet(accessor) {
35
94
  if (isSnippet(value)) {
36
95
  value = value.value;
37
96
  }
97
+ value = getGpuValueRecursively(value);
38
98
  }
39
99
  const ownSnippet = getOwnSnippet(value);
40
100
  if (ownSnippet) {
41
101
  return ownSnippet;
42
102
  }
43
103
  if (isGPUCallable(value)) {
44
- return value[$gpuCallable].call(ctx, []);
104
+ const result = value[$gpuCallable].call(ctx, []);
105
+ if (result.dataType !== UnknownData && !isAbstract(result.dataType)) {
106
+ return result;
107
+ }
108
+ // The result is a plain JS value (e.g. an array literal or a number returned from
109
+ // `tgpu.comptime`), so we treat it the same way as a directly provided JS value.
110
+ value = result.value;
45
111
  }
46
112
  if (isTgpuFn(value) || hasTinyestMetadata(value)) {
47
113
  const fn = ctx.resolve(value);
48
- return ctx.withResetIndentLevel(() => snip(`${fn.value}()`, accessor.schema, /* origin */ 'runtime', fn.possibleSideEffects));
114
+ return ctx.withResetIndentLevel(() => snip(`${fn.value}()`, fn.dataType, /* origin */ 'runtime', fn.possibleSideEffects));
49
115
  }
50
116
  ctx.pushMode(new NormalState());
51
117
  try {
118
+ const schema = concretizeSchema(accessor.schema, value);
52
119
  // Doing a deep copy each time so that we don't have to deal with refs
53
- const cloned = schemaCallWrapper(accessor.schema, value);
54
- return snip(cloned, accessor.schema, 'constant', /* possibleSideEffects */ false);
120
+ const cloned = schemaCallWrapper(schema, value);
121
+ return snip(cloned, schema, 'constant', /* possibleSideEffects */ false);
55
122
  }
56
123
  finally {
57
124
  ctx.popMode('normal');
@@ -1,7 +1,7 @@
1
1
  import { $gpuValueOf, $internal, $resolve } from '../../shared/symbols.ts';
2
2
  import type { LayoutMembership } from '../../tgpuBindGroupLayout.ts';
3
3
  import { type WgslExternalTexture } from '../../data/texture.ts';
4
- import type { ResolutionCtx, SelfResolvable } from '../../types.ts';
4
+ import { type ResolutionCtx, type SelfResolvable } from '../../types.ts';
5
5
  import { type ResolvedSnippet } from '../../data/snippet.ts';
6
6
  import type { Infer } from '../../shared/repr.ts';
7
7
  export interface TgpuExternalTexture {
@@ -1,8 +1,9 @@
1
- import { $gpuValueOf, $internal, $ownSnippet, $resolve } from "../../shared/symbols.js";
1
+ import { $comptimeValueOf, $gpuValueOf, $internal, $ownSnippet, $resolve, } from "../../shared/symbols.js";
2
2
  import { getName, setName } from "../../shared/meta.js";
3
3
  import { textureExternal } from "../../data/texture.js";
4
4
  import { valueProxyHandler } from "../valueProxyUtils.js";
5
5
  import { inCodegenMode } from "../../execMode.js";
6
+ import { runtimeOnly } from "../../types.js";
6
7
  import { snip } from "../../data/snippet.js";
7
8
  export function isExternalTexture(value) {
8
9
  return value?.resourceType === 'external-texture';
@@ -33,6 +34,7 @@ export class TgpuExternalTextureImpl {
33
34
  get [$ownSnippet]() {
34
35
  return snip(this, schema, 'handle', false);
35
36
  },
37
+ [$comptimeValueOf]: runtimeOnly,
36
38
  [$resolve]: (ctx) => ctx.resolve(this),
37
39
  toString: () => `textureExternal:${getName(this) ?? '<unnamed>'}.$`,
38
40
  }, valueProxyHandler);
@@ -9,7 +9,7 @@ import type { TgpuDeviceOwningSoul, TgpuSoul } from '../../shared/soul.ts';
9
9
  import { $gpuValueOf, $internal, $repr, $resolve, $soul } from '../../shared/symbols.ts';
10
10
  import type { Default, TypedArray, UnionToIntersection } from '../../shared/utilityTypes.ts';
11
11
  import type { LayoutMembership } from '../../tgpuBindGroupLayout.ts';
12
- import type { ResolutionCtx, SelfResolvable } from '../../types.ts';
12
+ import { type ResolutionCtx, type SelfResolvable } from '../../types.ts';
13
13
  import type { ExperimentalTgpuRoot } from '../root/rootTypes.ts';
14
14
  import type { TextureProps } from './textureProps.ts';
15
15
  import type { AllowedUsages, LiteralToExtensionMap, RenderFlag, SampledFlag } from './usageExtension.ts';
@@ -3,7 +3,8 @@ import { inCodegenMode } from "../../execMode.js";
3
3
  import { snip } from "../../data/snippet.js";
4
4
  import { getName, setName } from "../../shared/meta.js";
5
5
  import { getTextureFormatInfo, } from "./textureFormats.js";
6
- import { $gpuValueOf, $internal, $ownSnippet, $repr, $resolve, $soul, } from "../../shared/symbols.js";
6
+ import { $gpuValueOf, $internal, $ownSnippet, $comptimeValueOf, $repr, $resolve, $soul, } from "../../shared/symbols.js";
7
+ import { runtimeOnly } from "../../types.js";
7
8
  import { valueProxyHandler } from "../valueProxyUtils.js";
8
9
  import { clearTextureWithColor, generateTextureMipmaps, writeImages } from "./textureUtils.js";
9
10
  import { mipLevelSize, origin3d, resolveImageWrite, writeRegionSize, } from "./textureWrite.js";
@@ -313,6 +314,7 @@ class TgpuFixedTextureViewImpl {
313
314
  get [$ownSnippet]() {
314
315
  return snip(this, schema, /* origin */ 'handle', false);
315
316
  },
317
+ [$comptimeValueOf]: runtimeOnly,
316
318
  [$resolve]: (ctx) => ctx.resolve(this),
317
319
  toString: () => `${this.toString()}.$`,
318
320
  }, valueProxyHandler);
@@ -375,6 +377,7 @@ export class TgpuLaidOutTextureViewImpl {
375
377
  get [$ownSnippet]() {
376
378
  return snip(this, schema, /* origin */ 'handle', false);
377
379
  },
380
+ [$comptimeValueOf]: runtimeOnly,
378
381
  [$resolve]: (ctx) => ctx.resolve(this),
379
382
  toString: () => `${this.toString()}.$`,
380
383
  }, valueProxyHandler);
@@ -405,10 +408,14 @@ export class TgpuTextureRenderViewImpl {
405
408
  };
406
409
  this[$internal] = {
407
410
  unwrap: () => {
408
- return baseTexture[$internal].materialize().createView({
409
- label: getName(this) ?? '<unnamed>',
410
- ...this.descriptor,
411
- });
411
+ const soul = this[$soul];
412
+ if (!soul.raw) {
413
+ soul.raw = soul.texture[$internal].materialize().createView({
414
+ label: getName(this) ?? '<unnamed>',
415
+ ...soul.descriptor,
416
+ });
417
+ }
418
+ return soul.raw;
412
419
  },
413
420
  format: descriptor.format ?? baseTexture.props.format,
414
421
  aspect: descriptor.aspect,
@@ -1,15 +1,16 @@
1
- import { $internal, $resolve } from '../../shared/symbols.ts';
1
+ import { $comptimeValueOf, $internal, $resolve } from '../../shared/symbols.ts';
2
+ import { type ComptimeValue, type ResolutionCtx, type SelfResolvable, type WithComptimeValue } from '../../types.ts';
2
3
  import { type ResolvedSnippet, type Snippet } from '../../data/snippet.ts';
3
- import type { ResolutionCtx, SelfResolvable } from '../../types.ts';
4
4
  /**
5
5
  * The result of calling `tgpu.unroll(...)`. The code responsible for
6
6
  * generating shader code can check if the value of a snippet is
7
7
  * an instance of `UnrollableIterable`, and act accordingly.
8
8
  */
9
- export declare class UnrollableIterable implements SelfResolvable {
9
+ export declare class UnrollableIterable implements SelfResolvable, WithComptimeValue {
10
10
  readonly [$internal] = true;
11
11
  readonly snippet: Snippet;
12
12
  constructor(snippet: Snippet);
13
+ [$comptimeValueOf](): ComptimeValue;
13
14
  [$resolve](ctx: ResolutionCtx): ResolvedSnippet;
14
15
  }
15
16
  /**
@@ -1,5 +1,6 @@
1
- import { $gpuCallable, $internal, $resolve } from "../../shared/symbols.js";
1
+ import { $comptimeValueOf, $gpuCallable, $internal, $resolve } from "../../shared/symbols.js";
2
2
  import { setName } from "../../shared/meta.js";
3
+ import { asComptime, } from "../../types.js";
3
4
  import { withValue } from "../../data/snippet.js";
4
5
  /**
5
6
  * The result of calling `tgpu.unroll(...)`. The code responsible for
@@ -12,6 +13,11 @@ export class UnrollableIterable {
12
13
  constructor(snippet) {
13
14
  this.snippet = snippet;
14
15
  }
16
+ [$comptimeValueOf]() {
17
+ // Outside of for-of loops, `tgpu.unroll(...)` is transparent, just like in JS
18
+ const known = asComptime(this.snippet);
19
+ return known ? { comptime: true, value: known.value } : { comptime: false };
20
+ }
15
21
  [$resolve](ctx) {
16
22
  return ctx.resolveSnippet(this.snippet);
17
23
  }
@@ -1,3 +1,5 @@
1
- import { type SelfResolvable, type WithOwnSnippet } from '../types.ts';
2
- export declare const valueProxyHandler: ProxyHandler<SelfResolvable & WithOwnSnippet>;
1
+ import { type SelfResolvable, type WithComptimeValue, type WithOwnSnippet } from '../types.ts';
2
+ type ValueProxyTarget = SelfResolvable & WithOwnSnippet & WithComptimeValue;
3
+ export declare const valueProxyHandler: ProxyHandler<ValueProxyTarget>;
3
4
  export declare function getGpuValueRecursively<T>(value: unknown): T;
5
+ export {};