typegpu 0.12.6 → 0.12.7

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 (44) 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 +4 -2
  5. package/core/function/ioSchema.d.ts +7 -2
  6. package/core/function/ioSchema.js +41 -15
  7. package/core/function/tgpuFragmentFn.d.ts +1 -1
  8. package/core/function/tgpuFragmentFn.js +2 -1
  9. package/core/function/tgpuVertexFn.js +2 -1
  10. package/core/pipeline/connectAttachmentToShader.d.ts +2 -2
  11. package/core/pipeline/connectAttachmentToShader.js +16 -21
  12. package/core/pipeline/connectTargetsToShader.d.ts +5 -1
  13. package/core/pipeline/connectTargetsToShader.js +23 -18
  14. package/core/pipeline/renderPipeline.d.ts +5 -4
  15. package/core/pipeline/renderPipeline.js +10 -12
  16. package/core/resolve/namespace.js +2 -2
  17. package/core/root/rootTypes.d.ts +6 -2
  18. package/core/slot/accessor.d.ts +1 -1
  19. package/core/slot/accessor.js +72 -5
  20. package/core/texture/texture.js +8 -4
  21. package/data/autoStruct.d.ts +1 -1
  22. package/data/autoStruct.js +8 -4
  23. package/data/deepEqual.js +19 -9
  24. package/data/matrix.js +10 -3
  25. package/data/partialIO.d.ts +8 -1
  26. package/data/partialIO.js +36 -19
  27. package/data/ref.js +2 -2
  28. package/data/snippet.d.ts +6 -4
  29. package/data/snippet.js +8 -4
  30. package/data/wgslTypes.d.ts +3 -0
  31. package/internal.d.ts +1 -0
  32. package/nameUtils.d.ts +7 -0
  33. package/nameUtils.js +60 -0
  34. package/package.json +1 -1
  35. package/shared/meta.js +1 -1
  36. package/shared/symbols.js +1 -1
  37. package/shared/tseynit.js +54 -50
  38. package/std/boolean.js +9 -10
  39. package/std/operators.js +9 -4
  40. package/std/texture.d.ts +2 -2
  41. package/tgsl/accessIndex.js +19 -23
  42. package/tgsl/forOfUtils.js +6 -6
  43. package/tgsl/generationHelpers.js +2 -2
  44. package/tgsl/wgslGenerator.js +85 -23
@@ -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');
@@ -405,10 +405,14 @@ export class TgpuTextureRenderViewImpl {
405
405
  };
406
406
  this[$internal] = {
407
407
  unwrap: () => {
408
- return baseTexture[$internal].materialize().createView({
409
- label: getName(this) ?? '<unnamed>',
410
- ...this.descriptor,
411
- });
408
+ const soul = this[$soul];
409
+ if (!soul.raw) {
410
+ soul.raw = soul.texture[$internal].materialize().createView({
411
+ label: getName(this) ?? '<unnamed>',
412
+ ...soul.descriptor,
413
+ });
414
+ }
415
+ return soul.raw;
412
416
  },
413
417
  format: descriptor.format ?? baseTexture.props.format,
414
418
  aspect: descriptor.aspect,
@@ -12,7 +12,7 @@ export declare class AutoStruct implements BaseData, SelfResolvable {
12
12
  [$internal]: Record<string, never>;
13
13
  type: 'auto-struct';
14
14
  readonly [$repr]: Record<string, unknown>;
15
- constructor(validProps: Record<string, BaseData>, typeForExtraProps: BaseData | undefined, locations?: Record<string, number>);
15
+ constructor(validProps: Record<string, BaseData>, typeForExtraProps: BaseData | undefined, locations?: Record<string, number>, autoInterpolateIntegers?: boolean);
16
16
  /**
17
17
  * Used for accessing builtins, varying and attributes in code.
18
18
  */
@@ -22,11 +22,17 @@ export class AutoStruct {
22
22
  #locations;
23
23
  #cachedStruct;
24
24
  #typeForExtraProps;
25
- constructor(validProps, typeForExtraProps, locations) {
25
+ #autoInterpolateIntegers;
26
+ static {
27
+ AutoStruct.prototype[$internal] = {};
28
+ AutoStruct.prototype.type = 'auto-struct';
29
+ }
30
+ constructor(validProps, typeForExtraProps, locations, autoInterpolateIntegers = false) {
26
31
  this.#validProps = validProps;
27
32
  this.#typeForExtraProps = typeForExtraProps;
28
33
  this.#allocated = {};
29
34
  this.#locations = locations;
35
+ this.#autoInterpolateIntegers = autoInterpolateIntegers;
30
36
  this.#usedWgslKeys = new Set();
31
37
  }
32
38
  /**
@@ -67,7 +73,7 @@ export class AutoStruct {
67
73
  if (!this.#cachedStruct) {
68
74
  this.#cachedStruct = createIoSchema(Object.fromEntries(Object.values(this.#allocated).map((alloc) => {
69
75
  return [alloc.prop, alloc.type];
70
- })), this.#locations);
76
+ })), this.#locations, this.#autoInterpolateIntegers);
71
77
  const ownName = getName(this);
72
78
  // Passing the given name forward
73
79
  if (ownName) {
@@ -83,5 +89,3 @@ export class AutoStruct {
83
89
  return `auto-struct:${getName(this) ?? '<unnamed>'}`;
84
90
  }
85
91
  }
86
- AutoStruct.prototype[$internal] = {};
87
- AutoStruct.prototype.type = 'auto-struct';
package/data/deepEqual.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { isDisarray, isLooseDecorated, isUnstruct } from "./dataTypes.js";
2
+ import { isWgslStorageTexture, isWgslTexture } from "./texture.js";
2
3
  import { isAtomic, isDecorated, isPtr, isWgslArray, isWgslStruct } from "./wgslTypes.js";
3
4
  /**
4
5
  * Performs a deep comparison of two TypeGPU data schemas.
@@ -53,11 +54,20 @@ export function deepEqual(a, b) {
53
54
  if (isPtr(a) && isPtr(b)) {
54
55
  return (a.addressSpace === b.addressSpace &&
55
56
  a.access === b.access &&
57
+ a.implicit === b.implicit &&
56
58
  deepEqual(a.inner, b.inner));
57
59
  }
58
60
  if (isAtomic(a) && isAtomic(b)) {
59
61
  return deepEqual(a.inner, b.inner);
60
62
  }
63
+ if (isWgslTexture(a) && isWgslTexture(b)) {
64
+ // Dimension and multisampling are already encoded in the type
65
+ return deepEqual(a.sampleType, b.sampleType);
66
+ }
67
+ if (isWgslStorageTexture(a) && isWgslStorageTexture(b)) {
68
+ // Dimension is already encoded in the type
69
+ return a.format === b.format && a.access === b.access;
70
+ }
61
71
  if ((isDecorated(a) && isDecorated(b)) || (isLooseDecorated(a) && isLooseDecorated(b))) {
62
72
  if (!deepEqual(a.inner, b.inner)) {
63
73
  return false;
@@ -65,18 +75,18 @@ export function deepEqual(a, b) {
65
75
  if (a.attribs.length !== b.attribs.length) {
66
76
  return false;
67
77
  }
68
- // Create comparable string representations for each attribute
69
- const getAttrKey = (attr) => {
70
- const anyAttr = attr;
71
- return `${anyAttr.type}(${(anyAttr.params ?? []).join(',')})`;
72
- };
73
- const attrsA = a.attribs.map(getAttrKey);
74
- const attrsB = b.attribs.map(getAttrKey);
75
- for (let i = 0; i < attrsA.length; i++) {
76
- if (attrsA[i] !== attrsB[i]) {
78
+ for (let i = 0; i < a.attribs.length; i++) {
79
+ const attrA = a.attribs[i];
80
+ const attrB = b.attribs[i];
81
+ const paramsA = attrA.params ?? [];
82
+ const paramsB = attrB.params ?? [];
83
+ if (attrA.type !== attrB.type ||
84
+ paramsA.length !== paramsB.length ||
85
+ paramsA.some((param, idx) => param !== paramsB[idx])) {
77
86
  return false;
78
87
  }
79
88
  }
89
+ return true;
80
90
  }
81
91
  // All other types have been checked for equality at the start
82
92
  return true;
package/data/matrix.js CHANGED
@@ -8,7 +8,7 @@ import { numericLiteralToSnippet } from "../tgsl/generationHelpers.js";
8
8
  import { f32 } from "./numeric.js";
9
9
  import {} from "./snippet.js";
10
10
  import { vec2f, vec3f, vec4f } from "./vector.js";
11
- import { isVec, } from "./wgslTypes.js";
11
+ import { isMat, isMatInstance, isVec, } from "./wgslTypes.js";
12
12
  export class MatBase {
13
13
  }
14
14
  function createMatSchema(options) {
@@ -18,7 +18,14 @@ function createMatSchema(options) {
18
18
  normalImpl: (...args) => {
19
19
  const elements = [];
20
20
  for (const arg of args) {
21
- if (typeof arg === 'number') {
21
+ if (isMatInstance(arg)) {
22
+ for (const column of arg.columns) {
23
+ for (const elem of column) {
24
+ elements.push(elem);
25
+ }
26
+ }
27
+ }
28
+ else if (typeof arg === 'number') {
22
29
  elements.push(arg);
23
30
  }
24
31
  else {
@@ -35,7 +42,7 @@ function createMatSchema(options) {
35
42
  }
36
43
  return new options.MatImpl(...elements);
37
44
  },
38
- argTypes: (...args) => args.map((arg) => (isVec(arg) ? arg : f32)),
45
+ argTypes: (...args) => args.map((arg) => (isVec(arg) || isMat(arg) ? arg : f32)),
39
46
  codegenImpl: (ctx, args) => ctx.gen.typeInstantiation(schema, args),
40
47
  });
41
48
  const schema = Object.assign(construct, {
@@ -4,10 +4,17 @@ import type { InferPatch } from '../shared/repr.ts';
4
4
  export interface WriteInstruction {
5
5
  data: Uint8Array<ArrayBuffer>;
6
6
  gpuOffset: number;
7
+ /** How many bytes to upload. Can be larger than `data`, e.g. to cover trailing padding. */
8
+ uploadSize: number;
7
9
  }
8
10
  /**
9
11
  * Converts `{idx, value}[]` sparse arrays into `Record<number, T>` format.
10
12
  */
11
13
  export declare function convertPartialToPatch(schema: wgsl.BaseData, data: unknown): unknown;
12
- export declare function getPatchInstructions<TData extends wgsl.BaseData>(schema: TData, data: unknown, targetBuffer?: ArrayBuffer): WriteInstruction[];
14
+ /**
15
+ * @param validateRange Called for every byte range that is about to be written, before any data
16
+ * is written into `targetBuffer`. Returns how many bytes to upload for that range.
17
+ * Throwing from it leaves `targetBuffer` untouched.
18
+ */
19
+ export declare function getPatchInstructions<TData extends wgsl.BaseData>(schema: TData, data: unknown, targetBuffer?: ArrayBuffer, validateRange?: (start: number, end: number) => number): WriteInstruction[];
13
20
  export declare function patchArrayBuffer<T extends BaseData>(buffer: ArrayBuffer, schema: T, data: InferPatch<T>): void;
package/data/partialIO.js CHANGED
@@ -37,7 +37,12 @@ export function convertPartialToPatch(schema, data) {
37
37
  return data;
38
38
  }
39
39
  const isLittleEndian = getSystemEndianness() === 'little';
40
- export function getPatchInstructions(schema, data, targetBuffer) {
40
+ /**
41
+ * @param validateRange Called for every byte range that is about to be written, before any data
42
+ * is written into `targetBuffer`. Returns how many bytes to upload for that range.
43
+ * Throwing from it leaves `targetBuffer` untouched.
44
+ */
45
+ export function getPatchInstructions(schema, data, targetBuffer, validateRange) {
41
46
  const totalSize = sizeOf(schema);
42
47
  if (totalSize === 0 || data === undefined || data === null) {
43
48
  return [];
@@ -46,6 +51,8 @@ export function getPatchInstructions(schema, data, targetBuffer) {
46
51
  const writer = new BufferWriter(buf);
47
52
  const compiledView = new DataView(buf);
48
53
  const segments = [];
54
+ // Deferred until every range is validated
55
+ const pendingWrites = [];
49
56
  function collect(node, value, offset, padding) {
50
57
  if (value === undefined || value === null) {
51
58
  return;
@@ -67,7 +74,9 @@ export function getPatchInstructions(schema, data, targetBuffer) {
67
74
  const elementPadding = elementSize - sizeOf(arrSchema.elementType);
68
75
  if (ArrayBuffer.isView(value)) {
69
76
  const copyLen = Math.min(value.byteLength, arrSchema.elementCount * elementSize);
70
- new Uint8Array(buf, offset, copyLen).set(new Uint8Array(value.buffer, value.byteOffset, copyLen));
77
+ pendingWrites.push(() => {
78
+ new Uint8Array(buf, offset, copyLen).set(new Uint8Array(value.buffer, value.byteOffset, copyLen));
79
+ });
71
80
  segments.push({ start: offset, end: offset + copyLen, padding });
72
81
  return;
73
82
  }
@@ -88,17 +97,19 @@ export function getPatchInstructions(schema, data, targetBuffer) {
88
97
  }
89
98
  const leafSize = sizeOf(node);
90
99
  const compiledWriter = getCompiledWriter(node);
91
- if (compiledWriter) {
92
- compiledWriter(compiledView, offset, value, isLittleEndian, offset + leafSize);
93
- }
94
- else {
95
- writer.seekTo(offset);
96
- writeData(writer, node, value);
97
- }
100
+ pendingWrites.push(() => {
101
+ if (compiledWriter) {
102
+ compiledWriter(compiledView, offset, value, isLittleEndian, offset + leafSize);
103
+ }
104
+ else {
105
+ writer.seekTo(offset);
106
+ writeData(writer, node, value);
107
+ }
108
+ });
98
109
  segments.push({ start: offset, end: offset + leafSize, padding });
99
110
  }
100
111
  collect(schema, data, 0);
101
- const instructions = [];
112
+ const runs = [];
102
113
  let run = null;
103
114
  for (const seg of segments) {
104
115
  if (run && seg.start === run.end + (run.padding ?? 0)) {
@@ -106,21 +117,27 @@ export function getPatchInstructions(schema, data, targetBuffer) {
106
117
  }
107
118
  else {
108
119
  if (run) {
109
- instructions.push({
110
- gpuOffset: run.start,
111
- data: new Uint8Array(buf, run.start, run.end - run.start).slice(),
112
- });
120
+ runs.push(run);
113
121
  }
114
122
  run = seg;
115
123
  }
116
124
  }
117
125
  if (run) {
118
- instructions.push({
119
- gpuOffset: run.start,
120
- data: new Uint8Array(buf, run.start, run.end - run.start).slice(),
121
- });
126
+ runs.push(run);
127
+ }
128
+ const uploads = runs.map(({ start, end }) => ({
129
+ start,
130
+ end,
131
+ uploadSize: validateRange?.(start, end) ?? end - start,
132
+ }));
133
+ for (const write of pendingWrites) {
134
+ write();
122
135
  }
123
- return instructions;
136
+ return uploads.map(({ start, end, uploadSize }) => ({
137
+ gpuOffset: start,
138
+ data: new Uint8Array(buf, start, end - start).slice(),
139
+ uploadSize,
140
+ }));
124
141
  }
125
142
  export function patchArrayBuffer(buffer, schema, data) {
126
143
  const instructions = getPatchInstructions(schema, data, buffer);
package/data/ref.js CHANGED
@@ -4,7 +4,7 @@ import { setName } from "../shared/meta.js";
4
4
  import { $gpuCallable, $internal, $ownSnippet, $resolve } from "../shared/symbols.js";
5
5
  import { UnknownData } from "./dataTypes.js";
6
6
  import { createPtrFromOrigin, explicitFrom } from "./ptr.js";
7
- import { isAlias, snip, withDataType } from "./snippet.js";
7
+ import { isStoredInMemory, snip, withDataType, } from "./snippet.js";
8
8
  import { isNaturallyEphemeral, isPtr } from "./wgslTypes.js";
9
9
  export const _ref = (() => {
10
10
  const impl = ((value) => INTERNAL_createRef(value));
@@ -23,7 +23,7 @@ export const _ref = (() => {
23
23
  - Try 'd.ref(${typeStr}(${value}));' instead to create a new referencable value.
24
24
  -----`);
25
25
  }
26
- if (isAlias(value) && isNaturallyEphemeral(value.dataType)) {
26
+ if (isStoredInMemory(value) && isNaturallyEphemeral(value.dataType)) {
27
27
  const typeStr = ctx.resolve(value.dataType).value;
28
28
  throw new WgslTypeError(stitch `d.ref(${value}) is illegal, cannot take a reference to a scalar value.
29
29
  -----
package/data/snippet.d.ts CHANGED
@@ -7,25 +7,26 @@ export type Origin = 'uniform' | 'readonly' | 'mutable' | 'workgroup' | 'private
7
7
  */
8
8
  export declare function fallthroughCopyOrigin(origin: Origin): Origin;
9
9
  /**
10
- * Whether a snippet aliases a value that lives outside the current expression.
10
+ * Whether a snippet refers to a value already stored in memory.
11
11
  *
12
12
  * @example
13
13
  * ```ts
14
14
  * function foo(a: number) {
15
+ * 'use gpu';
15
16
  * const color = d.vec3f(1, 2, 3);
16
17
  * return color * a;
17
18
  * }
18
19
  *
19
- * // References:
20
+ * // Stored in memory:
20
21
  * // - color
21
22
  * // - a
22
23
  * //
23
- * // Not references:
24
+ * // Ephemeral values:
24
25
  * // - d.vec3f(1, 2, 3)
25
26
  * // - color * a
26
27
  * ```
27
28
  */
28
- export declare function isAlias(snippet: Snippet): boolean;
29
+ export declare function isStoredInMemory(snippet: Snippet): boolean;
29
30
  export declare const originToPtrParams: {
30
31
  readonly uniform: {
31
32
  readonly space: "uniform";
@@ -61,6 +62,7 @@ export declare const originToPtrParams: {
61
62
  };
62
63
  };
63
64
  export type OriginToPtrParams = typeof originToPtrParams;
65
+ export declare function isAddressableOrigin(origin: Origin): origin is keyof OriginToPtrParams;
64
66
  export interface Snippet {
65
67
  readonly value: unknown;
66
68
  /**
package/data/snippet.js CHANGED
@@ -16,25 +16,26 @@ export function fallthroughCopyOrigin(origin) {
16
16
  return 'runtime';
17
17
  }
18
18
  /**
19
- * Whether a snippet aliases a value that lives outside the current expression.
19
+ * Whether a snippet refers to a value already stored in memory.
20
20
  *
21
21
  * @example
22
22
  * ```ts
23
23
  * function foo(a: number) {
24
+ * 'use gpu';
24
25
  * const color = d.vec3f(1, 2, 3);
25
26
  * return color * a;
26
27
  * }
27
28
  *
28
- * // References:
29
+ * // Stored in memory:
29
30
  * // - color
30
31
  * // - a
31
32
  * //
32
- * // Not references:
33
+ * // Ephemeral values:
33
34
  * // - d.vec3f(1, 2, 3)
34
35
  * // - color * a
35
36
  * ```
36
37
  */
37
- export function isAlias(snippet) {
38
+ export function isStoredInMemory(snippet) {
38
39
  return !(snippet.origin === 'runtime' || snippet.origin === 'constant');
39
40
  }
40
41
  export const originToPtrParams = {
@@ -48,6 +49,9 @@ export const originToPtrParams = {
48
49
  // Local declarations are also in the `function` address space
49
50
  'local-def': { space: 'function', access: 'read-write' },
50
51
  };
52
+ export function isAddressableOrigin(origin) {
53
+ return origin in originToPtrParams;
54
+ }
51
55
  class SnippetImpl {
52
56
  value;
53
57
  dataType;
@@ -709,6 +709,7 @@ export interface Mat2x2f extends BaseData {
709
709
  readonly [$validUniformSchema]: true;
710
710
  (...elements: [number, number, number, number]): m2x2f;
711
711
  (...columns: [v2f, v2f]): m2x2f;
712
+ (other: m2x2f): m2x2f;
712
713
  (): m2x2f;
713
714
  identity(): m2x2f;
714
715
  }
@@ -724,6 +725,7 @@ export interface Mat3x3f extends BaseData {
724
725
  readonly [$validUniformSchema]: true;
725
726
  (...elements: [number, number, number, number, number, number, number, number, number]): m3x3f;
726
727
  (...columns: [v3f, v3f, v3f]): m3x3f;
728
+ (other: m3x3f): m3x3f;
727
729
  (): m3x3f;
728
730
  identity(): m3x3f;
729
731
  }
@@ -739,6 +741,7 @@ export interface Mat4x4f extends BaseData {
739
741
  readonly [$validUniformSchema]: true;
740
742
  (...elements: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number]): m4x4f;
741
743
  (...columns: [v4f, v4f, v4f, v4f]): m4x4f;
744
+ (other: m4x4f): m4x4f;
742
745
  (): m4x4f;
743
746
  identity(): m4x4f;
744
747
  translation(vec: v3f): m4x4f;
package/internal.d.ts CHANGED
@@ -13,4 +13,5 @@ export type { RestoreContext } from './serial/types.ts';
13
13
  export { deserializeDataSchema, serializeDataSchema, type SerializedDataSchema, } from './serial/schema.ts';
14
14
  export type { ResolutionCtx, FunctionArgument } from './types.ts';
15
15
  export type { Snippet, ResolvedSnippet, Origin } from './data/snippet.ts';
16
+ export type { AutoStruct } from './data/autoStruct.ts';
16
17
  export type { ShaderGenerator, ResolvedStatement, BinaryOperator, ShaderGeneratorClass, FunctionDefinitionOptions, ConstantDefinitionOptions, VariableDefinitionOptions, } from './tgsl/shaderGenerator.ts';
package/nameUtils.d.ts CHANGED
@@ -1,4 +1,11 @@
1
1
  export declare const bannedTokens: Set<string>;
2
+ /**
3
+ * Predeclared enumerants that appear as template parameters, e.g. `var<storage, read_write>` or
4
+ * `texture_storage_2d<rgba8unorm, write>`. A module-scope or local declaration with one of these names
5
+ * would shadow the enumerant, so a template using it would refer to the declaration instead.
6
+ * Unlike `bannedTokens`, these remain valid as struct member names, which never shadow anything.
7
+ */
8
+ export declare const templateEnumerants: Set<string>;
2
9
  export declare const builtins: Set<string>;
3
10
  /**
4
11
  * Sanitizes the primer so that it is compliant with WGSL guidelines.
package/nameUtils.js CHANGED
@@ -178,6 +178,66 @@ export const bannedTokens = new Set([
178
178
  'uniform',
179
179
  'storage',
180
180
  ]);
181
+ /**
182
+ * Predeclared enumerants that appear as template parameters, e.g. `var<storage, read_write>` or
183
+ * `texture_storage_2d<rgba8unorm, write>`. A module-scope or local declaration with one of these names
184
+ * would shadow the enumerant, so a template using it would refer to the declaration instead.
185
+ * Unlike `bannedTokens`, these remain valid as struct member names, which never shadow anything.
186
+ */
187
+ export const templateEnumerants = new Set([
188
+ // address spaces
189
+ 'function',
190
+ 'private',
191
+ 'workgroup',
192
+ 'uniform',
193
+ 'storage',
194
+ 'immediate',
195
+ // access modes
196
+ 'read',
197
+ 'write',
198
+ 'read_write',
199
+ // texel formats
200
+ 'rgba8unorm',
201
+ 'rgba8snorm',
202
+ 'rgba8uint',
203
+ 'rgba8sint',
204
+ 'rgba16unorm',
205
+ 'rgba16snorm',
206
+ 'rgba16uint',
207
+ 'rgba16sint',
208
+ 'rgba16float',
209
+ 'rg8unorm',
210
+ 'rg8snorm',
211
+ 'rg8uint',
212
+ 'rg8sint',
213
+ 'rg16unorm',
214
+ 'rg16snorm',
215
+ 'rg16uint',
216
+ 'rg16sint',
217
+ 'rg16float',
218
+ 'r8unorm',
219
+ 'r8snorm',
220
+ 'r8uint',
221
+ 'r8sint',
222
+ 'r16unorm',
223
+ 'r16snorm',
224
+ 'r16uint',
225
+ 'r16sint',
226
+ 'r16float',
227
+ 'r32uint',
228
+ 'r32sint',
229
+ 'r32float',
230
+ 'rg32uint',
231
+ 'rg32sint',
232
+ 'rg32float',
233
+ 'rgba32uint',
234
+ 'rgba32sint',
235
+ 'rgba32float',
236
+ 'bgra8unorm',
237
+ 'rgb10a2unorm',
238
+ 'rgb10a2uint',
239
+ 'rg11b10ufloat',
240
+ ]);
181
241
  export const builtins = new Set([
182
242
  // constructors
183
243
  'array',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typegpu",
3
- "version": "0.12.6",
3
+ "version": "0.12.7",
4
4
  "description": "A thin layer between JS and WebGPU/WGSL that improves development experience and allows for faster iteration.",
5
5
  "keywords": [
6
6
  "compute",
package/shared/meta.js CHANGED
@@ -1,4 +1,4 @@
1
- const version = "0.12.6";
1
+ const version = "0.12.7";
2
2
  import { DEV, TEST } from "./env.js";
3
3
  import { $getNameForward, $soul, isMarkedInternal } from "./symbols.js";
4
4
  import { normalizeMetadata } from "./normalizeMetadata.js";
package/shared/symbols.js CHANGED
@@ -1,4 +1,4 @@
1
- const version = "0.12.6";
1
+ const version = "0.12.7";
2
2
  export const $internal = Symbol(`typegpu:${version}:$internal`);
3
3
  /** A plain record of all definitional state of a resource, surviving transfer between runtimes */
4
4
  export const $soul = Symbol(`typegpu:${version}:$soul`);