typegpu 0.12.7 → 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 (60) hide show
  1. package/core/function/autoIO.js +2 -2
  2. package/core/function/comptime.js +7 -5
  3. package/core/function/createCallableSchema.js +4 -3
  4. package/core/function/dualImpl.js +20 -19
  5. package/core/function/ioSchema.d.ts +10 -0
  6. package/core/function/ioSchema.js +37 -16
  7. package/core/function/tgpuFn.d.ts +11 -1
  8. package/core/function/tgpuFn.js +9 -2
  9. package/core/pipeline/renderPipeline.d.ts +4 -2
  10. package/core/pipeline/renderPipeline.js +12 -10
  11. package/core/pipeline/timeable.js +15 -2
  12. package/core/root/configurableImpl.js +1 -2
  13. package/core/texture/externalTexture.d.ts +1 -1
  14. package/core/texture/externalTexture.js +3 -1
  15. package/core/texture/texture.d.ts +1 -1
  16. package/core/texture/texture.js +4 -1
  17. package/core/unroll/tgpuUnroll.d.ts +4 -3
  18. package/core/unroll/tgpuUnroll.js +7 -1
  19. package/core/valueProxyUtils.d.ts +4 -2
  20. package/core/valueProxyUtils.js +4 -2
  21. package/data/compiledIO.js +12 -9
  22. package/data/dataIO.d.ts +10 -6
  23. package/data/dataIO.js +19 -4
  24. package/data/dataTypes.d.ts +0 -5
  25. package/data/dataTypes.js +0 -6
  26. package/data/matrixColumnsAccess.d.ts +8 -0
  27. package/data/matrixColumnsAccess.js +15 -0
  28. package/data/offsetUtils.js +181 -102
  29. package/data/ref.d.ts +12 -5
  30. package/data/ref.js +50 -23
  31. package/data/snippet.d.ts +7 -0
  32. package/data/snippet.js +9 -0
  33. package/data/struct.js +3 -0
  34. package/data/wgslTypes.d.ts +3 -3
  35. package/package.json +1 -1
  36. package/resolutionCtx.js +1 -1
  37. package/shared/meta.js +1 -1
  38. package/shared/symbols.d.ts +5 -0
  39. package/shared/symbols.js +6 -1
  40. package/std/numeric.js +1 -4
  41. package/std/packing.js +10 -10
  42. package/tgpuLogger.d.ts +1 -1
  43. package/tgpuLogger.js +0 -2
  44. package/tgsl/accessIndex.js +18 -15
  45. package/tgsl/accessProp.js +10 -7
  46. package/tgsl/conversion.js +3 -2
  47. package/tgsl/forOfUtils.js +2 -2
  48. package/tgsl/generationHelpers.d.ts +4 -3
  49. package/tgsl/generationHelpers.js +16 -3
  50. package/tgsl/infixDispatch.d.ts +4 -1
  51. package/tgsl/infixDispatch.js +13 -1
  52. package/tgsl/makeDereferenceable.d.ts +1 -1
  53. package/tgsl/makeDereferenceable.js +3 -1
  54. package/tgsl/shellless.js +5 -9
  55. package/tgsl/stringifySnippet.d.ts +9 -0
  56. package/tgsl/stringifySnippet.js +24 -0
  57. package/tgsl/wgslGenerator.d.ts +1 -0
  58. package/tgsl/wgslGenerator.js +98 -66
  59. package/types.d.ts +23 -2
  60. package/types.js +30 -5
package/data/dataIO.js CHANGED
@@ -4,7 +4,7 @@ import { alignmentOf, customAlignmentOf } from "./alignmentOf.js";
4
4
  import { mat2x2f, mat3x3f, mat4x4f } from "./matrix.js";
5
5
  import { sizeOf } from "./sizeOf.js";
6
6
  import { vec2f, vec2h, vec2i, vec2u, vec3f, vec3h, vec3i, vec3u, vec4f, vec4h, vec4i, vec4u, } from "./vector.js";
7
- import { isWgslArray } from "./wgslTypes.js";
7
+ import * as wgsl from "./wgslTypes.js";
8
8
  import { getCompiledWriter } from "./compiledIO.js";
9
9
  import { getName } from "../shared/meta.js";
10
10
  import { roundUp } from "../mathUtils.js";
@@ -104,8 +104,18 @@ const dataWriters = {
104
104
  }
105
105
  },
106
106
  mat3x3f(output, _, value) {
107
- for (let i = 0; i < value.length; ++i) {
108
- output.writeFloat32(value[i]);
107
+ if (value instanceof Float32Array || wgsl.isMatInstance(value)) {
108
+ for (let i = 0; i < value.length; ++i) {
109
+ output.writeFloat32(value[i]);
110
+ }
111
+ }
112
+ else {
113
+ for (let i = 0; i < 3; ++i) {
114
+ for (let j = 0; j < 3; ++j) {
115
+ output.writeFloat32(value[3 * i + j]);
116
+ }
117
+ output.writeFloat32(0);
118
+ }
109
119
  }
110
120
  },
111
121
  mat4x4f(output, _, value) {
@@ -607,7 +617,7 @@ export function calculateOffsets(options, schema, data) {
607
617
  const bufferSize = sizeOf(schema);
608
618
  const startOffset = options?.startOffset ?? 0;
609
619
  let naturalSize = undefined;
610
- if (isWgslArray(schema) && Array.isArray(data)) {
620
+ if (wgsl.isWgslArray(schema) && Array.isArray(data)) {
611
621
  const arrayData = data;
612
622
  naturalSize =
613
623
  arrayData.length * roundUp(sizeOf(schema.elementType), alignmentOf(schema.elementType));
@@ -619,6 +629,11 @@ export function calculateOffsets(options, schema, data) {
619
629
  const endOffset = options?.endOffset ?? naturalEndOffset ?? bufferSize;
620
630
  return { startOffset, endOffset };
621
631
  }
632
+ /**
633
+ * A function for filling in buffers with data based on TypeGPU schemas.
634
+ * When schema instances or JS arrays are passed, the padding is applied automatically.
635
+ * ArrayBuffers are expected to be already padded.
636
+ */
622
637
  export function writeToArrayBuffer(buffer, schema, data, options) {
623
638
  const { startOffset, endOffset } = calculateOffsets(options, schema, data);
624
639
  // Fast path: raw byte copy, user guarantees the padded layout
@@ -3,7 +3,6 @@ import type { Infer, InferGPURecord, InferInput, InferInputRecord, InferPatch, I
3
3
  import type { $gpuRepr, $inRepr, $invalidSchemaReason, $memIdent, $repr, $reprPartial, $reprPatch, $validVertexSchema } from '../shared/symbols.ts';
4
4
  import type { Prettify } from '../shared/utilityTypes.ts';
5
5
  import type { WgslExternalTexture, WgslStorageTexture, WgslTexture } from './texture.ts';
6
- import type { Snippet } from './snippet.ts';
7
6
  import type { PackedData } from './vertexFormatData.ts';
8
7
  import * as wgsl from './wgslTypes.ts';
9
8
  import type { WgslComparisonSampler, WgslSampler } from './sampler.ts';
@@ -130,8 +129,4 @@ export type AnyData = wgsl.AnyWgslData | AnyLooseData;
130
129
  export type AnyConcreteData = Exclude<AnyData, wgsl.AbstractInt | wgsl.AbstractFloat | wgsl.Void | WgslTexture | WgslStorageTexture | WgslExternalTexture | WgslSampler | WgslComparisonSampler>;
131
130
  export declare const UnknownData: unique symbol;
132
131
  export type UnknownData = typeof UnknownData;
133
- export declare class MatrixColumnsAccess {
134
- readonly matrix: Snippet;
135
- constructor(matrix: Snippet);
136
- }
137
132
  export {};
package/data/dataTypes.js CHANGED
@@ -73,9 +73,3 @@ export function isData(value) {
73
73
  return wgsl.isWgslData(value) || isLooseData(value);
74
74
  }
75
75
  export const UnknownData = Symbol('UNKNOWN');
76
- export class MatrixColumnsAccess {
77
- matrix;
78
- constructor(matrix) {
79
- this.matrix = matrix;
80
- }
81
- }
@@ -0,0 +1,8 @@
1
+ import { $comptimeValueOf } from '../shared/symbols.ts';
2
+ import { type ComptimeValue, type WithComptimeValue } from '../types.ts';
3
+ import type { Snippet } from './snippet.ts';
4
+ export declare class MatrixColumnsAccess implements WithComptimeValue {
5
+ readonly matrix: Snippet;
6
+ constructor(matrix: Snippet);
7
+ [$comptimeValueOf](): ComptimeValue;
8
+ }
@@ -0,0 +1,15 @@
1
+ import { $comptimeValueOf } from "../shared/symbols.js";
2
+ import { asComptime } from "../types.js";
3
+ export class MatrixColumnsAccess {
4
+ matrix;
5
+ constructor(matrix) {
6
+ this.matrix = matrix;
7
+ }
8
+ [$comptimeValueOf]() {
9
+ // Same as accessing `.columns` on a matrix in JS
10
+ const known = asComptime(this.matrix);
11
+ return known
12
+ ? { comptime: true, value: known.value.columns }
13
+ : { comptime: false };
14
+ }
15
+ }
@@ -4,10 +4,26 @@ import { offsetsForProps } from "./offsets.js";
4
4
  import { sizeOf } from "./sizeOf.js";
5
5
  import { isContiguous } from "./isContiguous.js";
6
6
  import { getLongestContiguousPrefix } from "./getLongestContiguousPrefix.js";
7
- import { isVec, isWgslArray, isWgslStruct } from "./wgslTypes.js";
8
- import { undecorate } from "./dataTypes.js";
7
+ import { isMat, isVec, isWgslArray, isWgslStruct } from "./wgslTypes.js";
8
+ import { isDisarray, isUnstruct, undecorate } from "./dataTypes.js";
9
+ import { vec2f, vec3f, vec4f } from "./vector.js";
10
+ /**
11
+ * The absolute byte offset of the node, counted from the start of the root schema.
12
+ */
9
13
  const OFFSET_MARKER = Symbol('indirectOffset');
14
+ /**
15
+ * The number of contiguous data bytes starting at the node's offset.
16
+ * The run is not limited to the node itself: it continues into the data that follows,
17
+ * until the first padding byte or the end of the root schema.
18
+ * NaN means the run reaches a contiguous runtime-sized array, so its end is unknown.
19
+ *
20
+ * @remarks The parent computes this value as if the node had no padding inside.
21
+ * Non-contiguous nodes have to correct it (e.g. cap it at their own padding) before using it.
22
+ */
10
23
  const CONTIGUOUS_MARKER = Symbol('indirectContiguous');
24
+ function minContiguous(a, b) {
25
+ return Number.isNaN(a) ? b : Math.min(a, b);
26
+ }
11
27
  function isOffsetProxy(value) {
12
28
  return (typeof value === 'object' &&
13
29
  value !== null &&
@@ -26,12 +42,19 @@ function getMarker(target, prop) {
26
42
  }
27
43
  return undefined;
28
44
  }
29
- function makeProxy(schema, baseOffset, contiguous = sizeOf(schema)) {
45
+ function throwIfLoose(schema) {
46
+ if (isUnstruct(schema) || isDisarray(schema)) {
47
+ throw new Error(`memoryLayoutOf: loose schemas are not supported (encountered '${schema.type}').`);
48
+ }
49
+ }
50
+ function makeProxy(schema, baseOffset, contiguous) {
30
51
  const unwrapped = undecorate(schema);
31
- const vecComponentCount = isVec(unwrapped) ? unwrapped.componentCount : undefined;
32
- if (vecComponentCount !== undefined) {
33
- const componentSize = sizeOf(unwrapped.primitive);
34
- return makeVecProxy(scalarNode(baseOffset, contiguous), componentSize, vecComponentCount);
52
+ throwIfLoose(unwrapped);
53
+ if (isVec(unwrapped)) {
54
+ return makeVecProxy(unwrapped, scalarNode(baseOffset, contiguous));
55
+ }
56
+ if (isMat(unwrapped)) {
57
+ return makeMatProxy(unwrapped, scalarNode(baseOffset, contiguous));
35
58
  }
36
59
  if (isWgslStruct(unwrapped)) {
37
60
  return makeStructProxy(unwrapped, scalarNode(baseOffset, contiguous));
@@ -42,100 +65,190 @@ function makeProxy(schema, baseOffset, contiguous = sizeOf(schema)) {
42
65
  return scalarNode(baseOffset, contiguous);
43
66
  }
44
67
  export function createOffsetProxy(schema, baseOffset = 0) {
45
- return makeProxy(schema, baseOffset, sizeOf(schema));
68
+ return makeProxy(schema, baseOffset, sizeOf(undecorate(schema)));
46
69
  }
47
- function makeVecProxy(target, componentSize, componentCount) {
48
- const baseOffset = target[OFFSET_MARKER];
49
- return new Proxy(target, {
70
+ const vecPropToIndex = {
71
+ x: 0,
72
+ y: 1,
73
+ z: 2,
74
+ w: 3,
75
+ r: 0,
76
+ g: 1,
77
+ b: 2,
78
+ a: 3,
79
+ '0': 0,
80
+ '1': 1,
81
+ '2': 2,
82
+ '3': 3,
83
+ };
84
+ function makeVecProxy(vec, parent) {
85
+ const baseOffset = parent[OFFSET_MARKER];
86
+ const componentCount = vec.componentCount;
87
+ const componentSize = sizeOf(vec.primitive);
88
+ return new Proxy(parent, {
50
89
  get(t, prop) {
51
90
  const marker = getMarker(t, prop);
52
91
  if (marker !== undefined) {
53
92
  return marker;
54
93
  }
55
- const idx = prop === 'x' || prop === '0'
56
- ? 0
57
- : prop === 'y' || prop === '1'
58
- ? 1
59
- : prop === 'z' || prop === '2'
60
- ? 2
61
- : prop === 'w' || prop === '3'
62
- ? 3
63
- : -1;
64
- if (idx < 0 || idx >= componentCount) {
94
+ if (typeof prop !== 'string') {
95
+ return undefined;
96
+ }
97
+ const index = vecPropToIndex[prop] ?? -1;
98
+ if (index < 0 || index >= componentCount) {
65
99
  return undefined;
66
100
  }
67
- const byteOffset = idx * componentSize;
101
+ const byteOffset = index * componentSize;
68
102
  const contiguous = Math.max(0, t[CONTIGUOUS_MARKER] - byteOffset);
69
103
  return scalarNode(baseOffset + byteOffset, contiguous);
70
104
  },
71
105
  });
72
106
  }
73
- function makeArrayProxy(array, target) {
107
+ function makeMatProxy(mat, parent) {
108
+ const [columnCount, columnSchema] = mat.type === 'mat2x2f' ? [2, vec2f] : mat.type === 'mat3x3f' ? [3, vec3f] : [4, vec4f];
109
+ const elementSize = 4;
110
+ const columnSize = columnCount * elementSize;
111
+ const columnStride = columnCount === 2 ? 8 : 16;
112
+ const hasPadding = columnCount === 3;
113
+ const remainingFromParent = parent[CONTIGUOUS_MARKER];
114
+ const ownProxy = !hasPadding
115
+ ? parent
116
+ : scalarNode(parent[OFFSET_MARKER], minContiguous(remainingFromParent, columnSize));
117
+ const columns = new Proxy(ownProxy, {
118
+ get(t, prop) {
119
+ const marker = getMarker(t, prop);
120
+ if (marker !== undefined) {
121
+ return marker;
122
+ }
123
+ if (typeof prop !== 'string') {
124
+ return undefined;
125
+ }
126
+ if (prop === 'length') {
127
+ return columnCount;
128
+ }
129
+ const idx = Number(prop);
130
+ if (!Number.isInteger(idx) || idx < 0 || idx >= columnCount) {
131
+ return undefined;
132
+ }
133
+ const columnOffset = idx * columnStride;
134
+ const contiguous = hasPadding ? columnSize : Math.max(0, remainingFromParent - columnOffset);
135
+ return makeVecProxy(columnSchema, scalarNode(parent[OFFSET_MARKER] + columnOffset, contiguous));
136
+ },
137
+ });
138
+ return new Proxy(ownProxy, {
139
+ get(t, prop) {
140
+ const marker = getMarker(t, prop);
141
+ if (marker !== undefined) {
142
+ return marker;
143
+ }
144
+ if (typeof prop !== 'string') {
145
+ return undefined;
146
+ }
147
+ if (prop === 'columns') {
148
+ return columns;
149
+ }
150
+ const index = Number(prop);
151
+ if (!Number.isInteger(index) ||
152
+ index < 0 ||
153
+ index * elementSize >= columnCount * columnStride) {
154
+ return undefined;
155
+ }
156
+ const byteOffset = elementSize * index;
157
+ if (byteOffset % columnStride === columnSize) {
158
+ return undefined;
159
+ }
160
+ const remaining = Math.max(0, remainingFromParent - byteOffset);
161
+ const columnEnd = Math.floor(byteOffset / columnStride) * columnStride + columnSize;
162
+ const localRemaining = columnEnd - byteOffset;
163
+ return scalarNode(t[OFFSET_MARKER] + byteOffset, hasPadding ? localRemaining : remaining);
164
+ },
165
+ });
166
+ }
167
+ function makeArrayProxy(array, parent) {
74
168
  const elementType = array.elementType;
75
169
  const elementSize = sizeOf(elementType);
76
170
  const stride = roundUp(elementSize, alignmentOf(elementType));
77
171
  const hasPadding = stride > elementSize;
78
- return new Proxy(target, {
172
+ const elementLCP = getLongestContiguousPrefix(elementType);
173
+ const remainingFromParent = parent[CONTIGUOUS_MARKER];
174
+ const ownProxy = isContiguous(array)
175
+ ? parent
176
+ : scalarNode(parent[OFFSET_MARKER], minContiguous(remainingFromParent, getLongestContiguousPrefix(array)));
177
+ return new Proxy(ownProxy, {
79
178
  get(t, prop) {
80
179
  const marker = getMarker(t, prop);
81
180
  if (marker !== undefined) {
82
181
  return marker;
83
182
  }
84
- if (prop === 'length') {
85
- return array.elementCount;
86
- }
87
183
  if (typeof prop !== 'string') {
88
184
  return undefined;
89
185
  }
186
+ if (prop === 'length') {
187
+ return array.elementCount;
188
+ }
90
189
  const index = Number(prop);
91
190
  if (!Number.isInteger(index) || index < 0 || index >= array.elementCount) {
92
191
  return undefined;
93
192
  }
94
193
  const elementOffset = index * stride;
95
- const remainingFromHere = !isContiguous(elementType)
96
- ? elementSize + getLongestContiguousPrefix(elementType) // it is too much, but we correct it later
97
- : Math.max(0, t[CONTIGUOUS_MARKER] - elementOffset);
98
- const childContiguous = hasPadding
99
- ? Math.min(remainingFromHere, elementSize)
100
- : remainingFromHere;
194
+ const remaining = Math.max(0, remainingFromParent - elementOffset);
195
+ let childContiguous;
196
+ if (hasPadding) {
197
+ childContiguous = minContiguous(remaining, elementSize);
198
+ }
199
+ else if (!isContiguous(elementType) && index < array.elementCount - 1) {
200
+ childContiguous = elementSize + elementLCP;
201
+ }
202
+ else {
203
+ childContiguous = remaining;
204
+ }
101
205
  return makeProxy(elementType, t[OFFSET_MARKER] + elementOffset, childContiguous);
102
206
  },
103
207
  });
104
208
  }
105
- function makeStructProxy(struct, target) {
209
+ function makeStructProxy(struct, parent) {
106
210
  const offsets = offsetsForProps(struct);
107
211
  const propTypes = struct.propTypes;
108
- const propNames = Object.keys(propTypes);
212
+ const props = Object.entries(propTypes);
213
+ const structSize = sizeOf(struct);
214
+ const remainingFromParent = parent[CONTIGUOUS_MARKER];
215
+ const ownProxy = isContiguous(struct)
216
+ ? parent
217
+ : scalarNode(parent[OFFSET_MARKER], minContiguous(remainingFromParent, getLongestContiguousPrefix(struct)));
109
218
  const meta = new Map();
110
- let runStart = 0;
111
- for (let i = 0; i < propNames.length; i++) {
112
- const name = propNames[i];
113
- if (!name) {
114
- continue;
115
- }
116
- const type = propTypes[name];
117
- if (!type) {
118
- continue;
119
- }
219
+ let nextPropLocalRemainingData = 0;
220
+ let nextPropRunReachesEnd = false;
221
+ for (let i = props.length - 1; i >= 0; i--) {
222
+ const [name, type] = props[i];
120
223
  const info = offsets[name];
121
- const padding = info.padding ?? 0;
122
- const typeContiguous = isContiguous(type);
123
- const isRunEnd = i === propNames.length - 1 || padding > 0 || !typeContiguous;
124
- if (!isRunEnd) {
125
- continue;
126
- }
127
- const runEnd = info.offset + (typeContiguous ? info.size : getLongestContiguousPrefix(type));
128
- for (let j = runStart; j <= i; j++) {
129
- const runName = propNames[j];
130
- if (!runName) {
131
- continue;
132
- }
133
- const runInfo = offsets[runName];
134
- meta.set(runName, { offset: runInfo.offset, runEnd });
224
+ const dataSize = sizeOf(undecorate(type));
225
+ const noGapAfter = (info.padding ?? 0) === 0 && sizeOf(type) === dataSize;
226
+ let dataAfter = 0;
227
+ let runReachesEnd = false;
228
+ if (noGapAfter) {
229
+ if (i === props.length - 1) {
230
+ runReachesEnd = true;
231
+ }
232
+ else {
233
+ const [, nextType] = props[i + 1];
234
+ if (isContiguous(nextType)) {
235
+ dataAfter = sizeOf(nextType) + nextPropLocalRemainingData;
236
+ runReachesEnd = nextPropRunReachesEnd;
237
+ }
238
+ else {
239
+ dataAfter = getLongestContiguousPrefix(nextType);
240
+ }
241
+ }
135
242
  }
136
- runStart = i + 1;
243
+ meta.set(name, {
244
+ offset: info.offset,
245
+ localRemaining: dataSize + dataAfter,
246
+ runReachesEnd,
247
+ });
248
+ nextPropLocalRemainingData = dataAfter;
249
+ nextPropRunReachesEnd = runReachesEnd;
137
250
  }
138
- return new Proxy(target, {
251
+ return new Proxy(ownProxy, {
139
252
  get(t, prop) {
140
253
  const marker = getMarker(t, prop);
141
254
  if (marker !== undefined) {
@@ -145,52 +258,17 @@ function makeStructProxy(struct, target) {
145
258
  return undefined;
146
259
  }
147
260
  const m = meta.get(prop);
148
- if (!m) {
149
- return undefined;
150
- }
151
- const remainingFromHere = Math.max(0, t[CONTIGUOUS_MARKER] - m.offset);
152
- const localLimit = Math.max(0, m.runEnd - m.offset);
153
261
  const propSchema = propTypes[prop];
154
- if (!propSchema) {
262
+ if (!m || !propSchema) {
155
263
  return undefined;
156
264
  }
157
- return makeProxy(propSchema, t[OFFSET_MARKER] + m.offset, sizeOf(struct) === m.runEnd ? remainingFromHere : localLimit);
265
+ const childContiguous = m.runReachesEnd
266
+ ? m.localRemaining + Math.max(0, remainingFromParent - structSize)
267
+ : m.localRemaining;
268
+ return makeProxy(propSchema, t[OFFSET_MARKER] + m.offset, childContiguous);
158
269
  },
159
270
  });
160
271
  }
161
- function getRootContiguous(schema) {
162
- const unwrapped = undecorate(schema);
163
- if (isWgslStruct(unwrapped)) {
164
- const offsets = offsetsForProps(unwrapped);
165
- const propTypes = unwrapped.propTypes;
166
- const propNames = Object.keys(propTypes);
167
- for (let i = 0; i < propNames.length; i++) {
168
- const name = propNames[i];
169
- if (!name) {
170
- continue;
171
- }
172
- const info = offsets[name];
173
- const padding = info.padding ?? 0;
174
- const runEnd = info.offset + info.size;
175
- const isRunEnd = i === propNames.length - 1 || padding > 0;
176
- if (isRunEnd) {
177
- return runEnd;
178
- }
179
- }
180
- return 0;
181
- }
182
- if (isWgslArray(unwrapped)) {
183
- const elementType = unwrapped.elementType;
184
- const elementSize = sizeOf(elementType);
185
- const stride = roundUp(elementSize, alignmentOf(elementType));
186
- const totalSize = sizeOf(schema);
187
- if (!Number.isFinite(totalSize)) {
188
- return elementSize;
189
- }
190
- return stride > elementSize ? elementSize : totalSize;
191
- }
192
- return sizeOf(schema);
193
- }
194
272
  /**
195
273
  * A function that retrieves offset and information for a specific primitive within a data schema.
196
274
  * Example usage:
@@ -209,10 +287,11 @@ function getRootContiguous(schema) {
209
287
  * @returns An object containing the offset and contiguous byte information.
210
288
  */
211
289
  export function memoryLayoutOf(schema, accessor) {
290
+ throwIfLoose(undecorate(schema));
212
291
  if (!accessor) {
213
292
  return {
214
293
  offset: 0,
215
- contiguous: getRootContiguous(schema),
294
+ contiguous: getLongestContiguousPrefix(schema),
216
295
  };
217
296
  }
218
297
  const proxy = createOffsetProxy(schema);
package/data/ref.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { $internal, $ownSnippet, $resolve } from '../shared/symbols.ts';
2
- import type { DualFn, SelfResolvable } from '../types.ts';
1
+ import { $comptimeValueOf, $internal, $ownSnippet, $resolve } from '../shared/symbols.ts';
2
+ import type { ComptimeValue, DualFn, SelfResolvable, WithComptimeValue } from '../types.ts';
3
3
  import { type ResolvedSnippet, type Snippet } from './snippet.ts';
4
4
  import { type Ptr } from './wgslTypes.ts';
5
5
  interface ref<T> {
@@ -38,12 +38,19 @@ export declare function INTERNAL_createRef<T>(value: T): ref<T>;
38
38
  * generating shader code can check if the value of a snippet is
39
39
  * an instance of `RefOperator`, and act accordingly.
40
40
  */
41
- export declare class RefOperator implements SelfResolvable {
42
- #private;
41
+ export declare class RefOperator implements SelfResolvable, WithComptimeValue {
43
42
  readonly [$internal]: true;
44
43
  readonly snippet: Snippet;
45
- constructor(snippet: Snippet, ptrType: Ptr | undefined);
44
+ readonly ptrType: Ptr;
45
+ constructor(snippet: Snippet, ptrType: Ptr);
46
+ /**
47
+ * Whether `snippet` refers to an existing value in memory. If not (e.g. `d.ref(42)`),
48
+ * the ref has to be stored in a variable (`const life = d.ref(42)`) before it can be used.
49
+ */
50
+ get addressable(): boolean;
46
51
  get [$ownSnippet](): Snippet;
52
+ toString(): string;
53
+ [$comptimeValueOf](): ComptimeValue;
47
54
  [$resolve](): ResolvedSnippet;
48
55
  }
49
56
  export declare function derefSnippet(snippet: Snippet): Snippet;
package/data/ref.js CHANGED
@@ -1,11 +1,13 @@
1
1
  import { stitch } from "../core/resolve/stitch.js";
2
+ import { concretize } from "../tgsl/generationHelpers.js";
3
+ import { stringifySnippet } from "../tgsl/stringifySnippet.js";
2
4
  import { WgslTypeError } from "../errors.js";
3
5
  import { setName } from "../shared/meta.js";
4
- import { $gpuCallable, $internal, $ownSnippet, $resolve } from "../shared/symbols.js";
6
+ import { $comptimeValueOf, $gpuCallable, $internal, $ownSnippet, $resolve, } from "../shared/symbols.js";
5
7
  import { UnknownData } from "./dataTypes.js";
6
- import { createPtrFromOrigin, explicitFrom } from "./ptr.js";
7
- import { isStoredInMemory, snip, withDataType, } from "./snippet.js";
8
- import { isNaturallyEphemeral, isPtr } from "./wgslTypes.js";
8
+ import { createPtrFromOrigin, explicitFrom, ptrFn } from "./ptr.js";
9
+ import { isAddressableOrigin, isStoredInMemory, snip, withDataType, } from "./snippet.js";
10
+ import { isNaturallyEphemeral, isPtr, isVoid } from "./wgslTypes.js";
9
11
  export const _ref = (() => {
10
12
  const impl = ((value) => INTERNAL_createRef(value));
11
13
  setName(impl, 'ref');
@@ -14,20 +16,20 @@ export const _ref = (() => {
14
16
  impl[$gpuCallable] = {
15
17
  call(ctx, [value]) {
16
18
  if (value.origin === 'argument') {
17
- throw new WgslTypeError(stitch `d.ref(${value}) is illegal, cannot take a reference of an argument. Copy the value first, and take a reference of the copy.`);
19
+ throw new WgslTypeError(`d.ref(${stringifySnippet(value)}) is illegal, cannot take a reference of an argument. Copy the value first, and take a reference of the copy.`);
18
20
  }
19
21
  if (value.origin === 'constant-immutable-def' || value.origin === 'runtime-immutable-def') {
20
22
  const typeStr = ctx.resolve(value.dataType).value;
21
- throw new WgslTypeError(stitch `d.ref(${value}) is illegal, cannot take a reference to a constant.
23
+ throw new WgslTypeError(`d.ref(${stringifySnippet(value)}) is illegal, cannot take a reference to a constant.
22
24
  -----
23
- - Try 'd.ref(${typeStr}(${value}));' instead to create a new referencable value.
25
+ - Try 'd.ref(${typeStr}(${stringifySnippet(value)}));' instead to create a new referencable value.
24
26
  -----`);
25
27
  }
26
28
  if (isStoredInMemory(value) && isNaturallyEphemeral(value.dataType)) {
27
29
  const typeStr = ctx.resolve(value.dataType).value;
28
- throw new WgslTypeError(stitch `d.ref(${value}) is illegal, cannot take a reference to a scalar value.
30
+ throw new WgslTypeError(`d.ref(${stringifySnippet(value)}) is illegal, cannot take a reference to a scalar value.
29
31
  -----
30
- - Try 'd.ref(${typeStr}(${value}));' instead to create a new referencable scalar.
32
+ - Try 'd.ref(${typeStr}(${stringifySnippet(value)}));' instead to create a new referencable scalar.
31
33
  -----`);
32
34
  }
33
35
  if (isPtr(value.dataType)) {
@@ -35,17 +37,23 @@ export const _ref = (() => {
35
37
  // made by assigning a reference to a `const`.
36
38
  return withDataType(explicitFrom(value.dataType), value);
37
39
  }
40
+ if (value.dataType === UnknownData || isVoid(value.dataType)) {
41
+ throw new WgslTypeError(`d.ref(${stringifySnippet(value)}) is illegal, cannot determine the WGSL type of '${stringifySnippet(value)}'.`);
42
+ }
38
43
  /**
39
- * Pointer type only exists if the ref was created from a reference (buttery-butter).
44
+ * The value is addressable only if the ref was created from a reference (buttery-butter).
40
45
  *
41
46
  * @example
42
47
  * ```ts
43
- * const life = ref(42); // created from a value
48
+ * const life = ref(42); // created from a value, has to be stored in a variable
44
49
  * const boid = ref(layout.$.boids[0]); // created from a reference
45
50
  * ```
46
51
  */
47
- const ptrType = createPtrFromOrigin(value.origin, value.dataType);
48
- return snip(new RefOperator(value, ptrType), ptrType ?? UnknownData,
52
+ const ptrType = createPtrFromOrigin(value.origin, value.dataType) ??
53
+ // Values that are not addressable become addressable once stored in a
54
+ // function-scope variable (`const life = d.ref(42)`), so that's the type they get.
55
+ ptrFn(concretize(value.dataType));
56
+ return snip(new RefOperator(value, ptrType), ptrType,
49
57
  /* origin */ 'runtime', value.possibleSideEffects);
50
58
  },
51
59
  };
@@ -108,23 +116,38 @@ export function INTERNAL_createRef(value) {
108
116
  export class RefOperator {
109
117
  [$internal];
110
118
  snippet;
111
- #ptrType;
119
+ ptrType;
112
120
  constructor(snippet, ptrType) {
113
121
  this[$internal] = true;
114
122
  this.snippet = snippet;
115
- this.#ptrType = ptrType;
123
+ this.ptrType = ptrType;
124
+ }
125
+ /**
126
+ * Whether `snippet` refers to an existing value in memory. If not (e.g. `d.ref(42)`),
127
+ * the ref has to be stored in a variable (`const life = d.ref(42)`) before it can be used.
128
+ */
129
+ get addressable() {
130
+ return isAddressableOrigin(this.snippet.origin);
116
131
  }
117
132
  get [$ownSnippet]() {
118
- if (!this.#ptrType) {
119
- throw new Error(stitch `Cannot take a reference of ${this.snippet}`);
120
- }
121
- return snip(this, this.#ptrType, this.snippet.origin, this.snippet.possibleSideEffects);
133
+ return snip(this, this.ptrType, this.snippet.origin, this.snippet.possibleSideEffects);
134
+ }
135
+ toString() {
136
+ return `d.ref(${stringifySnippet(this.snippet)})`;
137
+ }
138
+ [$comptimeValueOf]() {
139
+ // References only exist in the generated shader code
140
+ return { comptime: false };
122
141
  }
123
142
  [$resolve]() {
124
- if (!this.#ptrType) {
125
- throw new Error(stitch `Cannot take a reference of ${this.snippet}`);
143
+ if (!this.addressable) {
144
+ const valueStr = stringifySnippet(this.snippet);
145
+ throw new WgslTypeError(`d.ref(${valueStr}) has to be stored in a variable before use, since '${valueStr}' is not an existing value that can be referenced.
146
+ -----
147
+ - Try 'const ref = d.ref(${valueStr});', and use 'ref' instead.
148
+ -----`);
126
149
  }
127
- return snip(stitch `(&${this.snippet})`, this.#ptrType, this.snippet.origin, this.snippet.possibleSideEffects);
150
+ return snip(stitch `(&${this.snippet})`, this.ptrType, this.snippet.origin, this.snippet.possibleSideEffects);
128
151
  }
129
152
  }
130
153
  export function derefSnippet(snippet) {
@@ -133,7 +156,11 @@ export function derefSnippet(snippet) {
133
156
  }
134
157
  const innerType = snippet.dataType.inner;
135
158
  if (snippet.value instanceof RefOperator) {
136
- return snip(stitch `${snippet.value.snippet}`, innerType, snippet.origin, snippet.possibleSideEffects);
159
+ // Dereferencing gives back the value the ref was created from, along with its origin.
160
+ // For non-addressable refs (e.g. `d.ref(1)`), that makes the result a non-reference,
161
+ // which in turn disallows mutating it (e.g. `d.ref(1).$ = 2`).
162
+ const inner = snippet.value.snippet;
163
+ return snip(stitch `${inner}`, innerType, inner.origin, snippet.possibleSideEffects);
137
164
  }
138
165
  return snip(stitch `(*${snippet})`, innerType, snippet.origin, snippet.possibleSideEffects);
139
166
  }
package/data/snippet.d.ts CHANGED
@@ -62,7 +62,14 @@ export declare const originToPtrParams: {
62
62
  };
63
63
  };
64
64
  export type OriginToPtrParams = typeof originToPtrParams;
65
+ /**
66
+ * Whether values of this origin can have their address taken.
67
+ */
65
68
  export declare function isAddressableOrigin(origin: Origin): origin is keyof OriginToPtrParams;
69
+ /**
70
+ * Whether values of this origin can be mutated.
71
+ */
72
+ export declare function isMutableOrigin(origin: Origin): boolean;
66
73
  export interface Snippet {
67
74
  readonly value: unknown;
68
75
  /**