typegpu 0.12.5 → 0.12.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/common/index.d.ts +1 -0
  2. package/common/index.js +1 -0
  3. package/common/writeChannels.d.ts +31 -0
  4. package/common/writeChannels.js +32 -0
  5. package/core/commandEncoder/computePass.d.ts +13 -0
  6. package/core/commandEncoder/computePass.js +5 -2
  7. package/core/commandEncoder/renderPass.d.ts +18 -4
  8. package/core/commandEncoder/renderPass.js +6 -2
  9. package/core/declare/tgpuDeclare.d.ts +11 -0
  10. package/core/declare/tgpuDeclare.js +1 -1
  11. package/core/immediate/immediateVar.d.ts +52 -0
  12. package/core/immediate/immediateVar.js +149 -0
  13. package/core/pipeline/computePipeline.d.ts +17 -0
  14. package/core/pipeline/computePipeline.js +23 -9
  15. package/core/pipeline/drawState.d.ts +8 -4
  16. package/core/pipeline/drawState.js +41 -51
  17. package/core/pipeline/priors.js +1 -0
  18. package/core/pipeline/renderPipeline.d.ts +18 -1
  19. package/core/pipeline/renderPipeline.js +15 -1
  20. package/core/root/init.js +3 -0
  21. package/core/root/rootTypes.d.ts +11 -0
  22. package/core/slot/slotTypes.d.ts +2 -1
  23. package/core/texture/texture.d.ts +20 -14
  24. package/core/texture/texture.js +70 -65
  25. package/core/texture/textureUtils.d.ts +3 -6
  26. package/core/texture/textureUtils.js +142 -165
  27. package/core/texture/textureWrite.d.ts +61 -0
  28. package/core/texture/textureWrite.js +59 -0
  29. package/data/dataIO.d.ts +2 -2
  30. package/data/snippet.d.ts +5 -1
  31. package/data/snippet.js +1 -0
  32. package/data/wgslTypes.d.ts +1 -1
  33. package/errors.d.ts +7 -0
  34. package/errors.js +15 -0
  35. package/indexNamedExports.d.ts +5 -2
  36. package/indexNamedExports.js +2 -1
  37. package/internal.d.ts +1 -1
  38. package/internal.js +1 -1
  39. package/package.json +2 -2
  40. package/resolutionCtx.d.ts +5 -0
  41. package/resolutionCtx.js +11 -0
  42. package/serial/restore.d.ts +3 -1
  43. package/serial/restore.js +2 -0
  44. package/shared/meta.js +1 -1
  45. package/shared/symbols.js +1 -1
  46. package/shared/tseynit.d.ts +1 -0
  47. package/shared/tseynit.js +39 -7
  48. package/std/atomic.d.ts +13 -0
  49. package/std/atomic.js +63 -29
  50. package/std/derivative.js +14 -9
  51. package/std/index.d.ts +4 -4
  52. package/std/index.js +5 -5
  53. package/std/numeric.d.ts +5 -1
  54. package/std/numeric.js +3 -3
  55. package/std/packing.d.ts +61 -1
  56. package/std/packing.js +214 -25
  57. package/std/subgroup.d.ts +4 -0
  58. package/std/subgroup.js +34 -0
  59. package/std/texture.d.ts +13 -0
  60. package/std/texture.js +48 -0
  61. package/tgpuUnstable.d.ts +1 -0
  62. package/tgpuUnstable.js +1 -0
  63. package/tgsl/conversion.d.ts +2 -0
  64. package/tgsl/conversion.js +3 -0
  65. package/tgsl/shaderGenerator.d.ts +1 -1
  66. package/tgsl/wgslGenerator.d.ts +5 -0
  67. package/tgsl/wgslGenerator.js +218 -50
  68. package/types.d.ts +9 -1
  69. package/types.js +5 -0
package/common/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { fullScreenTriangle } from './fullScreenTriangle.ts';
2
+ export { writeChannels } from './writeChannels.ts';
2
3
  export { writeSoA } from './writeSoA.ts';
package/common/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  // NOTE: This is a barrel file, internal files should not import things from this file
2
2
  export { fullScreenTriangle } from "./fullScreenTriangle.js";
3
+ export { writeChannels } from "./writeChannels.js";
3
4
  export { writeSoA } from "./writeSoA.js";
@@ -0,0 +1,31 @@
1
+ import type { TgpuTexture } from '../core/texture/texture.ts';
2
+ import { type TextureChannel, type TextureWriteOptions } from '../core/texture/textureWrite.ts';
3
+ import type { RenderFlag } from '../core/texture/usageExtension.ts';
4
+ export type TextureChannelSource = {
5
+ source: GPUCopyExternalImageSource;
6
+ from: TextureChannel;
7
+ };
8
+ export type TextureChannels = {
9
+ [Channel in TextureChannel]?: TextureChannelSource | undefined;
10
+ };
11
+ /**
12
+ * Writes image sources into individual channels of `texture`. Each entry
13
+ * writes a single channel, with `from` selecting which channel of the
14
+ * source to read. Omitted channels are left untouched.
15
+ *
16
+ * Requires the `'render'` usage flag on the texture.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * common.writeChannels(material, {
21
+ * r: { source: roughnessMap, from: 'r' },
22
+ * g: { source: metalnessMap, from: 'r' },
23
+ * a: { source: maskMap, from: 'r' },
24
+ * });
25
+ * ```
26
+ */
27
+ export declare function writeChannels(texture: TgpuTexture & RenderFlag, channels: TextureChannels, options?: TextureWriteOptions): void;
28
+ export declare namespace writeChannels {
29
+ type Channels = TextureChannels;
30
+ type Source = TextureChannelSource;
31
+ }
@@ -0,0 +1,32 @@
1
+ import { writeImages } from "../core/texture/textureUtils.js";
2
+ import { resolveImageWrite, } from "../core/texture/textureWrite.js";
3
+ import { $internal, $soul } from "../shared/symbols.js";
4
+ /**
5
+ * Writes image sources into individual channels of `texture`. Each entry
6
+ * writes a single channel, with `from` selecting which channel of the
7
+ * source to read. Omitted channels are left untouched.
8
+ *
9
+ * Requires the `'render'` usage flag on the texture.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * common.writeChannels(material, {
14
+ * r: { source: roughnessMap, from: 'r' },
15
+ * g: { source: metalnessMap, from: 'r' },
16
+ * a: { source: maskMap, from: 'r' },
17
+ * });
18
+ * ```
19
+ */
20
+ export function writeChannels(texture, channels, options = {}) {
21
+ const writes = [];
22
+ for (const to of ['r', 'g', 'b', 'a']) {
23
+ const entry = channels[to];
24
+ if (entry) {
25
+ writes.push({
26
+ ...resolveImageWrite(texture.props, entry.source, options),
27
+ channel: { from: entry.from, to },
28
+ });
29
+ }
30
+ }
31
+ writeImages(texture[$soul].device, texture[$internal].materialize(), writes);
32
+ }
@@ -1,8 +1,10 @@
1
1
  import type { PrimitiveOffsetInfo } from '../../data/offsetUtils.ts';
2
2
  import type { AnyWgslData } from '../../data/wgslTypes.ts';
3
+ import type { InferInput } from '../../shared/repr.ts';
3
4
  import { $internal } from '../../shared/symbols.ts';
4
5
  import type { TgpuBindGroup, TgpuBindGroupLayout, TgpuLayoutEntry } from '../../tgpuBindGroupLayout.ts';
5
6
  import type { IndirectFlag, TgpuBuffer } from '../buffer/buffer.ts';
7
+ import type { TgpuImmediateVar } from '../immediate/immediateVar.ts';
6
8
  import { ComputeDrawState } from '../pipeline/drawState.ts';
7
9
  import type { TgpuComputePipeline } from '../pipeline/computePipeline.ts';
8
10
  import type { ExperimentalTgpuRoot } from '../root/rootTypes.ts';
@@ -41,6 +43,17 @@ export interface TgpuComputePass {
41
43
  setBindGroup(bindGroup: TgpuBindGroup): void;
42
44
  /** Associates a bind group with the given layout */
43
45
  setBindGroup<Entries extends Record<string, TgpuLayoutEntry | null>>(bindGroupLayout: TgpuBindGroupLayout<Entries>, bindGroup: TgpuBindGroup<Entries> | GPUBindGroup): void;
46
+ /**
47
+ * Provides a value for the given immediate variable, used by subsequent dispatches.
48
+ * The value is captured (copied) at call time; mutating it afterwards has no
49
+ * effect until it is set again. Takes precedence over values held by the
50
+ * pipeline (`pipeline.with(immediate, value)`) and persists across pipeline
51
+ * switches until set again, like any other pass-level state.
52
+ *
53
+ * Passing an `ArrayBuffer` or typed array skips serialization entirely; the bytes
54
+ * are copied verbatim and the caller guarantees they match the schema's layout.
55
+ */
56
+ setImmediates<T extends AnyWgslData>(immediate: TgpuImmediateVar<T>, value: InferInput<T> | ArrayBuffer | ArrayBufferView): void;
44
57
  dispatchWorkgroups(x: number, y?: number, z?: number): void;
45
58
  /**
46
59
  * Dispatches compute workgroups using parameters read from a buffer.
@@ -1,7 +1,7 @@
1
1
  import { $internal } from "../../shared/symbols.js";
2
2
  import { isGPUBuffer } from "../../types.js";
3
3
  import { DISPATCH_INDIRECT_SIZE, resolveIndirectOffset } from "../pipeline/pipelineUtils.js";
4
- import { ComputeDrawState, emitComputeDispatch, recordBindGroup, stampComputePipeline, } from "../pipeline/drawState.js";
4
+ import { ComputeDrawState, emitComputeDispatch, recordBindGroup, selectPipeline, } from "../pipeline/drawState.js";
5
5
  import { unwrapTimestampWrites } from "./attachments.js";
6
6
  // --------------
7
7
  // Implementation
@@ -44,11 +44,14 @@ class TgpuComputePassImpl {
44
44
  emitComputeDispatch(this.#root, internals, pipeline, emit);
45
45
  }
46
46
  setPipeline(pipeline) {
47
- stampComputePipeline(this[$internal].state, pipeline);
47
+ selectPipeline(this[$internal].state, pipeline);
48
48
  }
49
49
  setBindGroup(first, bindGroup) {
50
50
  recordBindGroup(this[$internal].state, first, bindGroup);
51
51
  }
52
+ setImmediates(immediate, value) {
53
+ this[$internal].state.immediates.set(immediate, value);
54
+ }
52
55
  dispatchWorkgroups(x, y, z) {
53
56
  this.#emit((rawPass) => rawPass.dispatchWorkgroups(x, y, z));
54
57
  }
@@ -1,9 +1,11 @@
1
1
  import type { Disarray } from '../../data/dataTypes.ts';
2
2
  import type { PrimitiveOffsetInfo } from '../../data/offsetUtils.ts';
3
- import type { BaseData, WgslArray } from '../../data/wgslTypes.ts';
3
+ import type { AnyWgslData, BaseData, WgslArray } from '../../data/wgslTypes.ts';
4
+ import type { InferInput } from '../../shared/repr.ts';
4
5
  import { $internal } from '../../shared/symbols.ts';
5
6
  import type { TgpuBindGroup, TgpuBindGroupLayout, TgpuLayoutEntry } from '../../tgpuBindGroupLayout.ts';
6
7
  import type { IndexFlag, IndirectFlag, TgpuBuffer, VertexFlag } from '../buffer/buffer.ts';
8
+ import type { TgpuImmediateVar } from '../immediate/immediateVar.ts';
7
9
  import { RenderDrawState } from '../pipeline/drawState.ts';
8
10
  import type { TgpuRenderPipeline } from '../pipeline/renderPipeline.ts';
9
11
  import { type TgpuQuerySet } from '../querySet/querySet.ts';
@@ -58,6 +60,17 @@ export interface TgpuRenderCommands {
58
60
  setVertexBuffer<TData extends WgslArray | Disarray>(vertexLayout: TgpuVertexLayout<TData>, buffer: (TgpuBuffer<TData> & VertexFlag) | GPUBuffer, offset?: number, size?: number): void;
59
61
  /** Sets the current index buffer */
60
62
  setIndexBuffer<TData extends WgslArray | Disarray>(buffer: (TgpuBuffer<TData> & IndexFlag) | GPUBuffer, indexFormat: GPUIndexFormat, offset?: number, size?: number): void;
63
+ /**
64
+ * Provides a value for the given immediate variable, used by subsequent draw calls.
65
+ * The value is captured (copied) at call time; mutating it afterwards has no
66
+ * effect until it is set again. Takes precedence over values held by the
67
+ * pipeline (`pipeline.with(immediate, value)`) and persists across pipeline
68
+ * switches until set again, like any other pass-level state.
69
+ *
70
+ * Passing an `ArrayBuffer` or typed array skips serialization entirely; the bytes
71
+ * are copied verbatim and the caller guarantees they match the schema's layout.
72
+ */
73
+ setImmediates<T extends AnyWgslData>(immediate: TgpuImmediateVar<T>, value: InferInput<T> | ArrayBuffer | ArrayBufferView): void;
61
74
  draw(vertexCount: number, instanceCount?: number, firstVertex?: number, firstInstance?: number): void;
62
75
  drawIndexed(indexCount: number, instanceCount?: number, firstIndex?: number, baseVertex?: number, firstInstance?: number): void;
63
76
  /**
@@ -108,9 +121,10 @@ export interface TgpuRenderPass extends TgpuRenderCommands {
108
121
  endOcclusionQuery(): void;
109
122
  /**
110
123
  * Executes previously recorded {@link GPURenderBundle}s as part of this pass.
111
- * As per the WebGPU spec, this resets the raw pass's pipeline, bind group
112
- * and vertex/index buffer state. The state tracked by this typed pass is
113
- * re-applied on the next draw.
124
+ * As per the WebGPU spec, this resets the raw pass's pipeline, bind group,
125
+ * vertex/index buffer and immediate data state. The state tracked by this
126
+ * typed pass (including values set via `setImmediates`) is re-applied on
127
+ * the next draw.
114
128
  */
115
129
  executeBundles(bundles: Iterable<GPURenderBundle>): void;
116
130
  /** Completes the recording of this render pass */
@@ -2,7 +2,7 @@ import { $internal } from "../../shared/symbols.js";
2
2
  import { isGPUBuffer } from "../../types.js";
3
3
  import { DRAW_INDEXED_INDIRECT_SIZE, DRAW_INDIRECT_SIZE, resolveIndirectOffset, } from "../pipeline/pipelineUtils.js";
4
4
  import { isTexture, isTextureView } from "../texture/texture.js";
5
- import { emitRenderDraw, recordBindGroup, RenderDrawState, stampRenderPipeline, } from "../pipeline/drawState.js";
5
+ import { emitRenderDraw, recordBindGroup, RenderDrawState, selectPipeline, } from "../pipeline/drawState.js";
6
6
  import { isQuerySet } from "../querySet/querySet.js";
7
7
  import { unwrapAttachmentView, unwrapTimestampWrites, } from "./attachments.js";
8
8
  // --------------
@@ -125,7 +125,7 @@ class TgpuRenderCommandsImpl {
125
125
  emitRenderDraw(this.#root, internals, pipeline, usesIndexBuffer, emit);
126
126
  }
127
127
  setPipeline(pipeline) {
128
- stampRenderPipeline(this[$internal].state, pipeline);
128
+ selectPipeline(this[$internal].state, pipeline);
129
129
  }
130
130
  setBindGroup(first, bindGroup) {
131
131
  recordBindGroup(this[$internal].state, first, bindGroup);
@@ -140,6 +140,9 @@ class TgpuRenderCommandsImpl {
140
140
  state.indexBuffer = { buffer, indexFormat, offsetBytes: offset, sizeBytes: size };
141
141
  state.version++;
142
142
  }
143
+ setImmediates(immediate, value) {
144
+ this[$internal].state.immediates.set(immediate, value);
145
+ }
143
146
  draw(vertexCount, instanceCount, firstVertex, firstInstance) {
144
147
  this.#emit(false, (rawPass) => rawPass.draw(vertexCount, instanceCount, firstVertex, firstInstance));
145
148
  }
@@ -189,6 +192,7 @@ class TgpuRenderPassImpl extends TgpuRenderCommandsImpl {
189
192
  const internals = this[$internal];
190
193
  internals.rawPass.executeBundles(bundles);
191
194
  internals.appliedVersion = undefined;
195
+ internals.state.immediates.invalidate();
192
196
  }
193
197
  end() {
194
198
  this[$internal].rawPass.end();
@@ -1,3 +1,6 @@
1
+ import { type ResolvedSnippet } from '../../data/snippet.ts';
2
+ import { $internal, $resolve } from '../../shared/symbols.ts';
3
+ import type { ResolutionCtx, SelfResolvable } from '../../types.ts';
1
4
  /**
2
5
  * Extra declaration that shall be included in final WGSL code,
3
6
  * when resolving objects that use it.
@@ -13,3 +16,11 @@ export interface TgpuDeclare {
13
16
  * It was developed to ensure full compatibility of TypeGPU programs with current and future versions of WGSL.
14
17
  */
15
18
  export declare function declare(declaration: string): TgpuDeclare;
19
+ export declare class TgpuDeclareImpl implements TgpuDeclare, SelfResolvable {
20
+ #private;
21
+ readonly [$internal] = true;
22
+ constructor(declaration: string);
23
+ $uses(dependencyMap: Record<string, unknown>): this;
24
+ [$resolve](ctx: ResolutionCtx): ResolvedSnippet;
25
+ toString(): string;
26
+ }
@@ -15,7 +15,7 @@ export function declare(declaration) {
15
15
  // --------------
16
16
  // Implementation
17
17
  // --------------
18
- class TgpuDeclareImpl {
18
+ export class TgpuDeclareImpl {
19
19
  [$internal] = true;
20
20
  #externals;
21
21
  #declaration;
@@ -0,0 +1,52 @@
1
+ import { type BaseData, type MatData, type ScalarData, type VecData, type WgslStruct } from '../../data/wgslTypes.ts';
2
+ import type { TgpuNamable } from '../../shared/meta.ts';
3
+ import type { InferGPU, InferInput } from '../../shared/repr.ts';
4
+ import type { TgpuSoul } from '../../shared/soul.ts';
5
+ import { $gpuValueOf, $internal, $soul } from '../../shared/symbols.ts';
6
+ export type ImmediateData = ScalarData | VecData | MatData | WgslStruct;
7
+ export interface TgpuImmediateVarSoul<TDataType extends BaseData = BaseData> extends TgpuSoul<'immediate-var'> {
8
+ readonly dataType: TDataType;
9
+ readonly defaultValue: InferInput<TDataType> | undefined;
10
+ }
11
+ export interface TgpuImmediateVar<TDataType extends BaseData = BaseData> extends TgpuNamable {
12
+ readonly [$internal]: true;
13
+ readonly [$soul]: TgpuImmediateVarSoul<TDataType>;
14
+ readonly resourceType: 'immediate-var';
15
+ readonly dataType: TDataType;
16
+ readonly defaultValue: InferInput<TDataType> | undefined;
17
+ readonly [$gpuValueOf]: InferGPU<TDataType>;
18
+ readonly $: InferGPU<TDataType>;
19
+ }
20
+ /**
21
+ * Defines a variable in the 'immediate' address space, set from the CPU on a per-draw
22
+ * (or per-dispatch) basis without the overhead of a buffer.
23
+ *
24
+ * Only one immediate variable can be used in a single shader, and its size is limited
25
+ * by the device's `maxImmediateSize` limit. Requires the `immediate_address_space`
26
+ * WGSL language extension, check `root.enabledWgslLanguageFeatures` for support.
27
+ *
28
+ * @param dataType The schema of the held data's type. Cannot contain arrays, atomics or booleans.
29
+ * @param defaultValue The value used when no override is provided via `pipeline.with(immediate, value)`
30
+ * or `pass.setImmediates`. Captured (serialized) at creation time.
31
+ */
32
+ export declare function immediateVar<TDataType extends ImmediateData>(dataType: TDataType, defaultValue?: InferInput<TDataType>): TgpuImmediateVar<TDataType>;
33
+ export declare function isImmediateVar(value: unknown): value is TgpuImmediateVar;
34
+ /**
35
+ * Validates that the given immediate variable can be used with the given root.
36
+ * @returns The size of the immediate data in bytes, to be passed as
37
+ * `immediateSize` to `device.createPipelineLayout`.
38
+ */
39
+ export declare function validateImmediateUsage(immediate: TgpuImmediateVar, root: {
40
+ readonly enabledWgslLanguageFeatures: ReadonlySet<string>;
41
+ }): number;
42
+ /** Captured bytes of an immediate value, immutable once created */
43
+ export type ImmediateSnapshot = Uint8Array<ArrayBuffer>;
44
+ export declare function createImmediateSnapshot(immediate: TgpuImmediateVar, value: unknown): ImmediateSnapshot;
45
+ /** Captures pass overrides and tracks the last snapshot written to its encoder. */
46
+ export declare class ImmediatePassState {
47
+ #private;
48
+ set(immediate: TgpuImmediateVar, value: unknown): void;
49
+ invalidate(): void;
50
+ /** Pass overrides take precedence over pipeline values and the variable's default. */
51
+ write(pass: GPURenderPassEncoder | GPUComputePassEncoder | GPURenderBundleEncoder, immediate: TgpuImmediateVar, pipelineSnapshots: ReadonlyMap<TgpuImmediateVar, ImmediateSnapshot> | undefined, deduplicate: boolean): void;
52
+ }
@@ -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, bindGroup) {
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, bindGroup];
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
  }