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.
- 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 +4 -2
- package/core/function/ioSchema.d.ts +7 -2
- package/core/function/ioSchema.js +41 -15
- 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 +5 -4
- package/core/pipeline/renderPipeline.js +10 -12
- package/core/resolve/namespace.js +2 -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/texture.js +8 -4
- package/data/autoStruct.d.ts +1 -1
- package/data/autoStruct.js +8 -4
- package/data/deepEqual.js +19 -9
- package/data/matrix.js +10 -3
- package/data/partialIO.d.ts +8 -1
- package/data/partialIO.js +36 -19
- package/data/ref.js +2 -2
- package/data/snippet.d.ts +6 -4
- package/data/snippet.js +8 -4
- package/data/wgslTypes.d.ts +3 -0
- package/internal.d.ts +1 -0
- package/nameUtils.d.ts +7 -0
- package/nameUtils.js +60 -0
- package/package.json +1 -1
- package/shared/meta.js +1 -1
- package/shared/symbols.js +1 -1
- package/shared/tseynit.js +54 -50
- package/std/boolean.js +9 -10
- package/std/operators.js +9 -4
- package/std/texture.d.ts +2 -2
- package/tgsl/accessIndex.js +19 -23
- package/tgsl/forOfUtils.js +6 -6
- package/tgsl/generationHelpers.js +2 -2
- package/tgsl/wgslGenerator.js +85 -23
package/common/writeSoA.js
CHANGED
|
@@ -5,6 +5,7 @@ import { undecorate } from "../data/dataTypes.js";
|
|
|
5
5
|
import { offsetsForProps } from "../data/offsets.js";
|
|
6
6
|
import { sizeOf } from "../data/sizeOf.js";
|
|
7
7
|
import { isAtomic, isMat, isMat2x2f, isMat3x3f, isWgslArray } from "../data/wgslTypes.js";
|
|
8
|
+
import { getName } from "../shared/meta.js";
|
|
8
9
|
function packedSchemaOf(schema) {
|
|
9
10
|
const unpackedSchema = undecorate(schema);
|
|
10
11
|
return isAtomic(unpackedSchema) ? unpackedSchema.inner : unpackedSchema;
|
|
@@ -92,6 +93,17 @@ function scatterSoA(target, arraySchema, soaData, startOffset, endOffset) {
|
|
|
92
93
|
}
|
|
93
94
|
}
|
|
94
95
|
}
|
|
96
|
+
// Mirrors the check in `buffer.write()`. That check only runs after the data has been
|
|
97
|
+
// scattered into `buffer.arrayBuffer`, so we check up front to leave it untouched on failure.
|
|
98
|
+
function assertAlignedWrite(buffer, startOffset, endOffset) {
|
|
99
|
+
// Writes that reach the end of the schema also cover the padding up to a multiple of 4
|
|
100
|
+
const paddedEndOffset = endOffset === sizeOf(buffer.dataType) ? roundUp(endOffset, 4) : endOffset;
|
|
101
|
+
if (startOffset % 4 !== 0 || (paddedEndOffset - startOffset) % 4 !== 0) {
|
|
102
|
+
throw new Error(`Cannot write to bytes ${startOffset}-${endOffset} of buffer '${getName(buffer) ?? '<unnamed>'}'. ` +
|
|
103
|
+
'WebGPU requires writes to start and end at a multiple of 4 bytes. ' +
|
|
104
|
+
'Align the range to 4 bytes, write the whole buffer, or use 4-byte elements (e.g. d.u32 instead of d.u16).');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
95
107
|
export function writeSoA(buffer, data, options) {
|
|
96
108
|
const arrayBuffer = buffer.arrayBuffer;
|
|
97
109
|
const startOffset = options?.startOffset ?? 0;
|
|
@@ -99,6 +111,10 @@ export function writeSoA(buffer, data, options) {
|
|
|
99
111
|
const naturalSize = computeSoAByteLength(buffer.dataType, data);
|
|
100
112
|
const endOffset = options?.endOffset ??
|
|
101
113
|
(naturalSize === undefined ? bufferSize : Math.min(startOffset + naturalSize, bufferSize));
|
|
114
|
+
// Writes to mapped memory have no alignment rules
|
|
115
|
+
if (buffer.buffer.mapState !== 'mapped') {
|
|
116
|
+
assertAlignedWrite(buffer, startOffset, endOffset);
|
|
117
|
+
}
|
|
102
118
|
scatterSoA(new Uint8Array(arrayBuffer), buffer.dataType, data, startOffset, endOffset);
|
|
103
119
|
buffer.write(arrayBuffer, { startOffset, endOffset });
|
|
104
120
|
}
|
package/core/buffer/buffer.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getCompiledWriter } from "../../data/compiledIO.js";
|
|
2
2
|
import { convertPartialToPatch, getPatchInstructions } from "../../data/partialIO.js";
|
|
3
3
|
import { sizeOf } from "../../data/sizeOf.js";
|
|
4
|
+
import { roundUp } from "../../mathUtils.js";
|
|
4
5
|
import { isWgslData } from "../../data/wgslTypes.js";
|
|
5
6
|
import { getName, setName } from "../../shared/meta.js";
|
|
6
7
|
import { $internal, $soul } from "../../shared/symbols.js";
|
|
@@ -10,6 +11,14 @@ import { patchArrayBuffer } from "../../data/partialIO.js";
|
|
|
10
11
|
import { mutable, readonly, uniform, } from "./bufferBinding.js";
|
|
11
12
|
import { warnIfNotUniformAligned } from "../pipeline/webgpuLimitations.js";
|
|
12
13
|
const usageToUsageConstructor = { uniform, mutable, readonly };
|
|
14
|
+
/**
|
|
15
|
+
* WebGPU requires buffer sizes, writes, copies and mappings
|
|
16
|
+
* to be multiples of 4 bytes. Schemas like `arrayOf(u16, 3)` can have a smaller logical
|
|
17
|
+
* size, so the underlying allocation gets padded up to the next multiple of 4.
|
|
18
|
+
*/
|
|
19
|
+
function physicalSizeOf(schema) {
|
|
20
|
+
return roundUp(sizeOf(schema), 4);
|
|
21
|
+
}
|
|
13
22
|
export function INTERNAL_createBuffer(group, typeSchema, initialOrBuffer) {
|
|
14
23
|
if (!isWgslData(typeSchema)) {
|
|
15
24
|
return new TgpuBufferImpl(group, typeSchema, initialOrBuffer, ['storage', 'uniform']);
|
|
@@ -32,7 +41,7 @@ class TgpuBufferImpl {
|
|
|
32
41
|
#destroyed = false;
|
|
33
42
|
#internalBuffer;
|
|
34
43
|
get #hostBuffer() {
|
|
35
|
-
return (this.#internalBuffer ??= new ArrayBuffer(
|
|
44
|
+
return (this.#internalBuffer ??= new ArrayBuffer(physicalSizeOf(this.dataType)));
|
|
36
45
|
}
|
|
37
46
|
#mappedRange;
|
|
38
47
|
#initialCallback;
|
|
@@ -76,7 +85,7 @@ class TgpuBufferImpl {
|
|
|
76
85
|
const soul = this[$soul];
|
|
77
86
|
if (!soul.raw) {
|
|
78
87
|
soul.raw = soul.device.createBuffer({
|
|
79
|
-
size:
|
|
88
|
+
size: physicalSizeOf(soul.dataType),
|
|
80
89
|
usage: soul.flags,
|
|
81
90
|
mappedAtCreation: !!this.initial || !!this.#initialCallback,
|
|
82
91
|
label: getName(this) ?? '<unnamed>',
|
|
@@ -195,15 +204,31 @@ class TgpuBufferImpl {
|
|
|
195
204
|
writeToArrayBuffer(mapped, this.dataType, data, options);
|
|
196
205
|
return;
|
|
197
206
|
}
|
|
207
|
+
const { startOffset, endOffset } = calculateOffsets(options, this.dataType, data);
|
|
208
|
+
const size = this.#alignedWriteSize(startOffset, endOffset);
|
|
198
209
|
// If the caller already wrote directly into #hostBuffer via
|
|
199
210
|
// arrayBuffer, skip the redundant copy, the data is already in place.
|
|
200
211
|
if (!(data instanceof ArrayBuffer && data === this.#hostBuffer)) {
|
|
201
212
|
writeToArrayBuffer(this.#hostBuffer, this.dataType, data, options);
|
|
202
213
|
}
|
|
203
|
-
const { startOffset, endOffset } = calculateOffsets(options, this.dataType, data);
|
|
204
|
-
const size = endOffset - startOffset;
|
|
205
214
|
this[$soul].device.queue.writeBuffer(gpuBuffer, startOffset, this.#hostBuffer, startOffset, size);
|
|
206
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Returns the number of bytes to upload when writing to `[startOffset, endOffset)`.
|
|
218
|
+
* Writes that reach the end of the schema also cover the trailing padding (see {@link physicalSizeOf}).
|
|
219
|
+
* Other writes have to be 4-byte aligned already, since filling in the neighboring bytes
|
|
220
|
+
* from the host could overwrite data that was changed on the GPU.
|
|
221
|
+
*/
|
|
222
|
+
#alignedWriteSize(startOffset, endOffset) {
|
|
223
|
+
const paddedEndOffset = endOffset === sizeOf(this.dataType) ? physicalSizeOf(this.dataType) : endOffset;
|
|
224
|
+
const size = paddedEndOffset - startOffset;
|
|
225
|
+
if (startOffset % 4 !== 0 || size % 4 !== 0) {
|
|
226
|
+
throw new Error(`Cannot write to bytes ${startOffset}-${endOffset} of buffer '${getName(this) ?? '<unnamed>'}'. ` +
|
|
227
|
+
'WebGPU requires writes to start and end at a multiple of 4 bytes. ' +
|
|
228
|
+
'Align the range to 4 bytes, write the whole buffer, or use 4-byte elements (e.g. d.u32 instead of d.u16).');
|
|
229
|
+
}
|
|
230
|
+
return size;
|
|
231
|
+
}
|
|
207
232
|
/** @deprecated Use {@link patch} instead. */
|
|
208
233
|
writePartial(data) {
|
|
209
234
|
this.patch(convertPartialToPatch(this.dataType, data));
|
|
@@ -214,9 +239,19 @@ class TgpuBufferImpl {
|
|
|
214
239
|
patchArrayBuffer(this.#getMappedRange(), this.dataType, data);
|
|
215
240
|
}
|
|
216
241
|
else {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
242
|
+
// Every range is validated before the patch touches #hostBuffer, so a failing patch
|
|
243
|
+
// is neither uploaded halfway nor left behind in `arrayBuffer`
|
|
244
|
+
const instructions = getPatchInstructions(this.dataType, data, this.#hostBuffer, (start, end) => this.#alignedWriteSize(start, end));
|
|
245
|
+
for (const { data, gpuOffset, uploadSize } of instructions) {
|
|
246
|
+
if (uploadSize === data.byteLength) {
|
|
247
|
+
this[$soul].device.queue.writeBuffer(gpuBuffer, gpuOffset, data);
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
// Extending the write into the trailing padding (see physicalSizeOf)
|
|
251
|
+
const padded = new Uint8Array(uploadSize);
|
|
252
|
+
padded.set(data);
|
|
253
|
+
this[$soul].device.queue.writeBuffer(gpuBuffer, gpuOffset, padded);
|
|
254
|
+
}
|
|
220
255
|
}
|
|
221
256
|
}
|
|
222
257
|
}
|
|
@@ -238,7 +273,7 @@ class TgpuBufferImpl {
|
|
|
238
273
|
if (this.buffer.mapState === 'mapped') {
|
|
239
274
|
throw new Error('Cannot copy to a mapped buffer.');
|
|
240
275
|
}
|
|
241
|
-
const size =
|
|
276
|
+
const size = physicalSizeOf(this.dataType);
|
|
242
277
|
if (encoder) {
|
|
243
278
|
encoder[$internal].rawEncoder.copyBufferToBuffer(srcBuffer.buffer, 0, this.buffer, 0, size);
|
|
244
279
|
return;
|
|
@@ -258,14 +293,15 @@ class TgpuBufferImpl {
|
|
|
258
293
|
this.#unmapBuffer();
|
|
259
294
|
return res;
|
|
260
295
|
}
|
|
296
|
+
const size = physicalSizeOf(this.dataType);
|
|
261
297
|
const stagingBuffer = this[$soul].device.createBuffer({
|
|
262
|
-
size
|
|
298
|
+
size,
|
|
263
299
|
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
|
|
264
300
|
});
|
|
265
301
|
const commandEncoder = this[$soul].device.createCommandEncoder();
|
|
266
|
-
commandEncoder.copyBufferToBuffer(gpuBuffer, 0, stagingBuffer, 0,
|
|
302
|
+
commandEncoder.copyBufferToBuffer(gpuBuffer, 0, stagingBuffer, 0, size);
|
|
267
303
|
this[$soul].device.queue.submit([commandEncoder.finish()]);
|
|
268
|
-
await stagingBuffer.mapAsync(GPUMapMode.READ, 0,
|
|
304
|
+
await stagingBuffer.mapAsync(GPUMapMode.READ, 0, size);
|
|
269
305
|
const res = readFromArrayBuffer(stagingBuffer.getMappedRange(), this.dataType);
|
|
270
306
|
stagingBuffer.unmap();
|
|
271
307
|
stagingBuffer.destroy();
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
+
import { WeakMemo } from "../../memo.js";
|
|
1
2
|
import { $internal } from "../../shared/symbols.js";
|
|
2
3
|
import { isQuerySet } from "../querySet/querySet.js";
|
|
3
4
|
import { isGPUCanvasContext } from "../pipeline/typeGuards.js";
|
|
4
5
|
import { isTexture, isTextureView, } from "../texture/texture.js";
|
|
6
|
+
// Canvas contexts return the same texture within a frame, so its identity is enough to cache views
|
|
7
|
+
const defaultAttachmentViews = new WeakMemo((texture) => texture.createView());
|
|
5
8
|
export function unwrapAttachmentView(root, view) {
|
|
6
9
|
if (isTexture(view)) {
|
|
7
|
-
return root.unwrap(view)
|
|
10
|
+
return defaultAttachmentViews.getOrMake(root.unwrap(view));
|
|
8
11
|
}
|
|
9
12
|
if (isTextureView(view)) {
|
|
10
13
|
return root.unwrap(view);
|
|
11
14
|
}
|
|
12
15
|
if (isGPUCanvasContext(view)) {
|
|
13
|
-
return view.getCurrentTexture()
|
|
16
|
+
return defaultAttachmentViews.getOrMake(view.getCurrentTexture());
|
|
14
17
|
}
|
|
15
18
|
return view;
|
|
16
19
|
}
|
package/core/function/autoIO.js
CHANGED
|
@@ -39,7 +39,8 @@ export class AutoFragmentFn {
|
|
|
39
39
|
setName(impl, 'fragmentFn');
|
|
40
40
|
}
|
|
41
41
|
this.#core = createFnCore(impl, 'fragment');
|
|
42
|
-
this.autoIn = new AutoStruct({ ...builtinFragmentIn, ...varyings }, undefined, locations
|
|
42
|
+
this.autoIn = new AutoStruct({ ...builtinFragmentIn, ...varyings }, undefined, locations,
|
|
43
|
+
/* autoInterpolateIntegers */ true);
|
|
43
44
|
setName(this.autoIn, 'FragmentIn');
|
|
44
45
|
this.autoOut = new AutoStruct(builtinFragmentOut, vec4f);
|
|
45
46
|
setName(this.autoOut, 'FragmentOut');
|
|
@@ -68,7 +69,8 @@ export class AutoVertexFn {
|
|
|
68
69
|
this.#core = createFnCore(impl, 'vertex');
|
|
69
70
|
this.autoIn = new AutoStruct({ ...builtinVertexIn, ...attribs }, undefined, locations);
|
|
70
71
|
setName(this.autoIn, 'VertexIn');
|
|
71
|
-
this.autoOut = new AutoStruct(builtinVertexOut, undefined
|
|
72
|
+
this.autoOut = new AutoStruct(builtinVertexOut, undefined, undefined,
|
|
73
|
+
/* autoInterpolateIntegers */ true);
|
|
72
74
|
setName(this.autoOut, 'VertexOut');
|
|
73
75
|
}
|
|
74
76
|
toString() {
|
|
@@ -7,7 +7,12 @@ export type WithLocations<T extends Record<string, BaseData>> = {
|
|
|
7
7
|
export type IOLayoutToSchema<T> = T extends BaseData ? HasCustomLocation<T> extends true ? T : Decorate<T, Location<0>> : T extends Record<string, BaseData> ? WgslStruct<WithLocations<T>> : T extends {
|
|
8
8
|
type: 'void';
|
|
9
9
|
} ? void : never;
|
|
10
|
-
export declare function withLocations<T extends BaseData>(members: Record<string, T> | undefined, locations?: Record<string, number
|
|
10
|
+
export declare function withLocations<T extends BaseData>(members: Record<string, T> | undefined, locations?: Record<string, number>, autoInterpolateIntegers?: boolean): Record<string, BaseData>;
|
|
11
11
|
export declare function separateBuiltins(schema: Record<string, BaseData>, locations?: Record<string, number>): SeparatedEntryArgs;
|
|
12
12
|
export declare function separateAllAsPositional(schema: Record<string, BaseData>): SeparatedEntryArgs;
|
|
13
|
-
export declare function createIoSchema<T extends BaseData | Record<string, BaseData>>(layout: T, locations?: Record<string, number
|
|
13
|
+
export declare function createIoSchema<T extends BaseData | Record<string, BaseData>>(layout: T, locations?: Record<string, number>, autoInterpolateIntegers?: boolean): IOLayoutToSchema<T>;
|
|
14
|
+
/**
|
|
15
|
+
* WGSL requires integer inter-stage values to be flat interpolated. Interpolation is only
|
|
16
|
+
* inferred for shellless entry functions, so shells have to specify it explicitly.
|
|
17
|
+
*/
|
|
18
|
+
export declare function assertIntegerVaryingsInterpolated(layout: BaseData | Record<string, BaseData> | undefined, location: string): void;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { location, } from "../../data/attributes.js";
|
|
1
|
+
import { interpolate, location, } from "../../data/attributes.js";
|
|
2
2
|
import { isBuiltin } from "../../data/attributes.js";
|
|
3
3
|
import { getCustomLocation, isData } from "../../data/dataTypes.js";
|
|
4
4
|
import { INTERNAL_createStruct } from "../../data/struct.js";
|
|
5
|
-
import { isVoid } from "../../data/wgslTypes.js";
|
|
6
|
-
export function withLocations(members, locations = {}) {
|
|
5
|
+
import { isDecorated, isInteger, isIntegerVec, isInterpolateAttrib, isVoid, } from "../../data/wgslTypes.js";
|
|
6
|
+
export function withLocations(members, locations = {}, autoInterpolateIntegers = false) {
|
|
7
7
|
let nextLocation = 0;
|
|
8
8
|
const usedCustomLocations = new Set();
|
|
9
9
|
return Object.fromEntries(Object.entries(members ?? {})
|
|
@@ -15,7 +15,10 @@ export function withLocations(members, locations = {}) {
|
|
|
15
15
|
}
|
|
16
16
|
usedCustomLocations.add(customLocation);
|
|
17
17
|
}
|
|
18
|
-
return [
|
|
18
|
+
return [
|
|
19
|
+
key,
|
|
20
|
+
autoInterpolateIntegers ? withFlatInterpolationForInteger(member) : member,
|
|
21
|
+
];
|
|
19
22
|
})
|
|
20
23
|
.map(([key, member]) => {
|
|
21
24
|
if (isBuiltin(member)) {
|
|
@@ -57,15 +60,38 @@ export function separateAllAsPositional(schema) {
|
|
|
57
60
|
const positionalArgs = Object.entries(withLocs).map(([key, type]) => ({ schemaKey: key, type }));
|
|
58
61
|
return { dataSchema: undefined, positionalArgs };
|
|
59
62
|
}
|
|
60
|
-
export function createIoSchema(layout, locations = {}) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
63
|
+
export function createIoSchema(layout, locations = {}, autoInterpolateIntegers = false) {
|
|
64
|
+
if (isData(layout)) {
|
|
65
|
+
if (isVoid(layout) || isBuiltin(layout)) {
|
|
66
|
+
return layout;
|
|
67
|
+
}
|
|
68
|
+
return (getCustomLocation(layout) !== undefined ? layout : location(0, layout));
|
|
69
|
+
}
|
|
70
|
+
return INTERNAL_createStruct(withLocations(layout, locations, autoInterpolateIntegers),
|
|
71
|
+
/* isAbstruct */ false);
|
|
72
|
+
}
|
|
73
|
+
function needsFlatInterpolation(data) {
|
|
74
|
+
if (isBuiltin(data) || (isDecorated(data) && data.attribs.some(isInterpolateAttrib))) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
const inner = isDecorated(data) ? data.inner : data;
|
|
78
|
+
return isInteger(inner) || isIntegerVec(inner);
|
|
79
|
+
}
|
|
80
|
+
function withFlatInterpolationForInteger(data) {
|
|
81
|
+
return needsFlatInterpolation(data) ? interpolate('flat', data) : data;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* WGSL requires integer inter-stage values to be flat interpolated. Interpolation is only
|
|
85
|
+
* inferred for shellless entry functions, so shells have to specify it explicitly.
|
|
86
|
+
*/
|
|
87
|
+
export function assertIntegerVaryingsInterpolated(layout, location) {
|
|
88
|
+
if (layout === undefined || isData(layout)) {
|
|
89
|
+
// A single value is either a builtin or a fragment output, neither are varyings
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
for (const [key, member] of Object.entries(layout)) {
|
|
93
|
+
if (needsFlatInterpolation(member)) {
|
|
94
|
+
throw new Error(`Integer value "${key}" in ${location} requires flat interpolation. Wrap its schema in d.interpolate('flat', ...) or d.interpolate('flat, either', ...).`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
71
97
|
}
|
|
@@ -13,7 +13,7 @@ export type VertexOutToVarying<T> = OmitBuiltins<{
|
|
|
13
13
|
[K in keyof T]: InstanceToSchema<T[K]>;
|
|
14
14
|
}>;
|
|
15
15
|
type FragmentColorValue = Vec4f | Vec4i | Vec4u;
|
|
16
|
-
export type FragmentOutConstrained = IOLayout<FragmentColorValue | Decorated<FragmentColorValue,
|
|
16
|
+
export type FragmentOutConstrained = IOLayout<FragmentColorValue | Decorated<FragmentColorValue, Location[]> | AnyFragmentOutputBuiltin>;
|
|
17
17
|
/**
|
|
18
18
|
* Describes a fragment entry function signature (its arguments, return type and targets)
|
|
19
19
|
*/
|
|
@@ -3,7 +3,7 @@ import { $getNameForward, $internal, $resolve } from "../../shared/symbols.js";
|
|
|
3
3
|
import { addReturnTypeToExternals } from "../resolve/externals.js";
|
|
4
4
|
import { shaderStageSlot } from "../slot/internalSlots.js";
|
|
5
5
|
import { createFnCore } from "./fnCore.js";
|
|
6
|
-
import { createIoSchema, separateBuiltins } from "./ioSchema.js";
|
|
6
|
+
import { assertIntegerVaryingsInterpolated, createIoSchema, separateBuiltins, } from "./ioSchema.js";
|
|
7
7
|
import { stripTemplate } from "./templateUtils.js";
|
|
8
8
|
/**
|
|
9
9
|
* Creates a shell of a typed entry function for the fragment shader stage. Any function
|
|
@@ -55,6 +55,7 @@ function createFragmentFn(shell, implementation) {
|
|
|
55
55
|
return this;
|
|
56
56
|
},
|
|
57
57
|
[$resolve](ctx) {
|
|
58
|
+
assertIntegerVaryingsInterpolated(shell.in, `fragmentFn (${getName(this) ?? '<unnamed>'}) input`);
|
|
58
59
|
const entryInput = separateBuiltins(shell.in ?? {}, ctx.varyingLocations ?? {});
|
|
59
60
|
if (entryInput.dataSchema && isNamable(entryInput.dataSchema)) {
|
|
60
61
|
entryInput.dataSchema.$name(`${getName(this) ?? ''}_Input`);
|
|
@@ -2,7 +2,7 @@ import { getName, setName } from "../../shared/meta.js";
|
|
|
2
2
|
import { $getNameForward, $internal, $resolve } from "../../shared/symbols.js";
|
|
3
3
|
import { shaderStageSlot } from "../slot/internalSlots.js";
|
|
4
4
|
import { createFnCore } from "./fnCore.js";
|
|
5
|
-
import { createIoSchema, separateAllAsPositional } from "./ioSchema.js";
|
|
5
|
+
import { assertIntegerVaryingsInterpolated, createIoSchema, separateAllAsPositional, } from "./ioSchema.js";
|
|
6
6
|
import { stripTemplate } from "./templateUtils.js";
|
|
7
7
|
/**
|
|
8
8
|
* Creates a shell of a typed entry function for the vertex shader stage. Any function
|
|
@@ -50,6 +50,7 @@ function createVertexFn(shell, implementation) {
|
|
|
50
50
|
return this;
|
|
51
51
|
},
|
|
52
52
|
[$resolve](ctx) {
|
|
53
|
+
assertIntegerVaryingsInterpolated(shell.out, `vertexFn (${getName(this) ?? '<unnamed>'}) output`);
|
|
53
54
|
const outputWithLocation = createIoSchema(shell.out, ctx.varyingLocations).$name(`${getName(this) ?? ''}_Output`);
|
|
54
55
|
if (typeof implementation === 'string') {
|
|
55
56
|
core.setExternals('out', { Out: outputWithLocation });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type BaseData } from '../../data/wgslTypes.ts';
|
|
2
1
|
import type { ColorAttachment } from '../commandEncoder/attachments.ts';
|
|
2
|
+
import type { ConnectedTarget } from './connectTargetsToShader.ts';
|
|
3
3
|
import type { AnyFragmentColorAttachment } from './renderPipeline.ts';
|
|
4
|
-
export declare function connectAttachmentToShader(
|
|
4
|
+
export declare function connectAttachmentToShader(connectedTargets: (ConnectedTarget | null)[] | undefined, attachment: AnyFragmentColorAttachment): (ColorAttachment | null)[];
|
|
@@ -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(
|
|
7
|
-
if (
|
|
4
|
+
export function connectAttachmentToShader(connectedTargets, attachment) {
|
|
5
|
+
if (!connectedTargets) {
|
|
8
6
|
return [];
|
|
9
7
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
16
|
+
return attachment;
|
|
22
17
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
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
|
|
10
|
-
for (const key of
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
{
|
|
28
|
+
else {
|
|
29
|
+
const singleTarget = targets;
|
|
30
|
+
putInResult(getCustomLocation(fragmentOut) ?? 0, undefined, {
|
|
27
31
|
...singleTarget,
|
|
28
|
-
format: singleTarget?.format ??
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
225
|
+
connectedTargets: (ConnectedTarget | null)[] | undefined;
|
|
225
226
|
usedImmediate: TgpuImmediateVar | undefined;
|
|
226
227
|
};
|
|
227
228
|
declare class TgpuRenderPipelineImpl implements TgpuRenderPipeline {
|
|
@@ -280,8 +281,8 @@ 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
|
/**
|
|
@@ -42,7 +42,7 @@ export function INTERNAL_restoreRenderPipeline(soul, ctx) {
|
|
|
42
42
|
catchall: undefined,
|
|
43
43
|
logResources: undefined,
|
|
44
44
|
usedVertexLayouts: soul.usedVertexLayouts ?? [],
|
|
45
|
-
|
|
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.
|
|
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 ?? [])];
|
|
@@ -224,12 +224,10 @@ class TgpuRenderPipelineImpl {
|
|
|
224
224
|
}
|
|
225
225
|
#ownPassDescriptor() {
|
|
226
226
|
const { core, priors } = this[$internal];
|
|
227
|
-
const {
|
|
227
|
+
const { connectedTargets } = core.unwrap();
|
|
228
228
|
return {
|
|
229
229
|
label: getName(core) ?? '<unnamed>',
|
|
230
|
-
colorAttachments:
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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,7 +465,7 @@ class RenderPipelineCore {
|
|
|
467
465
|
if (tgpuDescriptor.multisample) {
|
|
468
466
|
descriptor.multisample = tgpuDescriptor.multisample;
|
|
469
467
|
}
|
|
470
|
-
return { resolutionResult, descriptor,
|
|
468
|
+
return { resolutionResult, descriptor, connectedTargets, connectedAttribs };
|
|
471
469
|
}
|
|
472
470
|
}
|
|
473
471
|
/**
|
|
@@ -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(),
|
package/core/root/rootTypes.d.ts
CHANGED
|
@@ -430,9 +430,13 @@ export interface TgpuRoot extends Unwrapper, WithBinding {
|
|
|
430
430
|
*/
|
|
431
431
|
get enabledWgslLanguageFeatures(): ReadonlySet<string>;
|
|
432
432
|
/**
|
|
433
|
-
*
|
|
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
|
-
*
|
|
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'>;
|
package/core/slot/accessor.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type AnyData } from '../../data/dataTypes.ts';
|
|
2
|
-
import type
|
|
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';
|