Pipelines¶
Zest uses a pipeline template system. Instead of creating GPU pipelines directly, you configure templates that are compiled on-demand when first used. This deferred compilation approach simplifies pipeline management - you only need to specify what you want, and Zest handles the actual device pipeline creation and caching.
Pipeline Templates vs Built Pipelines¶
| Pipeline Template | Built Pipeline |
|---|---|
| Configuration only | Actual GPU pipeline |
| Stored in device | Created per command list |
| Reusable | Cached internally |
| Configured once | Used for drawing |
// 1. Create template (once at startup)
zest_pipeline_template my_template = zest_CreatePipelineTemplate(device, "my_pipeline");
// ... configure ...
// 2. Build pipeline (in render callback)
zest_pipeline pipeline = zest_GetPipeline(my_template, cmd);
zest_cmd_BindPipeline(cmd, pipeline);
Creating a Pipeline Template¶
zest_pipeline_template sprite_template = zest_CreatePipelineTemplate(device, "sprite_pipeline");
// Set shaders
zest_SetPipelineShaders(sprite_template, vertex_shader, fragment_shader);
// Configure vertex input
zest_AddVertexInputBindingDescription(sprite_template, 0, sizeof(vertex_t), zest_input_rate_vertex);
zest_AddVertexAttribute(sprite_template, 0, 0, zest_format_r32g32b32_sfloat, offsetof(vertex_t, position));
zest_AddVertexAttribute(sprite_template, 0, 1, zest_format_r32g32_sfloat, offsetof(vertex_t, uv));
zest_AddVertexAttribute(sprite_template, 0, 2, zest_format_r8g8b8a8_unorm, offsetof(vertex_t, color));
// Configure state
zest_SetPipelineTopology(sprite_template, zest_topology_triangle_list);
zest_SetPipelineCullMode(sprite_template, zest_cull_mode_none);
zest_SetPipelineDepthTest(sprite_template, true, true);
zest_SetPipelineBlend(sprite_template, zest_AlphaBlendState());
Shaders¶
Creating Shaders¶
// From file (auto-compiles if needed)
zest_shader_handle vert = zest_CreateShaderFromFile(
device,
"shaders/sprite.vert", // Source file in GLSL
"sprite.vert.spv", // Cache name
zest_vertex_shader, // Shader type
false // Whether to disable caching (false = enable caching)
);
// From pre-compiled SPIR-V file
zest_shader_handle frag = zest_CreateShaderFromSPVFile(
device,
"shaders/sprite.frag.spv",
zest_fragment_shader
);
// From SPIR-V in memory
zest_shader_handle comp = zest_AddShaderFromSPVMemory(
device,
"compute.spv", // Name
spirv_data, // Buffer
spirv_size, // Size
zest_compute_shader // Shader type
);
Setting Shaders¶
// Graphics pipeline - both shaders at once
zest_SetPipelineShaders(my_template, vertex_shader, fragment_shader);
// Or set shaders separately
zest_SetPipelineVertShader(my_template, vertex_shader);
zest_SetPipelineFragShader(my_template, fragment_shader);
// Combined vertex+fragment shader (single file containing both)
zest_SetPipelineShader(my_template, combined_shader);
Note: Compute shaders are handled separately through zest_CreateCompute() rather than pipeline templates. See the Compute Pipelines section.
Vertex Input¶
Vertex input describes how vertex data is organized in memory and how it maps to shader inputs. A binding represents a buffer of vertex data, while attributes describe individual data fields within that buffer.
Binding Descriptions¶
// Per-vertex data (binding 0)
zest_AddVertexInputBindingDescription(
my_template,
0, // Binding index
sizeof(vertex_t), // Stride
zest_input_rate_vertex // Per vertex
);
// Per-instance data (binding 1)
zest_AddVertexInputBindingDescription(
my_template,
1,
sizeof(instance_t),
zest_input_rate_instance // Per instance
);
Attribute Descriptions¶
// Position at location 0
zest_AddVertexAttribute(
my_template,
0, // Binding
0, // Location
zest_format_r32g32b32_sfloat, // Format
offsetof(vertex_t, position) // Offset
);
// UV at location 1
zest_AddVertexAttribute(my_template, 0, 1, zest_format_r32g32_sfloat, offsetof(vertex_t, uv));
// Color at location 2 (packed)
zest_AddVertexAttribute(my_template, 0, 2, zest_format_r8g8b8a8_unorm, offsetof(vertex_t, color));
Clearing Vertex Input¶
Pipeline State¶
Topology¶
zest_SetPipelineTopology(my_template, zest_topology_triangle_list); // Default
zest_SetPipelineTopology(my_template, zest_topology_triangle_strip);
zest_SetPipelineTopology(my_template, zest_topology_line_list);
zest_SetPipelineTopology(my_template, zest_topology_point_list);
Culling¶
zest_SetPipelineCullMode(my_template, zest_cull_mode_back); // Cull back faces
zest_SetPipelineCullMode(my_template, zest_cull_mode_front); // Cull front faces
zest_SetPipelineCullMode(my_template, zest_cull_mode_none); // No culling (Default)
Front Face¶
zest_SetPipelineFrontFace(my_template, zest_front_face_counter_clockwise); // Default
zest_SetPipelineFrontFace(my_template, zest_front_face_clockwise);
Polygon Mode¶
zest_SetPipelinePolygonFillMode(my_template, zest_polygon_mode_fill); // Solid (Default)
zest_SetPipelinePolygonFillMode(my_template, zest_polygon_mode_line); // Wireframe
zest_SetPipelinePolygonFillMode(my_template, zest_polygon_mode_point); // Points
Depth Testing¶
// Enable depth test and write
zest_SetPipelineDepthTest(my_template, true, true);
// Depth test only (no write)
zest_SetPipelineDepthTest(my_template, true, false);
// Disable depth
zest_SetPipelineDepthTest(my_template, false, false);
Blend States¶
Built-in Blend States¶
zest_SetPipelineBlend(my_template, zest_BlendStateNone()); // No blending
zest_SetPipelineBlend(my_template, zest_AlphaBlendState()); // Standard alpha
zest_SetPipelineBlend(my_template, zest_AdditiveBlendState()); // Additive
zest_SetPipelineBlend(my_template, zest_PreMultiplyBlendState()); // Pre-multiplied
zest_SetPipelineBlend(my_template, zest_ImGuiBlendState()); // ImGui style
Custom Blend State¶
zest_color_blend_attachment_t blend = {
.blend_enable = ZEST_TRUE,
.src_color_blend_factor = zest_blend_factor_src_alpha,
.dst_color_blend_factor = zest_blend_factor_one_minus_src_alpha,
.color_blend_op = zest_blend_op_add,
.src_alpha_blend_factor = zest_blend_factor_one,
.dst_alpha_blend_factor = zest_blend_factor_one_minus_src_alpha,
.alpha_blend_op = zest_blend_op_add,
.color_write_mask = zest_color_component_r_bit | zest_color_component_g_bit |
zest_color_component_b_bit | zest_color_component_a_bit
};
zest_SetPipelineBlend(my_template, blend);
Copying Templates¶
Create variants from existing templates:
// Copy and modify
zest_pipeline_template wireframe = zest_CopyPipelineTemplate("wireframe", solid_template);
zest_SetPipelinePolygonFillMode(wireframe, zest_polygon_mode_line);
// Copy with different shaders
zest_pipeline_template shadow = zest_CopyPipelineTemplate("shadow", main_template);
zest_SetPipelineShaders(shadow, shadow_vert, shadow_frag);
Pipeline Layouts¶
Pipeline layouts define the interface between shaders and descriptor sets (resources like buffers and textures). Zest provides a default bindless layout that works for most use cases. Custom layouts are not currently fully supported, although most of the API is there some of the internals need updating to allow it.
zest_pipeline_layout_info_t layout_info = zest_NewPipelineLayoutInfo(device);
zest_AddPipelineLayoutDescriptorLayout(&layout_info, my_descriptor_layout);
zest_pipeline_layout layout = zest_CreatePipelineLayout(&layout_info);
Using Pipelines¶
void RenderCallback(const zest_command_list cmd, void* user_data) {
app_t* app = (app_t*)user_data;
// Build pipeline for this command list, once built the pipeline is cached and subsequent
// calls will retrieve the pipeline from the cache instead.
zest_pipeline pipeline = zest_GetPipeline(app->pipeline_template, cmd);
// Bind pipeline
zest_cmd_BindPipeline(cmd, pipeline);
// Set viewport/scissor (min_depth, max_depth)
zest_cmd_SetScreenSizedViewport(cmd, 0.0f, 1.0f);
// Draw...
zest_cmd_Draw(cmd, vertex_count, instance_count, 0, 0);
}
Compute Pipelines¶
// Create compute shader
zest_shader_handle comp = zest_CreateShaderFromFile(
device, "compute.comp", "compute.spv", zest_compute_shader, false);
// Create compute handle
zest_compute_handle compute = zest_CreateCompute(device, "particle_sim", comp);
// Use in compute pass
zest_compute compute_obj = zest_GetCompute(compute);
zest_BeginComputePass(compute_obj, "Simulate"); {
zest_SetPassTask(ComputeCallback, app);
zest_EndPass();
}
Best Practices¶
- Create templates at startup - Not during rendering
- Use
zest_CopyPipelineTemplate- For variants with minor differences - Cache pipelines are automatic -
zest_GetPipelinehandles caching - Match vertex input to shaders - Locations must align
See Also¶
- Shaders Guide - Shader compilation details
- Pipeline API - Complete function reference
- Instancing Tutorial - Pipeline with instancing