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.
- package/common/writeSoA.js +16 -0
- package/core/buffer/buffer.js +47 -11
- package/core/commandEncoder/attachments.js +5 -2
- package/core/function/autoIO.js +5 -3
- package/core/function/comptime.js +7 -5
- package/core/function/createCallableSchema.js +4 -3
- package/core/function/dualImpl.js +20 -19
- package/core/function/ioSchema.d.ts +17 -2
- package/core/function/ioSchema.js +74 -27
- package/core/function/tgpuFn.d.ts +11 -1
- package/core/function/tgpuFn.js +9 -2
- package/core/function/tgpuFragmentFn.d.ts +1 -1
- package/core/function/tgpuFragmentFn.js +2 -1
- package/core/function/tgpuVertexFn.js +2 -1
- package/core/pipeline/connectAttachmentToShader.d.ts +2 -2
- package/core/pipeline/connectAttachmentToShader.js +16 -21
- package/core/pipeline/connectTargetsToShader.d.ts +5 -1
- package/core/pipeline/connectTargetsToShader.js +23 -18
- package/core/pipeline/renderPipeline.d.ts +9 -6
- package/core/pipeline/renderPipeline.js +22 -22
- package/core/pipeline/timeable.js +15 -2
- package/core/resolve/namespace.js +2 -2
- package/core/root/configurableImpl.js +1 -2
- package/core/root/rootTypes.d.ts +6 -2
- package/core/slot/accessor.d.ts +1 -1
- package/core/slot/accessor.js +72 -5
- package/core/texture/externalTexture.d.ts +1 -1
- package/core/texture/externalTexture.js +3 -1
- package/core/texture/texture.d.ts +1 -1
- package/core/texture/texture.js +12 -5
- package/core/unroll/tgpuUnroll.d.ts +4 -3
- package/core/unroll/tgpuUnroll.js +7 -1
- package/core/valueProxyUtils.d.ts +4 -2
- package/core/valueProxyUtils.js +4 -2
- package/data/autoStruct.d.ts +1 -1
- package/data/autoStruct.js +8 -4
- package/data/compiledIO.js +12 -9
- package/data/dataIO.d.ts +10 -6
- package/data/dataIO.js +19 -4
- package/data/dataTypes.d.ts +0 -5
- package/data/dataTypes.js +0 -6
- package/data/deepEqual.js +19 -9
- package/data/matrix.js +10 -3
- package/data/matrixColumnsAccess.d.ts +8 -0
- package/data/matrixColumnsAccess.js +15 -0
- package/data/offsetUtils.js +181 -102
- package/data/partialIO.d.ts +8 -1
- package/data/partialIO.js +36 -19
- package/data/ref.d.ts +12 -5
- package/data/ref.js +51 -24
- package/data/snippet.d.ts +13 -4
- package/data/snippet.js +17 -4
- package/data/struct.js +3 -0
- package/data/wgslTypes.d.ts +6 -3
- package/internal.d.ts +1 -0
- package/nameUtils.d.ts +7 -0
- package/nameUtils.js +60 -0
- package/package.json +1 -1
- package/resolutionCtx.js +1 -1
- package/shared/meta.js +1 -1
- package/shared/symbols.d.ts +5 -0
- package/shared/symbols.js +6 -1
- package/shared/tseynit.js +54 -50
- package/std/boolean.js +9 -10
- package/std/numeric.js +1 -4
- package/std/operators.js +9 -4
- package/std/packing.js +10 -10
- package/std/texture.d.ts +2 -2
- package/tgpuLogger.d.ts +1 -1
- package/tgpuLogger.js +0 -2
- package/tgsl/accessIndex.js +37 -38
- package/tgsl/accessProp.js +10 -7
- package/tgsl/conversion.js +3 -2
- package/tgsl/forOfUtils.js +8 -8
- package/tgsl/generationHelpers.d.ts +4 -3
- package/tgsl/generationHelpers.js +18 -5
- package/tgsl/infixDispatch.d.ts +4 -1
- package/tgsl/infixDispatch.js +13 -1
- package/tgsl/makeDereferenceable.d.ts +1 -1
- package/tgsl/makeDereferenceable.js +3 -1
- package/tgsl/shellless.js +5 -9
- package/tgsl/stringifySnippet.d.ts +9 -0
- package/tgsl/stringifySnippet.js +24 -0
- package/tgsl/wgslGenerator.d.ts +1 -0
- package/tgsl/wgslGenerator.js +180 -86
- package/types.d.ts +23 -2
- package/types.js +30 -5
package/data/offsetUtils.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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,
|
|
209
|
+
function makeStructProxy(struct, parent) {
|
|
106
210
|
const offsets = offsetsForProps(struct);
|
|
107
211
|
const propTypes = struct.propTypes;
|
|
108
|
-
const
|
|
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
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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:
|
|
294
|
+
contiguous: getLongestContiguousPrefix(schema),
|
|
216
295
|
};
|
|
217
296
|
}
|
|
218
297
|
const proxy = createOffsetProxy(schema);
|
package/data/partialIO.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
92
|
-
compiledWriter
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
|
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.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
|
-
|
|
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 {
|
|
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(
|
|
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(
|
|
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
|
-
if (
|
|
28
|
+
if (isStoredInMemory(value) && isNaturallyEphemeral(value.dataType)) {
|
|
27
29
|
const typeStr = ctx.resolve(value.dataType).value;
|
|
28
|
-
throw new WgslTypeError(
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
119
|
+
ptrType;
|
|
112
120
|
constructor(snippet, ptrType) {
|
|
113
121
|
this[$internal] = true;
|
|
114
122
|
this.snippet = snippet;
|
|
115
|
-
this
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return
|
|
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
|
|
125
|
-
|
|
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
|
|
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
|
-
|
|
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
|
}
|