Tutorial 1: Minimal Template¶
This tutorial walks through the simplest possible Zest application - displaying a blank screen using a frame graph.
For all the tutorials we use SDL2 as the windowing library.
Example: examples/SDL2/zest-minimal-template
What You'll Learn¶
- Creating a device and context
- Building a frame graph
- Defining a render pass
- Frame graph caching
The Application Structure¶
Every Zest application follows this pattern:
struct app_t {
zest_device device; // GPU resources (one per app)
zest_context context; // Window/swapchain (one per window)
};
Step 1: Include Headers¶
Important
The ZEST_IMPLEMENTATION defines must be in exactly one .cpp or c file.
Step 2: Initialize Device and Context¶
int main(int argc, char *argv[]) {
app_t app = {};
// Create window
zest_window_data_t window = zest_implsdl2_CreateWindow(
50, 50, // Position
1280, 768, // Size
0, // Maximised
"Minimal Example"
);
// Create device (Vulkan instance, GPU selection, resource pools)
app.device = zest_implsdl2_CreateVulkanDevice(&window, false);
// Configure context options
zest_create_context_info_t create_info = zest_CreateContextInfo();
// Create context (swapchain, command pools, frame resources)
app.context = zest_CreateContext(app.device, &window, &create_info);
// Run main loop
MainLoop(&app);
// Cleanup
zest_DestroyDevice(app.device);
return 0;
}
Step 3: The Main Loop¶
void MainLoop(app_t *app) {
int running = 1;
SDL_Event event;
while (running) {
// Process window events
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) running = 0;
}
// Update device state (deferred cleanup, etc.)
zest_UpdateDevice(app->device);
// Generate cache key for frame graph
zest_frame_graph_cache_key_t cache_key = zest_InitialiseCacheKey(app->context, 0, 0);
// Begin frame (acquires swapchain image)
if (zest_BeginFrame(app->context)) {
// Try to get cached frame graph
zest_frame_graph frame_graph = zest_GetCachedFrameGraph(app->context, &cache_key);
if (!frame_graph) {
// Build new frame graph
frame_graph = BuildFrameGraph(app, &cache_key);
}
// End frame (executes frame graph, submits commands, presents)
zest_EndFrame(app->context, frame_graph);
}
}
}
Key Points¶
zest_UpdateDevice()must be called every framezest_BeginFrame()returnsfalseif window is minimized or the window changes size which results in the swapchain being recreated.- Frame graphs are cached to avoid recompilation
Step 4: Building the Frame Graph¶
zest_frame_graph BuildFrameGraph(app_t *app, zest_frame_graph_cache_key_t *cache_key) {
if (zest_BeginFrameGraph(app->context, "Render Graph", cache_key)) {
// Import the swapchain as a resource
zest_ImportSwapchainResource();
// Create a render pass
zest_BeginRenderPass("Draw Nothing"); {
// Declare output to swapchain
zest_ConnectSwapChainOutput();
// Set the render callback
zest_SetPassTask(BlankScreen, 0);
// End pass definition
zest_EndPass();
}
// Compile and return
return zest_EndFrameGraph();
}
return 0;
}
What Happens Here¶
- Import swapchain - Makes it available for output
- Begin render pass - Starts pass definition
- Connect output - Declares this pass writes to swapchain
- Set task - Assigns the render callback
- End frame graph - Compiles barriers and synchronization
Step 5: The Render Callback¶
void BlankScreen(const zest_command_list command_list, void *user_data) {
// This is where rendering commands go
// With nothing here, we get a blank screen (cleared to the clear color)
}
The callback receives:
command_list- For recording GPU commandsuser_data- Whatever you passed tozest_SetPassTask()
Understanding Frame Graph Caching¶
// Cache key includes context state + optional custom data
zest_frame_graph_cache_key_t key = zest_InitialiseCacheKey(context, custom_data, data_size);
// First frame: cache miss, build graph
zest_frame_graph graph = zest_GetCachedFrameGraph(context, &key);
// graph == NULL
// Build and cache
graph = BuildFrameGraph(...);
// Next frame: cache hit, skip building
graph = zest_GetCachedFrameGraph(context, &key);
// graph == valid cached graph
Use custom data in the cache key for different configurations:
struct config_t { int render_mode; };
config_t config = { .render_mode = 1 };
zest_frame_graph_cache_key_t key = zest_InitialiseCacheKey(context, &config, sizeof(config));
Complete Code¶
See the full source at examples/SDL2/zest-minimal-template/zest-minimal-template.cpp.
Next Steps¶
- Tutorial 2: Adding ImGui - Add a user interface
- Frame Graph Concept - Deep dive on frame graphs