Passes & resources
Connect graphics and compute work with explicit inputs, outputs, history, and storage buffers.
Graphics passes
{
"name": "exposure",
"fragment": "shaders/exposure.fsh",
"reads": { "Scene": "$scene" },
"writes": ["display"]
}
Each pass needs name, reads, writes, and exactly one of fragment or compute. Graphics uses a generated fullscreen triangle, with vec2 texture coordinates at location 0. Output locations follow writes: 1–8 equal-dimension images, cleared before drawing.
Declare graphics samplers with reads keys; Sulkan manages their bindings.
Input sources
| Source | Meaning |
|---|---|
$scene | Color before native post-processing |
$depth | World depth before the hand clear |
$handDepth | Subsequent main depth, including the hand |
$terrainOpaque | Color immediately before Sodium translucent terrain |
$terrainOpaqueDepth | Corresponding terrain-boundary depth |
$texture/name | Declared texture asset |
resource.name | Current-frame graph image |
resource.name@previous | Previous image; requires history |
Sulkan…, Output0, and gl_… are reserved binding names. Terrain snapshots preserve one terrain boundary, not a second full world render or every offscreen transparency target.
Scheduling and history
Each post-scene image has one writer. Reads establish dependencies; declaration order does not. Use new names for successive outputs. Current-frame feedback and cycles fail. Unused passes are culled from the presentation root.
Scene targets cannot also have post writers. Compatible transient images with disjoint lifetimes may share storage; history and scene targets have dedicated storage.
Previous images/buffers start zeroed. Resize, world change, large camera motion/rotation, FOV change, and long frame gaps invalidate history. Honor CameraDeltaAndHistoryValid.w. History swaps only after successful submission.
Compute passes
Add a matching display resource and set present to it in the full manifest:
{
"name": "copy",
"compute": "shaders/copy.csh",
"localSize": [8, 8, 1],
"reads": { "Source": "$scene" },
"writes": ["display"]
}
#version 450
void main() {
ivec2 pixel = ivec2(gl_GlobalInvocationID.xy);
ivec2 extent = imageSize(Output0);
if (any(greaterThanEqual(pixel, extent))) return;
vec2 uv = (vec2(pixel) + 0.5) / vec2(extent);
imageStore(Output0, pixel, texture(Source, uv));
}
Sulkan generates the frame block, samplers, Output0 storage image, and local workgroup declaration. Do not redeclare them, including through the frame include.
Default localSize is [8,8,1]. Dispatch defaults to the first output’s dimensions; dispatch: [x,y,z] gives explicit invocation extents. It rounds up to complete workgroups: always bounds-check.
Storage formats include RGBA8_UNORM, RGBA16_FLOAT, RGBA32_FLOAT, R32_FLOAT, R32_UINT, R16_UINT, R8_UINT, R32_SINT, RGBA32_UINT, and RGBA32_SINT, subject to device support.
Storage buffers
Declare buffers at root, then bind them in compute:
{
"buffers": { "lighting.blocks": { "bytes": 256, "history": true } }
}
{
"name": "fill",
"compute": "shaders/fill.csh",
"localSize": [16, 1, 1],
"dispatch": [64, 1, 1],
"reads": {},
"writes": [],
"buffers": { "Blocks": { "resource": "lighting.blocks", "access": "write" } }
}
The shader sees Blocks.data, an unsized std430 uint[]. Use bit conversions for packed floats. Sizes are multiples of 4, from 4 bytes to 512 MiB. At most 64 buffers are allowed.
access is read or write. Write permits intra-dispatch reads and atomics and starts zeroed each frame. Each current buffer has one writer. Reads can reference @previous if history is enabled.
Buffer-only passes require explicit dispatch and a buffer writer. Connect that buffer to a later pass that contributes to present, or the writer is culled. No cross-workgroup synchronization is supplied: split dependent work into separate passes.
Conditional and filtered reads
readConditions omits bindings when options are disabled. Lists are OR clauses; nested lists are AND clauses. Positive option values count as enabled.
{
"readConditions": { "Bloom": ["BLOOM", ["WATER", "REFLECTIONS"]] },
"linearReads": ["Bloom"]
}
Declare the sampler in reads and options at root. Guard sampling with matching GLSL defines when bindings can disappear. linearReads accepts eligible graph color resources, not arbitrary external inputs.