Render Graph (Internal)
This document describes the render graph format used by the engine. Render graphs live in a global core catalog and are referenced by realms through logical render_graph_id. If a bound graph is missing or invalid, the core executes a safe fallback graph for that realm kind. Render graphs are realm-scoped (via binding), not window-scoped.
Goals
- Host control: The host defines the render sequence and dependencies.
- Logical IDs only: The host never sees GPU handles or internal IDs.
- Performance: Minimal per-frame overhead, cacheable plan, and reusable resources.
- Robustness: Invalid or missing graphs fall back to a default graph.
High-Level Flow
- The host upserts a render graph resource using logical IDs.
- The core validates the graph and compiles an execution plan.
- A realm binds to that graph by
render_graph_id. - On missing/invalid bindings, the core uses a fallback graph.
Execution order is strict: passes run exactly in graph topological order (plan.order).
The core does not inject implicit render passes outside the bound graph.
Graph Structure
Graph
| Field | Type | Description |
|---|---|---|
| graphId | LogicalId | Logical graph identifier (cache key) |
| nodes | Node[] | Render nodes |
| edges | Edge[] | Dependencies between nodes |
| resources | Resource[] | Declared resources |
| fallback | bool | Reserved metadata field (currently not used in runtime decisions) |
Node
| Field | Type | Description |
|---|---|---|
| nodeId | LogicalId | Logical node identifier |
| passId | string | Pass type (e.g., "forward") |
| inputs | LogicalId[] | Resource IDs read by this node |
| outputs | LogicalId[] | Resource IDs written by this node |
| params | Map | Optional parameters (clear, flags, etc.) |
Resource
| Field | Type | Description |
|---|---|---|
| resId | LogicalId | Logical resource identifier |
| kind | string | "texture", "buffer", "attachment" (defaults to "texture") |
| lifetime | string | "frame" or "persistent" (defaults to "frame") |
| aliasGroup | LogicalId? | Optional alias group for memory reuse |
Edge
| Field | Type | Description |
|---|---|---|
| fromNodeId | LogicalId | Dependency source |
| toNodeId | LogicalId | Dependency target |
| reason | string? | Optional: "read_after_write", "write_after_read" |
LogicalId
Logical IDs can be strings or numeric values.
Known Pass IDs
shadowlight-cullskyboxforwardoutlinessaossao-blurbloompostcomposeui
Bloom uses the emissive output from the forward pass when available and falls back to the HDR color buffer otherwise.
Minimal Example
{
"graphId": "main_render",
"nodes": [
{
"nodeId": "shadow_pass",
"passId": "shadow",
"inputs": [],
"outputs": ["shadow_atlas"]
},
{
"nodeId": "forward_pass",
"passId": "forward",
"inputs": ["shadow_atlas"],
"outputs": ["hdr_color", "depth"]
},
{
"nodeId": "outline_pass",
"passId": "outline",
"inputs": ["depth"],
"outputs": ["outline_color"]
},
{
"nodeId": "ssao_pass",
"passId": "ssao",
"inputs": ["depth"],
"outputs": ["ssao_raw"]
},
{
"nodeId": "ssao_blur_pass",
"passId": "ssao-blur",
"inputs": ["ssao_raw", "depth"],
"outputs": ["ssao_blur"]
},
{
"nodeId": "bloom_pass",
"passId": "bloom",
"inputs": ["hdr_color"],
"outputs": ["bloom_color"]
},
{
"nodeId": "post_pass",
"passId": "post",
"inputs": ["hdr_color", "outline_color", "ssao_blur", "bloom_color"],
"outputs": ["post_color"]
},
{
"nodeId": "compose_pass",
"passId": "compose",
"inputs": ["post_color"],
"outputs": ["swapchain"]
}
],
"edges": [
{ "fromNodeId": "shadow_pass", "toNodeId": "forward_pass" },
{ "fromNodeId": "forward_pass", "toNodeId": "outline_pass" },
{ "fromNodeId": "forward_pass", "toNodeId": "ssao_pass" },
{ "fromNodeId": "ssao_pass", "toNodeId": "ssao_blur_pass" },
{ "fromNodeId": "ssao_blur_pass", "toNodeId": "post_pass" },
{ "fromNodeId": "forward_pass", "toNodeId": "bloom_pass" },
{ "fromNodeId": "bloom_pass", "toNodeId": "post_pass" },
{ "fromNodeId": "outline_pass", "toNodeId": "post_pass" },
{ "fromNodeId": "post_pass", "toNodeId": "compose_pass" }
],
"resources": [
{ "resId": "shadow_atlas" },
{ "resId": "hdr_color" },
{ "resId": "depth" },
{ "resId": "outline_color" },
{ "resId": "ssao_raw" },
{ "resId": "ssao_blur" },
{ "resId": "bloom_color" },
{ "resId": "post_color" },
{ "resId": "swapchain", "kind": "attachment" }
],
"fallback": true
}
Validation Rules (Core)
- DAG only: No cycles.
- Resources exist: Every input/output refers to a declared resource.
- Write ordering: A resource must be produced before it is read.
- Edge reason coherence: When
reasonis set, dependency direction must match resource usage. - Multiple writers: Rewrites of the same resource must preserve explicit dependency ordering.
- Pass compatibility: Each
pass_idmust be a known core pass type. - Realm bind compatibility:
two-drealms can bind only UI-only graphs (uipass). - Safe updates: updating a graph already bound to realms must preserve compatibility with all those realms.
If validation fails, the core switches to the fallback graph.
Fallback Graph
The fallback graph represents the default rendering pipeline that always works. It is used when:
- The host provides no graph.
- The provided graph fails validation.
Example fallback:
shadow -> forward -> outline + ssao -> ssao-blur + bloom -> post -> compose
Performance Notes
- Cache per graph hash: Compile once per
RenderGraphDeschash and reuse across graph IDs/realms. - Cache pruning: Compiled-plan cache entries are removed when the last referencing graph is disposed.
- Alias groups: Allow the core to reuse memory for non-overlapping resources.
- Frame lifetime:
lifetime = "frame"resources are recycled automatically. - Minimal validation on hot path: Validate only when the graph changes.