Custom Passes
Custom rendering hooks into the graph through two State methods. One runs at startup. The other runs every frame.
configure_render_graph()
This is the setup hook. It runs once when the renderer is constructed. The game adds passes, declares textures, and changes the pipeline shape.
#![allow(unused)] fn main() { fn configure_render_graph( &mut self, graph: &mut RenderGraph<World>, device: &wgpu::Device, surface_format: wgpu::TextureFormat, resources: RenderResources, ) { // Add custom textures let my_texture = graph.add_color_texture("my_effect") .format(wgpu::TextureFormat::Rgba16Float) .size(1920, 1080) .clear_color(wgpu::Color::BLACK) .transient(); // Add custom passes let my_pass = MyCustomPass::new(device); graph.add_pass( Box::new(my_pass), &[("input", resources.scene_color), ("output", my_texture)], ); } }
RenderResources
RenderResources is the bundle of ResourceIds the engine already declared. Anything custom usually reads from scene_color and writes to compute_output or swapchain.
| Field | Description |
|---|---|
scene_color | HDR color buffer (Rgba16Float) |
depth | Main depth buffer (Depth32Float) |
compute_output | Post-processed output before swapchain blit |
swapchain | Final swapchain output |
view_normals | View-space normals |
ssao_raw / ssao | Raw and blurred SSAO |
ssgi_raw / ssgi | Raw and blurred SSGI |
ssr_raw / ssr | Raw and blurred SSR |
surface_width / surface_height | Current window dimensions in pixels |
update_render_graph()
The per-frame hook. Use it for runtime state flips, not for adding passes (use the setup hook for that).
#![allow(unused)] fn main() { fn update_render_graph(&mut self, graph: &mut RenderGraph<World>, world: &World) { if self.bloom_changed { let _ = graph.set_pass_enabled("bloom_pass", self.bloom_enabled); self.bloom_changed = false; } } }
Adding Built-in Passes
The built-in pass types are reusable. A custom graph can pick and choose which built-ins to include.
#![allow(unused)] fn main() { use nightshade::render::passes; fn configure_render_graph( &mut self, graph: &mut RenderGraph<World>, device: &wgpu::Device, surface_format: wgpu::TextureFormat, resources: RenderResources, ) { let bloom_texture = graph.add_color_texture("bloom") .format(wgpu::TextureFormat::Rgba16Float) .size(960, 540) .clear_color(wgpu::Color::BLACK) .transient(); // Bloom let bloom_pass = passes::BloomPass::new(device, 1920, 1080); graph.add_pass( Box::new(bloom_pass), &[("hdr", resources.scene_color), ("bloom", bloom_texture)], ); // SSAO let ssao_pass = passes::SsaoPass::new(device, 1920, 1080); graph.add_pass( Box::new(ssao_pass), &[ ("depth", resources.depth), ("normals", resources.view_normals), ("ssao_raw", resources.ssao_raw), ], ); let ssao_blur_pass = passes::SsaoBlurPass::new(device, 1920, 1080); graph.add_pass( Box::new(ssao_blur_pass), &[("ssao_raw", resources.ssao_raw), ("ssao", resources.ssao)], ); // Final compositing let postprocess_pass = passes::PostProcessPass::new(device, surface_format, 0.3); graph.add_pass( Box::new(postprocess_pass), &[ ("hdr", resources.scene_color), ("bloom", bloom_texture), ("ssao", resources.ssao), ("output", resources.compute_output), ], ); // Blit to swapchain let blit_pass = passes::BlitPass::new(device, surface_format); graph.add_pass( Box::new(blit_pass), &[("input", resources.compute_output), ("output", resources.swapchain)], ); } }
PassBuilder Fluent API
pass() is an alternative to add_pass() that reads more like an expression. The builder adds the pass to the graph when it goes out of scope.
#![allow(unused)] fn main() { graph.pass(Box::new(bloom_pass)) .read("hdr", resources.scene_color) .write("bloom", bloom_texture); graph.pass(Box::new(postprocess_pass)) .read("hdr", resources.scene_color) .read("bloom", bloom_texture) .read("ssao", resources.ssao) .write("output", resources.swapchain); }
Conditional Passes
There are two ways to make a pass conditional. The first is to leave it out of the graph entirely at setup time.
#![allow(unused)] fn main() { fn configure_render_graph( &mut self, graph: &mut RenderGraph<World>, device: &wgpu::Device, surface_format: wgpu::TextureFormat, resources: RenderResources, ) { if self.ssao_enabled { let ssao_pass = passes::SsaoPass::new(device, 1920, 1080); graph.add_pass( Box::new(ssao_pass), &[ ("depth", resources.depth), ("normals", resources.view_normals), ("ssao_raw", resources.ssao_raw), ], ); } } }
The second is to add the pass unconditionally and toggle it at runtime.
#![allow(unused)] fn main() { fn update_render_graph(&mut self, graph: &mut RenderGraph<World>, _world: &World) { let _ = graph.set_pass_enabled("ssao_pass", self.ssao_enabled); } }
The toggle path is the right choice when the setting changes during play. The conditional-construction path is the right choice when the decision is fixed at startup, because it skips the per-frame check entirely.
Default Pipeline
If the game does not override configure_render_graph(), the default implementation adds three passes:
BloomPassruns HDR bloom at half resolution.PostProcessPassdoes tonemapping and compositing.BlitPasscopies to the swapchain.
The engine always adds the core passes (clear, sky, shadow, mesh, skinned mesh, grass, grid, lines, selection) regardless of what the custom configuration does. Overriding configure_render_graph() only changes the post-process tail.