typegpu 0.12.5 → 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/index.d.ts +1 -0
- package/common/index.js +1 -0
- package/common/writeChannels.d.ts +31 -0
- package/common/writeChannels.js +32 -0
- package/common/writeSoA.js +16 -0
- package/core/buffer/buffer.js +47 -11
- package/core/commandEncoder/attachments.js +5 -2
- package/core/commandEncoder/computePass.d.ts +13 -0
- package/core/commandEncoder/computePass.js +5 -2
- package/core/commandEncoder/renderPass.d.ts +18 -4
- package/core/commandEncoder/renderPass.js +6 -2
- package/core/declare/tgpuDeclare.d.ts +11 -0
- package/core/declare/tgpuDeclare.js +1 -1
- 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/immediate/immediateVar.d.ts +52 -0
- package/core/immediate/immediateVar.js +149 -0
- package/core/pipeline/computePipeline.d.ts +17 -0
- package/core/pipeline/computePipeline.js +23 -9
- 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/drawState.d.ts +8 -4
- package/core/pipeline/drawState.js +41 -51
- package/core/pipeline/priors.js +1 -0
- package/core/pipeline/renderPipeline.d.ts +22 -4
- package/core/pipeline/renderPipeline.js +25 -13
- package/core/resolve/namespace.js +2 -2
- package/core/root/init.js +3 -0
- package/core/root/rootTypes.d.ts +17 -2
- package/core/slot/accessor.d.ts +1 -1
- package/core/slot/accessor.js +72 -5
- package/core/slot/slotTypes.d.ts +2 -1
- package/core/texture/texture.d.ts +20 -14
- package/core/texture/texture.js +78 -69
- package/core/texture/textureUtils.d.ts +3 -6
- package/core/texture/textureUtils.js +142 -165
- package/core/texture/textureWrite.d.ts +61 -0
- package/core/texture/textureWrite.js +59 -0
- package/data/autoStruct.d.ts +1 -1
- package/data/autoStruct.js +8 -4
- package/data/dataIO.d.ts +2 -2
- 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 +11 -5
- package/data/snippet.js +9 -4
- package/data/wgslTypes.d.ts +4 -1
- package/errors.d.ts +7 -0
- package/errors.js +15 -0
- package/indexNamedExports.d.ts +5 -2
- package/indexNamedExports.js +2 -1
- package/internal.d.ts +2 -1
- package/internal.js +1 -1
- package/nameUtils.d.ts +7 -0
- package/nameUtils.js +60 -0
- package/package.json +2 -2
- package/resolutionCtx.d.ts +5 -0
- package/resolutionCtx.js +11 -0
- package/serial/restore.d.ts +3 -1
- package/serial/restore.js +2 -0
- package/shared/meta.js +1 -1
- package/shared/symbols.js +1 -1
- package/shared/tseynit.d.ts +1 -0
- package/shared/tseynit.js +80 -44
- package/std/atomic.d.ts +13 -0
- package/std/atomic.js +63 -29
- package/std/boolean.js +9 -10
- package/std/derivative.js +14 -9
- package/std/index.d.ts +4 -4
- package/std/index.js +5 -5
- package/std/numeric.d.ts +5 -1
- package/std/numeric.js +3 -3
- package/std/operators.js +9 -4
- package/std/packing.d.ts +61 -1
- package/std/packing.js +214 -25
- package/std/subgroup.d.ts +4 -0
- package/std/subgroup.js +34 -0
- package/std/texture.d.ts +15 -2
- package/std/texture.js +48 -0
- package/tgpuUnstable.d.ts +1 -0
- package/tgpuUnstable.js +1 -0
- package/tgsl/accessIndex.js +19 -23
- package/tgsl/conversion.d.ts +2 -0
- package/tgsl/conversion.js +3 -0
- package/tgsl/forOfUtils.js +6 -6
- package/tgsl/generationHelpers.js +2 -2
- package/tgsl/shaderGenerator.d.ts +1 -1
- package/tgsl/wgslGenerator.d.ts +5 -0
- package/tgsl/wgslGenerator.js +288 -58
- package/types.d.ts +9 -1
- package/types.js +5 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { writeToArrayBuffer } from "../../data/dataIO.js";
|
|
2
|
+
import { undecorate } from "../../data/dataTypes.js";
|
|
3
|
+
import { sizeOf } from "../../data/sizeOf.js";
|
|
4
|
+
import { snip } from "../../data/snippet.js";
|
|
5
|
+
import { isBool, isDecorated, isMat, isNumericSchema, isVec, isVecBool, isWgslStruct, } from "../../data/wgslTypes.js";
|
|
6
|
+
import { IllegalVarAccessError, MissingImmediatesError } from "../../errors.js";
|
|
7
|
+
import { isInsideTgpuFn } from "../../execMode.js";
|
|
8
|
+
import { getName, setName } from "../../shared/meta.js";
|
|
9
|
+
import { $gpuValueOf, $internal, $soul, isMarkedInternal } from "../../shared/symbols.js";
|
|
10
|
+
import { makeDereferenceable } from "../../tgsl/makeDereferenceable.js";
|
|
11
|
+
import { makeResolvable } from "../../tgsl/makeResolvable.js";
|
|
12
|
+
/**
|
|
13
|
+
* Defines a variable in the 'immediate' address space, set from the CPU on a per-draw
|
|
14
|
+
* (or per-dispatch) basis without the overhead of a buffer.
|
|
15
|
+
*
|
|
16
|
+
* Only one immediate variable can be used in a single shader, and its size is limited
|
|
17
|
+
* by the device's `maxImmediateSize` limit. Requires the `immediate_address_space`
|
|
18
|
+
* WGSL language extension, check `root.enabledWgslLanguageFeatures` for support.
|
|
19
|
+
*
|
|
20
|
+
* @param dataType The schema of the held data's type. Cannot contain arrays, atomics or booleans.
|
|
21
|
+
* @param defaultValue The value used when no override is provided via `pipeline.with(immediate, value)`
|
|
22
|
+
* or `pass.setImmediates`. Captured (serialized) at creation time.
|
|
23
|
+
*/
|
|
24
|
+
export function immediateVar(dataType, defaultValue) {
|
|
25
|
+
assertValidImmediateSchema(dataType);
|
|
26
|
+
return new TgpuImmediateVarImpl(dataType, defaultValue);
|
|
27
|
+
}
|
|
28
|
+
export function isImmediateVar(value) {
|
|
29
|
+
return value?.resourceType === 'immediate-var' && isMarkedInternal(value);
|
|
30
|
+
}
|
|
31
|
+
// --------------
|
|
32
|
+
// Implementation
|
|
33
|
+
// --------------
|
|
34
|
+
function assertValidImmediateSchema(schema, rootSchema = schema) {
|
|
35
|
+
if (isWgslStruct(schema)) {
|
|
36
|
+
for (const propType of Object.values(schema.propTypes)) {
|
|
37
|
+
assertValidImmediateSchema(undecorate(propType), rootSchema);
|
|
38
|
+
}
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (isBool(schema) || isVecBool(schema)) {
|
|
42
|
+
throw new Error(`Invalid schema '${rootSchema.type}' for immediateVar: immediates cannot contain booleans (found '${schema.type}'), use u32 or i32 instead`);
|
|
43
|
+
}
|
|
44
|
+
if (isDecorated(schema)) {
|
|
45
|
+
throw new Error(`Invalid schema for immediateVar: immediates cannot be decorated types`);
|
|
46
|
+
}
|
|
47
|
+
if (!isNumericSchema(schema) && !isVec(schema) && !isMat(schema)) {
|
|
48
|
+
throw new Error(`Invalid schema '${rootSchema.type}' for immediateVar: immediates can only hold scalars, vectors, matrices and structs of those (found '${schema.type}')`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Validates that the given immediate variable can be used with the given root.
|
|
53
|
+
* @returns The size of the immediate data in bytes, to be passed as
|
|
54
|
+
* `immediateSize` to `device.createPipelineLayout`.
|
|
55
|
+
*/
|
|
56
|
+
export function validateImmediateUsage(immediate, root) {
|
|
57
|
+
if (!root.enabledWgslLanguageFeatures.has('immediate_address_space')) {
|
|
58
|
+
throw new Error(`Immediate variable '${getName(immediate) ?? '<unnamed>'}' cannot be used, because the 'immediate_address_space' WGSL language extension is not supported in this environment. Check support with root.enabledWgslLanguageFeatures and fall back to a uniform buffer (e.g. via tgpu.accessor) when unavailable.`);
|
|
59
|
+
}
|
|
60
|
+
return sizeOf(immediate.dataType);
|
|
61
|
+
}
|
|
62
|
+
const defaultSnapshots = new WeakMap();
|
|
63
|
+
export function createImmediateSnapshot(immediate, value) {
|
|
64
|
+
const bytes = new Uint8Array(sizeOf(immediate.dataType));
|
|
65
|
+
writeToArrayBuffer(bytes.buffer, immediate.dataType, value);
|
|
66
|
+
return bytes;
|
|
67
|
+
}
|
|
68
|
+
/** Captures pass overrides and tracks the last snapshot written to its encoder. */
|
|
69
|
+
export class ImmediatePassState {
|
|
70
|
+
#snapshots = new Map();
|
|
71
|
+
#lastWritten;
|
|
72
|
+
set(immediate, value) {
|
|
73
|
+
this.#snapshots.set(immediate, createImmediateSnapshot(immediate, value));
|
|
74
|
+
}
|
|
75
|
+
invalidate() {
|
|
76
|
+
this.#lastWritten = undefined;
|
|
77
|
+
}
|
|
78
|
+
/** Pass overrides take precedence over pipeline values and the variable's default. */
|
|
79
|
+
write(pass, immediate, pipelineSnapshots, deduplicate) {
|
|
80
|
+
const snapshot = this.#snapshots.get(immediate) ??
|
|
81
|
+
pipelineSnapshots?.get(immediate) ??
|
|
82
|
+
defaultSnapshots.get(immediate);
|
|
83
|
+
if (snapshot === undefined) {
|
|
84
|
+
throw new MissingImmediatesError(getName(immediate));
|
|
85
|
+
}
|
|
86
|
+
if (deduplicate && this.#lastWritten === snapshot) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
pass.setImmediates(0, snapshot.buffer);
|
|
90
|
+
this.#lastWritten = snapshot;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
class TgpuImmediateVarImpl {
|
|
94
|
+
[$soul];
|
|
95
|
+
static {
|
|
96
|
+
const prototype = TgpuImmediateVarImpl.prototype;
|
|
97
|
+
prototype.resourceType = 'immediate-var';
|
|
98
|
+
makeDereferenceable(makeResolvable(prototype, {
|
|
99
|
+
asString() {
|
|
100
|
+
return `immediateVar:${getName(this) ?? '<unnamed>'}`;
|
|
101
|
+
},
|
|
102
|
+
resolve(ctx) {
|
|
103
|
+
ctx.registerImmediate(this);
|
|
104
|
+
const id = ctx.makeUniqueIdentifier(getName(this), 'global');
|
|
105
|
+
return ctx.gen.declareGlobalVar({
|
|
106
|
+
scope: 'immediate',
|
|
107
|
+
id,
|
|
108
|
+
dataType: this[$soul].dataType,
|
|
109
|
+
init: undefined,
|
|
110
|
+
});
|
|
111
|
+
},
|
|
112
|
+
}), {
|
|
113
|
+
codegenMode: {
|
|
114
|
+
getBaseSnippet(trackingProxy) {
|
|
115
|
+
return snip(trackingProxy, this[$soul].dataType, 'immediate',
|
|
116
|
+
/* possibleSideEffects */ false);
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
normalMode: {
|
|
120
|
+
get() {
|
|
121
|
+
throw new IllegalVarAccessError(isInsideTgpuFn()
|
|
122
|
+
? `Cannot access immediate variable '${getName(this) ?? '<unnamed>'}'. TypeGPU functions that depend on GPU resources need to be part of a compute dispatch or draw call`
|
|
123
|
+
: 'Immediate variables are inaccessible during normal JS execution');
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
constructor(dataType, defaultValue) {
|
|
129
|
+
this[$soul] = {
|
|
130
|
+
type: 'immediate-var',
|
|
131
|
+
dataType,
|
|
132
|
+
defaultValue,
|
|
133
|
+
label: undefined,
|
|
134
|
+
};
|
|
135
|
+
if (defaultValue !== undefined) {
|
|
136
|
+
defaultSnapshots.set(this, createImmediateSnapshot(this, defaultValue));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
get dataType() {
|
|
140
|
+
return this[$soul].dataType;
|
|
141
|
+
}
|
|
142
|
+
get defaultValue() {
|
|
143
|
+
return this[$soul].defaultValue;
|
|
144
|
+
}
|
|
145
|
+
$name(label) {
|
|
146
|
+
setName(this, label);
|
|
147
|
+
return this;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -3,11 +3,13 @@ import type { TgpuQuerySet } from '../querySet/querySet.ts';
|
|
|
3
3
|
import { type ResolvedSnippet } from '../../data/snippet.ts';
|
|
4
4
|
import type { AnyWgslData } from '../../data/wgslTypes.ts';
|
|
5
5
|
import type { TgpuNamable } from '../../shared/meta.ts';
|
|
6
|
+
import type { InferInput } from '../../shared/repr.ts';
|
|
6
7
|
import type { TgpuDeviceOwningSoul } from '../../shared/soul.ts';
|
|
7
8
|
import { $getNameForward, $internal, $resolve, $soul } from '../../shared/symbols.ts';
|
|
8
9
|
import { type TgpuBindGroup, type TgpuBindGroupLayout, type TgpuLayoutEntry } from '../../tgpuBindGroupLayout.ts';
|
|
9
10
|
import { type TgpuCommandEncoder } from '../commandEncoder/commandEncoder.ts';
|
|
10
11
|
import { type TgpuComputePass } from '../commandEncoder/computePass.ts';
|
|
12
|
+
import { type ImmediateSnapshot, type TgpuImmediateVar } from '../immediate/immediateVar.ts';
|
|
11
13
|
import type { LogResources } from '../../tgsl/consoleLog/types.ts';
|
|
12
14
|
import { type ResolutionCtx, type SelfResolvable } from '../../types.ts';
|
|
13
15
|
import type { IORecord } from '../function/fnTypes.ts';
|
|
@@ -27,6 +29,8 @@ export interface ComputePipelineInternals {
|
|
|
27
29
|
export interface TgpuComputePipelineSoul extends TgpuDeviceOwningSoul<'compute-pipeline', GPUComputePipeline> {
|
|
28
30
|
usedBindGroupLayouts?: TgpuBindGroupLayout[] | undefined;
|
|
29
31
|
bindGroups?: [TgpuBindGroupLayout, TgpuBindGroup | GPUBindGroup][] | undefined;
|
|
32
|
+
usedImmediate?: TgpuImmediateVar | undefined;
|
|
33
|
+
immediates?: [TgpuImmediateVar, ImmediateSnapshot][] | undefined;
|
|
30
34
|
timestampWrites?: TimestampWritesPriors['timestampWrites'];
|
|
31
35
|
performanceCallback?: TimestampWritesPriors['performanceCallback'];
|
|
32
36
|
nonTransferablePriors?: string[] | undefined;
|
|
@@ -42,6 +46,16 @@ export interface TgpuComputePipeline extends TgpuNamable, SelfResolvable, Timeab
|
|
|
42
46
|
with<Entries extends Record<string, TgpuLayoutEntry | null>>(bindGroupLayout: TgpuBindGroupLayout<Entries>, bindGroup: TgpuBindGroup<Entries>): this;
|
|
43
47
|
with(bindGroupLayout: TgpuBindGroupLayout, bindGroup: GPUBindGroup): this;
|
|
44
48
|
with(bindGroup: TgpuBindGroup): this;
|
|
49
|
+
/**
|
|
50
|
+
* Provides a value for the given immediate variable, applied on dispatch
|
|
51
|
+
* like the rest of the pipeline-held state and overridden by
|
|
52
|
+
* `pass.setImmediates`. The value is captured (copied) at call time;
|
|
53
|
+
* mutating it afterwards has no effect.
|
|
54
|
+
*
|
|
55
|
+
* Passing an `ArrayBuffer` or typed array skips serialization entirely; the bytes
|
|
56
|
+
* are copied verbatim and the caller guarantees they match the schema's layout.
|
|
57
|
+
*/
|
|
58
|
+
with<T extends AnyWgslData>(immediate: TgpuImmediateVar<T>, value: InferInput<T> | ArrayBuffer | ArrayBufferView): this;
|
|
45
59
|
/**
|
|
46
60
|
* Directs subsequent dispatches into the given compute pass, letting multiple
|
|
47
61
|
* pipelines share one pass (and one submission).
|
|
@@ -93,12 +107,14 @@ type TgpuComputePipelinePriors = {
|
|
|
93
107
|
readonly pass?: TgpuComputePass | undefined;
|
|
94
108
|
/** An encoder the pipeline records its own passes into, but does not submit */
|
|
95
109
|
readonly encoder?: TgpuCommandEncoder | undefined;
|
|
110
|
+
readonly immediatesMap?: Map<TgpuImmediateVar, ImmediateSnapshot> | undefined;
|
|
96
111
|
} & TimestampWritesPriors;
|
|
97
112
|
type Memo = {
|
|
98
113
|
pipeline: GPUComputePipeline;
|
|
99
114
|
usedBindGroupLayouts: TgpuBindGroupLayout[];
|
|
100
115
|
catchall: [number, TgpuBindGroup] | undefined;
|
|
101
116
|
logResources: LogResources | undefined;
|
|
117
|
+
usedImmediate: TgpuImmediateVar | undefined;
|
|
102
118
|
};
|
|
103
119
|
declare class TgpuComputePipelineImpl implements TgpuComputePipeline {
|
|
104
120
|
#private;
|
|
@@ -112,6 +128,7 @@ declare class TgpuComputePipelineImpl implements TgpuComputePipeline {
|
|
|
112
128
|
with<Entries extends Record<string, TgpuLayoutEntry | null>>(bindGroupLayout: TgpuBindGroupLayout<Entries>, bindGroup: TgpuBindGroup<Entries>): this;
|
|
113
129
|
with(bindGroupLayout: TgpuBindGroupLayout, bindGroup: GPUBindGroup): this;
|
|
114
130
|
with(bindGroup: TgpuBindGroup): this;
|
|
131
|
+
with<T extends AnyWgslData>(immediate: TgpuImmediateVar<T>, value: InferInput<T> | ArrayBuffer | ArrayBufferView): this;
|
|
115
132
|
with(pass: TgpuComputePass): this;
|
|
116
133
|
with(encoder: TgpuCommandEncoder): this;
|
|
117
134
|
with(encoder: GPUCommandEncoder): this;
|
|
@@ -6,6 +6,7 @@ import { $getNameForward, $internal, $resolve, $soul } from "../../shared/symbol
|
|
|
6
6
|
import { isBindGroup, isBindGroupLayout, } from "../../tgpuBindGroupLayout.js";
|
|
7
7
|
import { INTERNAL_adoptCommandEncoder, INTERNAL_createCommandEncoder, } from "../commandEncoder/commandEncoder.js";
|
|
8
8
|
import { INTERNAL_adoptComputePass } from "../commandEncoder/computePass.js";
|
|
9
|
+
import { createImmediateSnapshot, isImmediateVar, validateImmediateUsage, } from "../immediate/immediateVar.js";
|
|
9
10
|
import { emitComputeDispatch, finalizeOwnEncoder } from "./drawState.js";
|
|
10
11
|
import { isGPUCommandEncoder, isGPUComputePassEncoder, isTgpuCommandEncoder, isTgpuComputePass, } from "./typeGuards.js";
|
|
11
12
|
import { isGPUBuffer } from "../../types.js";
|
|
@@ -30,9 +31,11 @@ export function INTERNAL_restoreComputePipeline(soul, ctx) {
|
|
|
30
31
|
// The catchall group is already one of `bindGroups`, keyed by the layout it was resolved with
|
|
31
32
|
catchall: undefined,
|
|
32
33
|
logResources: undefined,
|
|
34
|
+
usedImmediate: soul.usedImmediate,
|
|
33
35
|
});
|
|
34
36
|
const pipeline = new TgpuComputePipelineImpl(core, {
|
|
35
37
|
bindGroupLayoutMap: new Map(soul.bindGroups),
|
|
38
|
+
immediatesMap: new Map(soul.immediates),
|
|
36
39
|
});
|
|
37
40
|
return restoreTimestampPriors(pipeline, soul);
|
|
38
41
|
}
|
|
@@ -59,6 +62,8 @@ class TgpuComputePipelineImpl {
|
|
|
59
62
|
soul.raw = memo.pipeline;
|
|
60
63
|
soul.usedBindGroupLayouts = memo.usedBindGroupLayouts;
|
|
61
64
|
soul.bindGroups = collectBindGroupPairs(memo.usedBindGroupLayouts, memo.catchall, priors.bindGroupLayoutMap);
|
|
65
|
+
soul.usedImmediate = memo.usedImmediate;
|
|
66
|
+
soul.immediates = [...(priors.immediatesMap ?? [])];
|
|
62
67
|
soul.timestampWrites = priors.timestampWrites;
|
|
63
68
|
soul.performanceCallback = priors.performanceCallback;
|
|
64
69
|
soul.nonTransferablePriors = nonTransferablePriorsOf(priors);
|
|
@@ -78,7 +83,7 @@ class TgpuComputePipelineImpl {
|
|
|
78
83
|
const { core, priors } = this[$internal];
|
|
79
84
|
return new TgpuComputePipelineImpl(core, { ...priors, ...patch });
|
|
80
85
|
}
|
|
81
|
-
with(first,
|
|
86
|
+
with(first, resource) {
|
|
82
87
|
const internals = this[$internal];
|
|
83
88
|
if (isTgpuComputePass(first)) {
|
|
84
89
|
return this.#withPriors({ pass: first, encoder: undefined });
|
|
@@ -98,10 +103,15 @@ class TgpuComputePipelineImpl {
|
|
|
98
103
|
encoder: INTERNAL_adoptCommandEncoder(internals.root, first),
|
|
99
104
|
});
|
|
100
105
|
}
|
|
106
|
+
if (isImmediateVar(first)) {
|
|
107
|
+
return this.#withPriors({
|
|
108
|
+
immediatesMap: new Map(internals.priors.immediatesMap).set(first, createImmediateSnapshot(first, resource)),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
101
111
|
if (isBindGroup(first) || isBindGroupLayout(first)) {
|
|
102
112
|
const [layout, group] = isBindGroup(first)
|
|
103
113
|
? [first.layout, first]
|
|
104
|
-
: [first,
|
|
114
|
+
: [first, resource];
|
|
105
115
|
return this.#withPriors({
|
|
106
116
|
bindGroupLayoutMap: new Map([
|
|
107
117
|
...(internals.priors.bindGroupLayoutMap ?? []),
|
|
@@ -220,19 +230,20 @@ class ComputePipelineCore {
|
|
|
220
230
|
if (this.#initAsyncPromise === undefined) {
|
|
221
231
|
// the pipeline did not start resolution & compilation
|
|
222
232
|
const device = this.root.device;
|
|
223
|
-
const { resolutionResult, module } = this.resolveAndCreateShaderModule();
|
|
224
|
-
const { usedBindGroupLayouts, catchall, logResources } = resolutionResult;
|
|
233
|
+
const { resolutionResult, module, immediateSize } = this.resolveAndCreateShaderModule();
|
|
234
|
+
const { usedBindGroupLayouts, catchall, logResources, usedImmediate } = resolutionResult;
|
|
225
235
|
this.#initAsyncPromise = device
|
|
226
236
|
.createComputePipelineAsync({
|
|
227
237
|
label: getName(this) ?? '<unnamed>',
|
|
228
238
|
layout: device.createPipelineLayout({
|
|
229
239
|
label: `${getName(this) ?? '<unnamed>'} - Pipeline Layout`,
|
|
230
240
|
bindGroupLayouts: usedBindGroupLayouts.map((l) => this.root.unwrap(l)),
|
|
241
|
+
immediateSize,
|
|
231
242
|
}),
|
|
232
243
|
compute: { module },
|
|
233
244
|
})
|
|
234
245
|
.then((pipeline) => {
|
|
235
|
-
this.#memo = { pipeline, usedBindGroupLayouts, catchall, logResources };
|
|
246
|
+
this.#memo = { pipeline, usedBindGroupLayouts, catchall, logResources, usedImmediate };
|
|
236
247
|
this.#performanceTracker.measureCompile(device);
|
|
237
248
|
})
|
|
238
249
|
.finally(() => {
|
|
@@ -249,20 +260,22 @@ class ComputePipelineCore {
|
|
|
249
260
|
throw new Error("'pipeline.initAsync()' was called and is not yet resolved.");
|
|
250
261
|
}
|
|
251
262
|
const device = this.root.device;
|
|
252
|
-
const { resolutionResult, module } = this.resolveAndCreateShaderModule();
|
|
253
|
-
const { usedBindGroupLayouts, catchall, logResources } = resolutionResult;
|
|
263
|
+
const { resolutionResult, module, immediateSize } = this.resolveAndCreateShaderModule();
|
|
264
|
+
const { usedBindGroupLayouts, catchall, logResources, usedImmediate } = resolutionResult;
|
|
254
265
|
this.#memo = {
|
|
255
266
|
pipeline: device.createComputePipeline({
|
|
256
267
|
label: getName(this) ?? '<unnamed>',
|
|
257
268
|
layout: device.createPipelineLayout({
|
|
258
269
|
label: `${getName(this) ?? '<unnamed>'} - Pipeline Layout`,
|
|
259
270
|
bindGroupLayouts: usedBindGroupLayouts.map((l) => this.root.unwrap(l)),
|
|
271
|
+
immediateSize,
|
|
260
272
|
}),
|
|
261
273
|
compute: { module },
|
|
262
274
|
}),
|
|
263
275
|
usedBindGroupLayouts,
|
|
264
276
|
catchall,
|
|
265
277
|
logResources,
|
|
278
|
+
usedImmediate,
|
|
266
279
|
};
|
|
267
280
|
this.#performanceTracker.measureCompile(device);
|
|
268
281
|
}
|
|
@@ -284,15 +297,16 @@ class ComputePipelineCore {
|
|
|
284
297
|
: undefined,
|
|
285
298
|
root: this.root,
|
|
286
299
|
}));
|
|
287
|
-
const { code, usedBindGroupLayouts, catchall } = resolutionResult;
|
|
300
|
+
const { code, usedBindGroupLayouts, catchall, usedImmediate } = resolutionResult;
|
|
288
301
|
if (catchall !== undefined) {
|
|
289
302
|
usedBindGroupLayouts[catchall[0]]?.$name(`${getName(this) ?? '<unnamed>'} - Automatic Bind Group & Layout`);
|
|
290
303
|
}
|
|
291
304
|
warnIfOverflow(usedBindGroupLayouts, device.limits);
|
|
305
|
+
const immediateSize = usedImmediate ? validateImmediateUsage(usedImmediate, this.root) : 0;
|
|
292
306
|
const module = device.createShaderModule({
|
|
293
307
|
label: `${getName(this) ?? '<unnamed>'} - Shader`,
|
|
294
308
|
code,
|
|
295
309
|
});
|
|
296
|
-
return { resolutionResult, module };
|
|
310
|
+
return { resolutionResult, module, immediateSize };
|
|
297
311
|
}
|
|
298
312
|
}
|
|
@@ -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
|
}
|
|
@@ -5,6 +5,7 @@ import type { IndexFlag, TgpuBuffer, VertexFlag } from '../buffer/buffer.ts';
|
|
|
5
5
|
import type { TgpuCommandEncoder } from '../commandEncoder/commandEncoder.ts';
|
|
6
6
|
import type { ComputePassInternals } from '../commandEncoder/computePass.ts';
|
|
7
7
|
import type { RenderPassInternals } from '../commandEncoder/renderPass.ts';
|
|
8
|
+
import { ImmediatePassState } from '../immediate/immediateVar.ts';
|
|
8
9
|
import type { ExperimentalTgpuRoot } from '../root/rootTypes.ts';
|
|
9
10
|
import type { TgpuVertexLayout } from '../vertexLayout/vertexLayout.ts';
|
|
10
11
|
import type { TgpuComputePipeline } from './computePipeline.ts';
|
|
@@ -24,6 +25,7 @@ export interface IndexBufferEntry {
|
|
|
24
25
|
export declare class RenderDrawState {
|
|
25
26
|
readonly bindGroups: Map<TgpuBindGroupLayout<Record<string, import("../../tgpuBindGroupLayout.ts").TgpuLayoutEntry | null>>, TgpuBindGroup | GPUBindGroup>;
|
|
26
27
|
readonly vertexBuffers: Map<TgpuVertexLayout<import("../../data/wgslTypes.ts").WgslArray<BaseData> | import("../../data/dataTypes.ts").Disarray<BaseData>>, VertexBufferEntry>;
|
|
28
|
+
readonly immediates: ImmediatePassState;
|
|
27
29
|
currentPipeline: TgpuRenderPipeline | undefined;
|
|
28
30
|
indexBuffer: IndexBufferEntry | undefined;
|
|
29
31
|
stencilReference: GPUStencilValue | undefined;
|
|
@@ -35,15 +37,17 @@ export declare class RenderDrawState {
|
|
|
35
37
|
}
|
|
36
38
|
export declare class ComputeDrawState {
|
|
37
39
|
readonly bindGroups: Map<TgpuBindGroupLayout<Record<string, import("../../tgpuBindGroupLayout.ts").TgpuLayoutEntry | null>>, TgpuBindGroup | GPUBindGroup>;
|
|
40
|
+
readonly immediates: ImmediatePassState;
|
|
38
41
|
currentPipeline: TgpuComputePipeline | undefined;
|
|
39
42
|
version: number;
|
|
40
43
|
rawAccessed: boolean;
|
|
41
44
|
}
|
|
42
45
|
export declare function recordBindGroup(state: RenderDrawState | ComputeDrawState, first: TgpuBindGroup | TgpuBindGroupLayout, bindGroup: TgpuBindGroup | GPUBindGroup | undefined): void;
|
|
43
|
-
/**
|
|
44
|
-
export declare function
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
/** Makes the pipeline current; its held state is resolved at draw time, below what the pass holds */
|
|
47
|
+
export declare function selectPipeline<TPipeline>(state: {
|
|
48
|
+
currentPipeline: TPipeline | undefined;
|
|
49
|
+
version: number;
|
|
50
|
+
}, pipeline: TPipeline): void;
|
|
47
51
|
export declare function requireIndexBuffer(indexBuffer: IndexBufferEntry | undefined): void;
|
|
48
52
|
export declare function finalizeOwnEncoder(encoder: TgpuCommandEncoder, core: object, logResources: LogResources | undefined, priors: TimestampWritesPriors & {
|
|
49
53
|
readonly encoder?: TgpuCommandEncoder | undefined;
|
|
@@ -4,10 +4,12 @@ import { logger } from "../../tgpuLogger.js";
|
|
|
4
4
|
import { isBindGroup, } from "../../tgpuBindGroupLayout.js";
|
|
5
5
|
import { logDataFromGPU } from "../../tgsl/consoleLog/deserializers.js";
|
|
6
6
|
import { isBuffer } from "../../types.js";
|
|
7
|
+
import { ImmediatePassState } from "../immediate/immediateVar.js";
|
|
7
8
|
import { queueTimestampResolve } from "./timeable.js";
|
|
8
9
|
export class RenderDrawState {
|
|
9
10
|
bindGroups = new Map();
|
|
10
11
|
vertexBuffers = new Map();
|
|
12
|
+
immediates = new ImmediatePassState();
|
|
11
13
|
currentPipeline;
|
|
12
14
|
indexBuffer;
|
|
13
15
|
stencilReference;
|
|
@@ -19,6 +21,7 @@ export class RenderDrawState {
|
|
|
19
21
|
}
|
|
20
22
|
export class ComputeDrawState {
|
|
21
23
|
bindGroups = new Map();
|
|
24
|
+
immediates = new ImmediatePassState();
|
|
22
25
|
currentPipeline;
|
|
23
26
|
version = 0;
|
|
24
27
|
rawAccessed = false;
|
|
@@ -32,38 +35,12 @@ export function recordBindGroup(state, first, bindGroup) {
|
|
|
32
35
|
}
|
|
33
36
|
state.version++;
|
|
34
37
|
}
|
|
35
|
-
/**
|
|
36
|
-
export function
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
for (const [layout, group] of priors.bindGroupLayoutMap) {
|
|
41
|
-
state.bindGroups.set(layout, group);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
if (priors.vertexLayoutMap) {
|
|
45
|
-
for (const [layout, buffer] of priors.vertexLayoutMap) {
|
|
46
|
-
state.vertexBuffers.set(layout, { buffer, offset: undefined, size: undefined });
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
if (priors.indexBuffer) {
|
|
50
|
-
state.indexBuffer = priors.indexBuffer;
|
|
51
|
-
}
|
|
52
|
-
if (priors.stencilReference !== undefined) {
|
|
53
|
-
state.stencilReference = priors.stencilReference;
|
|
54
|
-
}
|
|
55
|
-
state.version++;
|
|
56
|
-
}
|
|
57
|
-
/** The compute counterpart of {@link stampRenderPipeline} */
|
|
58
|
-
export function stampComputePipeline(state, pipeline) {
|
|
59
|
-
const { priors } = pipeline[$internal];
|
|
60
|
-
state.currentPipeline = pipeline;
|
|
61
|
-
if (priors.bindGroupLayoutMap) {
|
|
62
|
-
for (const [layout, group] of priors.bindGroupLayoutMap) {
|
|
63
|
-
state.bindGroups.set(layout, group);
|
|
64
|
-
}
|
|
38
|
+
/** Makes the pipeline current; its held state is resolved at draw time, below what the pass holds */
|
|
39
|
+
export function selectPipeline(state, pipeline) {
|
|
40
|
+
if (state.currentPipeline !== pipeline) {
|
|
41
|
+
state.currentPipeline = pipeline;
|
|
42
|
+
state.version++;
|
|
65
43
|
}
|
|
66
|
-
state.version++;
|
|
67
44
|
}
|
|
68
45
|
function applyIndexBuffer(encoder, root, entry) {
|
|
69
46
|
const { buffer, indexFormat, offsetBytes, sizeBytes } = entry;
|
|
@@ -117,25 +94,36 @@ function applyVertexBuffers(encoder, root, usedVertexLayouts, resolveVertexBuffe
|
|
|
117
94
|
}
|
|
118
95
|
}
|
|
119
96
|
function applyRenderPipelineState(encoder, root, pipeline, passState) {
|
|
120
|
-
const
|
|
97
|
+
const { core, priors } = pipeline[$internal];
|
|
98
|
+
const memo = core.unwrap();
|
|
121
99
|
encoder.setPipeline(memo.pipeline);
|
|
122
|
-
applyBindGroups(encoder, root, memo.usedBindGroupLayouts, memo.catchall, (layout) => passState.bindGroups.get(layout));
|
|
123
|
-
applyVertexBuffers(encoder, root, memo.usedVertexLayouts, (vertexLayout) =>
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
100
|
+
applyBindGroups(encoder, root, memo.usedBindGroupLayouts, memo.catchall, (layout) => passState.bindGroups.get(layout) ?? priors.bindGroupLayoutMap?.get(layout));
|
|
101
|
+
applyVertexBuffers(encoder, root, memo.usedVertexLayouts, (vertexLayout) => {
|
|
102
|
+
const passEntry = passState.vertexBuffers.get(vertexLayout);
|
|
103
|
+
if (passEntry !== undefined) {
|
|
104
|
+
return passEntry;
|
|
105
|
+
}
|
|
106
|
+
const buffer = priors.vertexLayoutMap?.get(vertexLayout);
|
|
107
|
+
return buffer !== undefined ? { buffer } : undefined;
|
|
108
|
+
});
|
|
109
|
+
const indexBuffer = passState.indexBuffer ?? priors.indexBuffer;
|
|
110
|
+
if (indexBuffer !== undefined) {
|
|
111
|
+
applyIndexBuffer(encoder, root, indexBuffer);
|
|
112
|
+
}
|
|
113
|
+
if (typeof encoder.setStencilReference === 'function') {
|
|
114
|
+
const stencilReference = passState.stencilReference ?? priors.stencilReference ?? 0;
|
|
115
|
+
const dirty = passState.rawAccessed || stencilReference !== passState.appliedStencilReference;
|
|
116
|
+
if (dirty) {
|
|
117
|
+
encoder.setStencilReference(stencilReference);
|
|
118
|
+
passState.appliedStencilReference = stencilReference;
|
|
132
119
|
}
|
|
133
120
|
}
|
|
134
121
|
}
|
|
135
122
|
function applyComputePipelineState(encoder, root, pipeline, passState) {
|
|
136
|
-
const
|
|
123
|
+
const { core, priors } = pipeline[$internal];
|
|
124
|
+
const memo = core.unwrap();
|
|
137
125
|
encoder.setPipeline(memo.pipeline);
|
|
138
|
-
applyBindGroups(encoder, root, memo.usedBindGroupLayouts, memo.catchall, (layout) => passState.bindGroups.get(layout));
|
|
126
|
+
applyBindGroups(encoder, root, memo.usedBindGroupLayouts, memo.catchall, (layout) => passState.bindGroups.get(layout) ?? priors.bindGroupLayoutMap?.get(layout));
|
|
139
127
|
}
|
|
140
128
|
export function requireIndexBuffer(indexBuffer) {
|
|
141
129
|
if (!indexBuffer) {
|
|
@@ -191,11 +179,9 @@ function reportIgnoredPriors(core, owner, hasTimestampWrites, logResources, pass
|
|
|
191
179
|
export function emitRenderDraw(root, passInternals, pipeline, usesIndexBuffer, emit, ownsPass = false) {
|
|
192
180
|
const { state, rawPass } = passInternals;
|
|
193
181
|
const { core, priors } = pipeline[$internal];
|
|
194
|
-
|
|
195
|
-
stampRenderPipeline(state, pipeline);
|
|
196
|
-
}
|
|
182
|
+
selectPipeline(state, pipeline);
|
|
197
183
|
if (usesIndexBuffer) {
|
|
198
|
-
requireIndexBuffer(state.indexBuffer);
|
|
184
|
+
requireIndexBuffer(state.indexBuffer ?? priors.indexBuffer);
|
|
199
185
|
}
|
|
200
186
|
const memo = core.unwrap();
|
|
201
187
|
if (!ownsPass) {
|
|
@@ -205,14 +191,15 @@ export function emitRenderDraw(root, passInternals, pipeline, usesIndexBuffer, e
|
|
|
205
191
|
applyRenderPipelineState(rawPass, root, pipeline, state);
|
|
206
192
|
passInternals.appliedVersion = state.version;
|
|
207
193
|
}
|
|
194
|
+
if (memo.usedImmediate !== undefined) {
|
|
195
|
+
state.immediates.write(rawPass, memo.usedImmediate, priors.immediatesMap, !state.rawAccessed);
|
|
196
|
+
}
|
|
208
197
|
emit(rawPass);
|
|
209
198
|
}
|
|
210
199
|
export function emitComputeDispatch(root, passInternals, pipeline, emit, ownsPass = false) {
|
|
211
200
|
const { state, rawPass } = passInternals;
|
|
212
201
|
const { core, priors } = pipeline[$internal];
|
|
213
|
-
|
|
214
|
-
stampComputePipeline(state, pipeline);
|
|
215
|
-
}
|
|
202
|
+
selectPipeline(state, pipeline);
|
|
216
203
|
const memo = core.unwrap();
|
|
217
204
|
if (!ownsPass) {
|
|
218
205
|
reportIgnoredPriors(core, passInternals.owner, !!priors.timestampWrites, memo.logResources, 'compute');
|
|
@@ -221,5 +208,8 @@ export function emitComputeDispatch(root, passInternals, pipeline, emit, ownsPas
|
|
|
221
208
|
applyComputePipelineState(rawPass, root, pipeline, state);
|
|
222
209
|
passInternals.appliedVersion = state.version;
|
|
223
210
|
}
|
|
211
|
+
if (memo.usedImmediate !== undefined) {
|
|
212
|
+
state.immediates.write(rawPass, memo.usedImmediate, priors.immediatesMap, !state.rawAccessed);
|
|
213
|
+
}
|
|
224
214
|
emit(rawPass);
|
|
225
215
|
}
|
package/core/pipeline/priors.js
CHANGED