| name | new-inline-pass |
| description | When adding a new Pass variant with DispatchKind::InlineRenderer to the Titan frame graph, update all synchronized sites — Pass enum, ALL_PASSES, manifest entry + resources, pipeline construction, dispatch arm, validation rules, canonical pin tests, all 8 RON configs, 3 bench tools |
| source | auto-skill |
| extracted_at | 2026-06-16T00:06:25.790Z |
New InlineRenderer Pass — full multi-site lockstep
Adding a new Pass variant that dispatches as DispatchKind::InlineRenderer
(not via StratumHook) requires touching ~15 sites across 4 crates. The
existing pass-variant-addition skill covers Stratum passes; this skill
covers passes that render or compute directly in lib.rs.
Sites to update (in dependency order)
1. Pass enum variant
crates/titan-rendering-frame-graph/src/lib.rs — add to pub enum Pass.
Include a doc comment describing what the pass does.
2. ALL_PASSES constant
crates/titan-rendering-3d/src/manifest.rs — add to ALL_PASSES slice
at the correct pipeline position.
3. New resources (if pass owns textures/buffers)
In manifest.rs, the resources module — add pub const entries for
any GPU resources the pass produces. For render passes, add output
textures (e.g. DEPTH_PREPASS). For compute passes that produce
visibility data, add SSBO resources (e.g. SCENE_INDIRECT_DRAWS,
SCENE_CULL_VISIBILITY).
Add each new resource to:
DECLARED_RESOURCES
HISTORY_RESOURCES (if frame-persistent)
UNTAPPABLE slice (default conservative — remove when tap wiring
is in place)
4. DECLARED_RESOURCES
Add every new pub const ResourceId to DECLARED_RESOURCES in
manifest.rs. This is mechanically enforced by
all_resource_ids_are_declared — any resource id that appears in
a manifest entry but not in DECLARED_RESOURCES fails that test.
5. Manifest entry
In manifest.rs, manifest_entry() — add an arm with:
Pass::NewPass => PassManifestEntry {
id: "NewPass",
inputs: &[r::INPUT_RESOURCE],
outputs: &[r::OUTPUT_RESOURCE],
dispatch: DispatchKind::InlineRenderer,
provenance: Provenance::Audited(AuditRecord {
algorithm: "...",
literature: &[Citation { ... }],
why_chosen: "...",
}),
},
Start as Provenance::Unaudited for scaffold; upgrade to Audited
with Tier-1 citations before merge.
6. Pass ordering validation
In frame-graph/src/lib.rs, validate_pass_ordering() — add a match
arm. If the pass has ordering requirements (e.g. must follow another
pass), assert them here. If no hard constraints, use an empty {}
arm to satisfy exhaustiveness.
7. Canonical-order pin tests
In frame-graph/src/lib.rs, update the pin tests that enforce
pipeline shape:
deferred_stratum_mid_passes_are_in_canonical_order
deferred_stratum_high_passes_are_in_canonical_order
forward_stratum_mid_passes_are_in_canonical_order
forward_stratum_high_passes_are_in_canonical_order
8. Pipeline construction
crates/titan-rendering-3d/src/frame_graph_build.rs — construct the
render or compute pipeline:
For compute passes: use include_str! from the shader crate.
let new_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("scene cull"),
layout: Some(&new_bgl),
module: &device.create_shader_module(
wgpu::ShaderModuleDescriptor {
label: Some("scene_cull.wgsl"),
source: wgpu::ShaderSource::Wgsl(
titan_rendering_scene_cull::SCENE_CULL_SHADER.into()
),
},
),
compilation_options: wgpu::PipelineCompilationOptions::default(),
cache: None,
});
For render passes: specify depth/stencil state, color targets,
and vertex/fragment shader modules.
Add the pipeline and any BGL fields to FrameGraphResources.
9. FrameGraphResources fields
crates/titan-rendering-3d/src/frame_graph_resources.rs — add
Option<wgpu::RenderPipeline> or Option<wgpu::ComputePipeline>
and any BGL fields. Initialize them in FrameGraphResources::new()
as None.
Wire them in frame_graph_build.rs when constructing the struct.
10. Viewport offscreen resources
crates/titan-rendering-3d/src/offscreen_viewport.rs — if the pass
owns viewport-sized textures:
- Add fields to
ViewportTarget (texture + view)
- Add fields to
ViewportFrameGraphInit (passes resource handles
through for resize)
- Wire in
install_frame_graph_resources()
11. Resize path
In lib.rs, the viewport resize path — create the pass's textures
at the appropriate resolution. Follow the pattern of existing passes
(e.g. hzb_resources creation). Texture format must match the
manifest entry.
12. Dispatch arm
lib.rs — add a Pass::NewPass => { ... } arm in the pass dispatch
match. Gate behind #[cfg(not(feature = "forward-only"))] if the
pass is deferred-only.
For compute passes: create bind groups from the BGL, bind
resources, call compute_pass.dispatch_workgroups().
For render passes: begin a render pass with appropriate attachments,
set pipeline, issue draw_indexed or draw_indirect.
Use warn_once_unimplemented_pass for scaffold passes with only
the Self::warn_once_unimplemented_pass(pass, &mut self.warned_passes); continue; body.
13. FrameGraphPlatform needs_gpu_driven (if applicable)
If the pass is part of the GPU-driven pipeline, add a
needs_scene_cull or equivalent predicate to
frame_graph_platform.rs and use it in frame_graph_build.rs to
gate pipeline construction.
14. RON configs — ALL 8 files
Add the new pass to every RON config that includes the pipeline
shape it belongs to:
crates/titan-rendering-frame-graph/configs/pipeline_deferred.ron
crates/titan-rendering-frame-graph/configs/pipeline_deferred_stratum_high.ron
crates/titan-rendering-frame-graph/configs/pipeline_deferred_stratum_mid.ron
crates/titan-rendering-frame-graph/configs/pipeline_deferred_stratum_low.ron
crates/titan-rendering-frame-graph/configs/pipeline_forward.ron
crates/titan-rendering-frame-graph/configs/pipeline_forward_stratum_high.ron
crates/titan-rendering-frame-graph/configs/pipeline_forward_stratum_mid.ron
crates/titan-rendering-frame-graph/configs/pipeline_forward_stratum_low.ron
Place the pass at its correct pipeline position. Culling passes go
after ShadowCascades and before GBufferFill/MainForward.
15. Bench tool pass lists (3 tools × 2 functions each)
Update all three bench tools:
tools/titan-render-bench/src/pipelines.rs
tools/titan-render-bench-probe/src/pipelines.rs
tools/titan-render-bench-shadows/src/pipelines.rs
Each has forward_stratum() and deferred_stratum() functions
(or equivalent). Add the new pass to each in pipeline order.
16. Workspace Cargo.toml dependency (if new crate)
If the pass uses a shader from a separate crate, add the dependency
to crates/titan-rendering-3d/Cargo.toml and update Cargo.lock.
Verification
cargo check --workspace
cargo test -p titan-rendering-3d
cargo test -p titan-rendering-frame-graph
cargo test -p titan-render-bench -p titan-render-bench-probe -p titan-render-bench-shadows
Common pitfalls
- Forgetting
HISTORY_RESOURCES: If the pass produces a texture
that carries frame-to-frame state, it must be in HISTORY_RESOURCES
or the history-aware allocator won't preserve it.
- Forgetting
DECLARED_RESOURCES: The
all_resource_ids_are_declared test catches this, but only if you
run the rendering-3d tests.
- Forgetting bench tools: The compiler doesn't catch missing
passes in bench pass lists. The pin tests do —
cargo test the
bench crates.
- Forgetting canonical pin tests: If you add a pass to RON
configs but not the pin test, the pin test fails — easy to find.
- AABB indexing consistency: When a compute shader uses thread
index to index both input AABBs and output indirect commands, the
two arrays must be parallel at the same index. Compacted uploads
(frustum-visible only) break this — use full-size arrays at
original group indices.
- Texture format consistency: The format in the manifest entry,
resize path, and pipeline attachments must match exactly.
R32Float vs Depth32Float mismatch = silent GPU error.