Passes¶
Passes are the building blocks of a frame graph. Each pass represents a unit of GPU work with defined inputs and outputs.
Pass Types¶
Render Pass¶
For graphics pipeline operations (drawing geometry):
zest_BeginRenderPass("Draw Scene"); {
zest_ConnectOutput(color_target);
zest_ConnectOutput(depth_target);
zest_SetPassTask(DrawCallback, user_data);
zest_EndPass();
}
Render passes use the graphics queue and support: - Vertex/index buffer binding - Graphics pipeline binding - Draw commands
Compute Pass¶
For compute shader dispatch:
zest_compute compute = zest_GetCompute(compute_handle);
zest_BeginComputePass(compute, "Particle Sim"); {
zest_ConnectInput(particle_positions);
zest_ConnectOutput(particle_positions); // Read-modify-write
zest_SetPassTask(ComputeCallback, user_data);
zest_EndPass();
}
Compute passes: - Require a zest_compute object (contains the compute pipeline) - Can run on compute or graphics queue - Support storage buffer/image operations
Transfer Pass¶
For data uploads and copies:
zest_BeginTransferPass("Upload"); {
zest_ConnectOutput(instance_buffer);
zest_SetPassTask(UploadCallback, layer);
zest_EndPass();
}
Transfer passes: - Handle CPU-to-GPU data transfers - Can run on dedicated transfer queue if available - Used for staging buffer uploads
Resource Connections¶
Inputs and Outputs¶
zest_ConnectInput(resource); // Pass reads from this resource
zest_ConnectOutput(resource); // Pass writes to this resource
The frame graph compiler uses these connections to: - Determine execution order - Insert memory barriers - Track resource state transitions
Swapchain Output¶
To write to the swapchain (final presentation):
This marks the pass as contributing to the final image. Passes without a path to the swapchain may be culled.
Input/Output Groups¶
For multiple related resources:
zest_resource_group group = zest_CreateResourceGroup();
zest_AddResourceToGroup(group, albedo);
zest_AddResourceToGroup(group, normal);
zest_AddResourceToGroup(group, depth);
zest_BeginRenderPass("G-Buffer"); {
zest_ConnectOutputGroup(group);
zest_SetPassTask(GBufferCallback, app);
zest_EndPass();
}
Pass Tasks (Callbacks)¶
Each pass requires a callback function that executes the actual GPU commands:
void MyRenderCallback(const zest_command_list cmd, void* user_data) {
app_t* app = (app_t*)user_data;
// Bind pipeline
zest_pipeline pipeline = zest_GetPipeline(app->pipeline, cmd);
zest_cmd_BindPipeline(cmd, pipeline);
// Set viewport/scissor
zest_cmd_SetScreenSizedViewport(cmd, 0.0f, 1.0f);
// Bind vertex buffer
zest_cmd_BindVertexBuffer(cmd, 0, 1, buffer);
// Draw
zest_cmd_Draw(cmd, vertex_count, 1, 0, 0);
}
// In frame graph
zest_SetPassTask(MyRenderCallback, app);
Callback Parameters¶
| Parameter | Type | Description |
|---|---|---|
cmd | zest_command_list | Command buffer for recording commands |
user_data | void* | User data passed to zest_SetPassTask |
Utility Callbacks¶
For passes that need no commands (e.g., just clears):
zest_BeginRenderPass("Clear Pass"); {
zest_ConnectOutput(color_target);
zest_SetResourceClearColor(color_target, 0.0f, 0.0f, 0.0f, 1.0f);
zest_SetPassTask(zest_EmptyRenderPass, NULL);
zest_EndPass();
}
zest_EmptyRenderPass is a built-in callback that records no commands.
Pass Options¶
Preventing Pass Culling¶
By default, passes without connections to the swapchain are culled:
zest_BeginRenderPass("Debug Pass"); {
zest_DoNotCull(); // Keep this pass even if unused
zest_SetPassTask(DebugCallback, app);
zest_EndPass();
}
Custom Descriptor Sets¶
Pass additional descriptor sets to a pass:
This is useful when you need descriptor sets beyond the global bindless set.
Accessing Resources in Callbacks¶
Within a pass callback, retrieve connected resources:
void MyCallback(const zest_command_list cmd, void* user_data) {
// Get input resource by name
zest_resource_node input = zest_GetPassInputResource(cmd, "ShadowMap");
// Get output resource by name
zest_resource_node output = zest_GetPassOutputResource(cmd, "ColorTarget");
// Get buffer directly
zest_buffer buf = zest_GetPassInputBuffer(cmd, "InstanceData");
zest_buffer out_buf = zest_GetPassOutputBuffer(cmd, "Results");
// Use resources...
}
Complete Example¶
void RenderScene(const zest_command_list cmd, void* user_data) {
app_t* app = (app_t*)user_data;
// Get pipeline variant for current render state
zest_pipeline pipeline = zest_GetPipeline(app->mesh_pipeline, cmd);
zest_cmd_BindPipeline(cmd, pipeline);
// Set viewport
zest_cmd_SetScreenSizedViewport(cmd, 0.0f, 1.0f);
// Bind vertex/index buffers
zest_cmd_BindVertexBuffer(cmd, 0, 1, app->vertex_buffer);
zest_cmd_BindIndexBuffer(cmd, app->index_buffer);
// Push constants
push_constants_t pc = {
.mvp = app->camera_mvp,
.texture_index = app->texture_index
};
zest_cmd_SendPushConstants(cmd, &pc, sizeof(pc));
// Draw indexed
zest_cmd_DrawIndexed(cmd, app->index_count, 1, 0, 0, 0);
}
// In frame graph setup
zest_BeginRenderPass("Scene"); {
zest_ConnectOutput(color_target);
zest_ConnectOutput(depth_target);
zest_SetPassTask(RenderScene, app);
zest_EndPass();
}
Best Practices¶
- Name passes descriptively - Names appear in debug output and profilers
- Minimize pass count - Combine passes when logical to reduce overhead
- Declare all resource dependencies - Missing connections cause undefined behavior
- Use groups for Multi Render Target (MRT) - Resource groups simplify multiple render target setups
- Let culling work - Avoid
zest_DoNotCullunless necessary
See Also¶
- Resources - Resource types and management
- Execution - Building and executing frame graphs
- Frame Graph API - Function reference