Symbols in this topic
UniformPool
Dynamic-offset ring allocator for many per-draw uniforms. Allocate a typed UniformSlot once, push values each frame, call endFrame(), and pass returned offsets through p.draw(draw, { offsets }).
Import
TypeScript
1
2
import { UniformPool } from "vgpu/core";
import type { UniformPoolOptions, UniformLayout, UniformSlot } from "vgpu/core";Signature
TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import type { Device } from "vgpu/core";
interface UniformPoolOptions {
readonly capacityBytes?: number;
}
interface UniformLayout<T> {
readonly size: number;
readonly bindings?: readonly GPUBindGroupLayoutEntry[];
readonly bindGroupLayout?: GPUBindGroupLayout;
encode(value: T, dst: ArrayBuffer, byteOffset: number): void;
}
interface UniformSlot<T> {
readonly pool: UniformPool;
readonly layout: UniformLayout<T>;
readonly bindGroup: GPUBindGroup;
readonly bindGroupLayout: GPUBindGroupLayout;
readonly gpu: GPUBuffer;
readonly stride: number;
push(value: T): number;
pushBytes(bytes: ArrayBufferView<ArrayBuffer>): number;
}
declare class UniformPool {
readonly device: Device;
readonly minOffsetAlignment: number;
readonly capacityBytes: number;
readonly maxUniformBindingSize: number;
readonly cpuMirror: ArrayBuffer;
readonly gpu: GPUBuffer;
constructor(device: Device, opts?: UniformPoolOptions);
get usedBytes(): number;
get disposed(): boolean;
alloc<T>(layout: UniformLayout<T>): UniformSlot<T>;
push<T>(slot: UniformSlot<T>, value: T): number;
pushBytes(slot: UniformSlot<unknown>, bytes: ArrayBufferView<ArrayBuffer>): number;
beginFrame(frameIndex: number): void;
endFrame(): void;
assertReadyForSubmit(where: string): void;
dispose(): void;
}Parameters
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
| device | Device | ✔ | — | Core device. Device limits determine alignment and max binding size. |
| opts | UniformPoolOptions | ✖ | {} | Pool capacity options. |
| opts.capacityBytes | number | ✖ | 4 * 1024 * 1024 | CPU mirror and GPU buffer size. |
| layout.size | number | ✔ | — | Logical uniform byte size before stride alignment. Must fit both pool capacity and maxUniformBufferBindingSize. |
| layout.bindings | readonly GPUBindGroupLayoutEntry[] | ✖ | Binding 0 uniform, dynamic offset, visibility vertex+fragment | Used only when layout.bindGroupLayout is omitted. |
| layout.bindGroupLayout | GPUBindGroupLayout | ✖ | New layout from layout.bindings or default binding 0 | Usually draw.layout(group, { dynamicOffsets: true }) so the draw pipeline and slot agree. |
| layout.encode | (value, dst, byteOffset) => void | ✔ | — | Writes one value into the pool CPU mirror at byteOffset. |
| alloc.layout | UniformLayout<T> | ✔ | — | Layout used for one reusable slot. Slot stride is roundUp(layout.size, minUniformBufferOffsetAlignment). |
| push.slot | UniformSlot<T> | ✔ | — | Must be allocated by the same pool. |
| push.value | T | ✔ | — | Encoded into the CPU mirror; returned offset is passed as dynamic offset. |
| pushBytes.bytes | ArrayBufferView<ArrayBuffer> | ✔ | — | Must have byte length exactly equal to slot.layout.size. |
| beginFrame.frameIndex | number | ✔ | — | Currently unused marker; call before pushes to reset the ring head to 0. |
| assertReadyForSubmit.where | string | ✔ | — | Error context if pushes are unflushed. |
Returns: Constructor returns UniformPool; alloc() returns UniformSlot<T>; push() / pushBytes() return the dynamic byte offset; usedBytes returns current ring head; lifecycle methods return void.
Throws: VGPU-UNIFORM-POOL-OVERFLOW when a push would exceed capacityBytes; VGPU-UNIFORM-LAYOUT-OVERSIZED when layout.size exceeds pool capacity or device maxUniformBufferBindingSize; VGPU-CORE-INVALID-USAGE when using a disposed pool, pushing a slot from another pool, pushBytes length mismatches, or submitting before endFrame() flushes unflushed pushes.
Examples
TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { init, clock, draw, frame, target } from "vgpu/mock";
import { UniformPool, type UniformLayout } from "vgpu/core";
const gpu = await init();
const colorTarget = target(gpu, { size: [32, 32] });
const drawable = draw(gpu, { shader: `
struct Object { model: mat4x4f }
@group(0) @binding(0) var<uniform> object: Object;
@vertex fn vs_main(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4f {
var p = array<vec2f, 3>(vec2f(-1, -1), vec2f(3, -1), vec2f(-1, 3));
return object.model * vec4f(p[vi], 0, 1);
}
@fragment fn fs_main() -> @location(0) vec4f { return vec4f(1); }
` });
type ObjectUniforms = { model: Float32Array };
const objectLayout: UniformLayout<ObjectUniforms> = {
size: 64,
bindGroupLayout: drawable.layout(0, { dynamicOffsets: true }),
encode(value, dst, byteOffset) {
new Float32Array(dst, byteOffset, 16).set(value.model);
},
};
const pool = new UniformPool(gpu.device, { capacityBytes: 1 << 20 });
const slot = pool.alloc(objectLayout);
drawable.group(0, slot.bindGroup);
pool.beginFrame(clock(gpu).frameCount);
const offset = slot.push({ model: new Float32Array(16) });
pool.endFrame();
frame(gpu, (currentFrame) => currentFrame.pass({ target: colorTarget }, (pass) => pass.draw(drawable, { offsets: { 0: [offset] } })));TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { init } from "vgpu/mock";
import { UniformPool, type UniformLayout } from "vgpu/core";
const gpu = await init();
const pool = new UniformPool(gpu.device, { capacityBytes: 1024 });
const layout: UniformLayout<Float32Array> = {
size: 16,
encode(value, dst, byteOffset) { new Float32Array(dst, byteOffset, 4).set(value); },
};
const slot = pool.alloc(layout);
pool.beginFrame(0);
const offset = slot.pushBytes(new Float32Array([1, 0, 0, 0]));
pool.endFrame();
void offset;Notes
- Call
beginFrame()before pushes andendFrame()before submitting commands that use the returned offsets. - A dynamic-offset bind group stores
offset: 0; the per-draw offset comes fromDrawCallOptions.offsets. - The pool is a ring for transient per-frame data, not long-lived storage. Persistent globals usually fit
UniformorSharedUniformsbetter. - See also:
Draw.layout,DrawCallOptions.offsets,FramePass.draw,Uniform,StructuredUniform.