Give each owner a layout, then share them to fit this pipeline within four bind groups. Highlighted bindings can change through a bind group; fixed resources require new pipelines.
A fixed resource such as root.createUniform(…) goes in the pipeline’s automatic bind group. Replacing that resource requires another pipeline, even for a shadow map resized by a quality setting. A tgpu.bindGroupLayout lets you swap resources, but uses one of each pipeline’s 4 bind groups.
With bindings, a module declares what its shaders read without owning a group. Your app combines module bindings and its own bindings in one layout, then sets one group per pass. Resources in that layout can change without rebuilding pipelines.
import{
import bindings
bindings}from'@typegpu/kit';
// A fog module declares what its shaders read, beside them
const
constvolume:bindings.Layout<{
readonly density:{
readonly texture:d.WgslTexture3d<d.F32>;
};
readonly smooth:{
readonly sampler:"filtering";
};
}>
volume=
import bindings
bindings.
functionlayout<{
readonly density:{
readonly texture:d.WgslTexture3d<d.F32>;
};
readonly smooth:{
readonly sampler:"filtering";
};
}>(entries:{
readonly density:{
readonly texture:d.WgslTexture3d<d.F32>;
};
readonly smooth:{
readonly sampler:"filtering";
};
}):bindings.Layout<{
readonly density:{
readonly texture:d.WgslTexture3d<d.F32>;
};
readonly smooth:{
readonly sampler:"filtering";
};
}>
Declares bindings for module shaders from the entries that tgpu.bindGroupLayout takes. Define
the layout once beside the shaders. A part made with bind can bind its resources directly.
Call share before creating pipelines to put the part in a shared layout.
@param ― entries the bindings, as tgpu.bindGroupLayout takes them
@returns ― a layout whose bind makes a part from resources
Combines parts from modules or your own code in one layout. Call it before creating pipelines.
Set its group on each pass with pass.setBindGroup(shared.layout, shared.group()), or give it to
passes.create as shared, which sets it in every pass it begins. Every pipeline in that pass
that reads a part then uses the shared group.
@param ― parts parts from any modules and your own, by key
@returns ― a layout you set on each pass, with the group for the parts' resources
Records a frame into one encoder and submits once, with a clock. Borrows the root. The
synchronous recorder borrows each frame's encoder.
@submits ― always once a frame after your recorder returns, and again at each frame.submit()
@param ― record your frame, recorded into each frame's encoder
@returns ― a loop you run with each frame's time and destroy
create(
constroot:TgpuRoot
root,({
encoder: TgpuCommandEncoder
Records commands for submission after the recorder returns. submit() replaces this encoder.
Read it at each call instead of keeping it, and let the frame submit it.
encoder})=>{
const
constpass:TgpuComputePass
pass=
encoder: TgpuCommandEncoder
Records commands for submission after the recorder returns. submit() replaces this encoder.
Read it at each call instead of keeping it, and let the frame submit it.
Contains every part's entries. Each entry is named key_name using the part's key in share.
layout,
constshared:bindings.Shared
shared.
Shared.group(): TgpuBindGroup
Returns the group for the current resources, for pass.setBindGroup(shared.layout, …).
Creates a group only when a resource has changed. Otherwise, it allocates nothing.
Replaces resources without creating pipelines. The next shared group uses the replacements.
Throws if a pipeline already binds the resources directly, because that pipeline would keep
the old resources.
Until an app shares it, a part uses fixed bindings without a separate group. These require TypeGPU resources. A raw GPUBuffer throws when the pipeline resolves. replace throws once a pipeline holds fixed resources, so a part whose resources change asks for its share.
bindings.share(root, parts) combines parts in one app-owned layout. Each entry uses its part’s key as a prefix, as in fog_density, fog_smooth and shadow_map.
Call it before creating the pipelines that read the parts. It checks every part before taking any, and throws for one that already has an owner or that a pipeline holds as fixed bindings.
A shared part’s own inject() reads from the shared layout. shared.inject() fills every part, for one with no fill of its own like the noise cache.
pass.setBindGroup(shared.layout, shared.group()) covers every pipeline in the pass. Without it, the dispatch or draw throws Missing bind groups for layouts. A description of passes given { shared } sets it in every pass it begins.
part.replace({ density: bigger }) swaps resources for the next shared.group(), with no new pipelines.
shared.group() creates a group only when an entry’s identity changes. A steady frame allocates nothing. Call it after a swap to allocate the group before the next frame.
Call replace between frames, then destroy the old resource. Already submitted work finishes with it. A later pass using the destroyed resource fails at submit.
A pipeline that holds a part as fixed bindings keeps the resources it was built with. A part whose resources change while the program runs, such as a texture that follows the canvas, needs its share before that pipeline builds. Give it unshared, a message naming the share to add, and a pipeline that reads the part without one throws it as it builds, at the first frame or warm-up rather than at the first change:
Makes a pipeline that reads the part throw this message as it builds, unless a
bindings.share holds the part. Give it to a part whose resources change while the program
runs, and name the call that shares it, so a missing share fails before the first change.
Default none: the pipeline binds the resources in its automatic group, and replace throws
after that.
unshared:'The fog grows: `bindings.share(root, { fog })` before the pipelines that read it.'},
);
Ambient occlusion makes its part this way, since a new depth size replaces its texture.
A shadow replaces its map only when you call its resize. An unshared shadow works until then, and resize throws at that call before it changes anything.
Used to instantiate caches for perlin noise generation, which reduce the amount of redundant calculations
if sampling is done more than once. Their domain can be changed at runtime, which makes this cache
dynamic (as opposed to perlin3d.staticCache, which is simpler at the cost of rigidity).
@param ―
options A set of general options for instances of this cache configuration.
Declares bindings that another layout can hold, such as one made by share. A bound
bindings.layout implements this interface. Other modules can implement it too, such as the
dynamic cache from @typegpu/noise with { entries: config.layout, resources: cache.bindings, inject: config.inject }.
Combines parts from modules or your own code in one layout. Call it before creating pipelines.
Set its group on each pass with pass.setBindGroup(shared.layout, shared.group()), or give it to
passes.create as shared, which sets it in every pass it begins. Every pipeline in that pass
that reads a part then uses the shared group.
@param ― parts parts from any modules and your own, by key
@returns ― a layout you set on each pass, with the group for the parts' resources
A pipeline with more groups than the device’s maxBindGroups, 4 on every device, fails with an error such as bindGroupLayoutCount (5) is larger than the maximum allowed (4). A census counts each pipeline’s groups, including the automatic group.
Sharing keeps the same binding count per stage. A shared layout exposes every entry to every stage, as the automatic group does, so a pipeline that reads one part counts every part. pipelines.compile names the bindings of a pipeline that goes over.
Per-draw bindings, such as a mesh’s textures, keep their own layout.
Groups owned by one module stay separate. The neighbour grid’s sort uses 4 groups of its own.