| name | bench-resize-gate |
| description | When a Titan bench tool using Renderer3d lacks on_resize → renderer.resize(), depth_prepass_view and HZB occlusion resources are never created — DepthPrepass, HzbBuild, and SceneCull passes silently skip, and taps produce no output |
| source | auto-skill |
| extracted_at | 2026-06-16T04:02:21.935Z |
Bench Resize Gate
When a Titan bench tool creates a Renderer3d but never calls renderer.resize(),
several rendering subsystems silently fail because their resources are only
allocated inside Renderer3d::resize().
The trap
depth_prepass_view, depth_prepass_texture, and hzb_occlusion_resources are
created inside Renderer3d::resize(), gated by self.fg.depth_prepass_pipeline.is_some().
The depth prepass pipeline IS created during frame graph build (before the first
render), but its view and texture are NOT — they wait for resize().
If resize() is never called:
depth_prepass_view stays None → the DepthPrepass pass arm does continue
before rendering any depth or reaching the tap dispatch code
hzb_occlusion_resources stays None → HzbBuild is never dispatched →
SceneCull reads an uninitialized zero-filled HZB where every texel is 0.0
(far plane under reverse-Z), so no candidate is ever occluded
- Tap output for
depth_prepass, scene_indirect_draws, and
scene_cull_visibility is never produced because the dispatch code is never
reached
The symptom is silent: the bench runs, the window appears, geometry renders
(via GBufferFill's fallback draw_indexed path), but HZB occlusion scores are
always "visible" and taps produce zero output files.
The fix
Add fn on_resize to the bench's App impl, matching the probe bench pattern:
fn on_resize(&mut self, width: u32, height: u32) {
if let Some(ref mut renderer) = self.renderer {
renderer.resize(width, height);
}
}
Why this is easy to miss
Renderer3d::new creates the device, surface, swapchain, and frame-graph
pipelines. It looks like a complete initialization. The resize() call looks
redundant for a non-resizable window — the initial size is already known. But
resize() is where the depth prepass texture + view + HZB chain are actually
allocated.
Verification
After adding on_resize, verify with:
cargo run -p titan-render-bench-culling --features debug-dumps -- \
--duration 3 --warmup 5 --tap depth_prepass --dump-window 5..7 --dump-once
Expected: titan-dumps/depth_prepass-frame5.bin (or frame6) is created.
CSV should show hzb_occluded entries for geometry behind occluders.