use crate::render_types::NUM_SHADOW_CASCADES;
use alloc::vec;
use alloc::vec::Vec;
use super::{
BufferDesc, BufferUsage, CompiledGraph, GraphBuilder, GraphError, PassId, PassKind,
PixelFormat, TextureDesc, TextureHandle, TextureSize, TextureUsage, full_mip_levels,
};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct FrameGraphInputs {
pub shadow_enabled: bool,
pub shadow_map_size: u32,
pub hdr_width: u32,
pub hdr_height: u32,
pub hdr_sample_count: u32,
pub bindless_cull_enabled: bool,
pub auto_exposure_enabled: bool,
pub bloom_enabled: bool,
pub velocity_enabled: bool,
pub taa_enabled: bool,
pub ssr_enabled: bool,
pub particles_enabled: bool,
pub fog_enabled: bool,
pub decals_enabled: bool,
pub ssr_prepass_enabled: bool,
pub ssao_enabled: bool,
pub upscale_enabled: bool,
pub transparent_enabled: bool,
pub lines_enabled: bool,
pub raymarch_enabled: bool,
pub two_pass_occlusion_enabled: bool,
pub ssgi_enabled: bool,
pub rt_reflections_enabled: bool,
pub unified_gbuffer_prepass: bool,
pub world_hidden: bool,
pub clustered_lighting_enabled: bool,
pub composite_reads_ao: bool,
pub shadowed_spot_count: u32,
pub spot_shadow_slice_size: u32,
pub hiz_build_enabled: bool,
}
impl FrameGraphInputs {
pub(crate) fn all_off() -> Self {
FrameGraphInputs {
shadow_enabled: false,
shadow_map_size: 2048,
hdr_width: 1280,
hdr_height: 720,
hdr_sample_count: 1,
bindless_cull_enabled: false,
auto_exposure_enabled: false,
bloom_enabled: false,
velocity_enabled: false,
taa_enabled: false,
ssr_enabled: false,
particles_enabled: false,
fog_enabled: false,
decals_enabled: false,
ssr_prepass_enabled: false,
ssao_enabled: false,
upscale_enabled: false,
transparent_enabled: false,
lines_enabled: false,
raymarch_enabled: false,
two_pass_occlusion_enabled: false,
ssgi_enabled: false,
rt_reflections_enabled: false,
unified_gbuffer_prepass: false,
world_hidden: false,
clustered_lighting_enabled: false,
composite_reads_ao: false,
shadowed_spot_count: 0,
spot_shadow_slice_size: 512,
hiz_build_enabled: false,
}
}
}
#[derive(Copy, Clone)]
struct GBufferHandles {
normal_depth: TextureHandle,
roughness: TextureHandle,
velocity: TextureHandle,
depth: TextureHandle,
}
pub fn build_frame_graph(inputs: &FrameGraphInputs) -> Result<CompiledGraph, GraphError> {
let masked = if inputs.world_hidden {
Some(FrameGraphInputs {
shadow_enabled: false,
bindless_cull_enabled: false,
auto_exposure_enabled: false,
bloom_enabled: false,
velocity_enabled: false,
taa_enabled: false,
ssr_enabled: false,
particles_enabled: false,
fog_enabled: false,
decals_enabled: false,
ssr_prepass_enabled: false,
ssao_enabled: false,
upscale_enabled: false,
transparent_enabled: false,
lines_enabled: false,
raymarch_enabled: false,
two_pass_occlusion_enabled: false,
ssgi_enabled: false,
rt_reflections_enabled: false,
clustered_lighting_enabled: false,
shadowed_spot_count: 0,
spot_shadow_slice_size: 512,
..*inputs
})
} else {
None
};
let inputs = masked.as_ref().unwrap_or(inputs);
let mut b = GraphBuilder::new();
let hdr_color = (inputs.hdr_sample_count > 1)
.then(|| b.import_texture("hdr_color", hdr_color_desc(inputs)));
let hdr_depth = b.import_texture("hdr_depth", hdr_depth_desc(inputs));
let hdr_resolve = b.import_texture("hdr_resolve", hdr_resolve_desc(inputs));
let two_pass = inputs.bindless_cull_enabled && inputs.two_pass_occlusion_enabled;
let hiz_pyramid = (inputs.hiz_build_enabled || two_pass)
.then(|| b.import_texture("hiz_pyramid", hiz_pyramid_desc(inputs)));
let (draw_args_v1, cull_status_v1) = if inputs.bindless_cull_enabled {
let draw_args = b.import_buffer("draw_args", draw_args_desc());
let cull_status = if two_pass {
Some(b.import_buffer("cull_status", cull_status_desc()))
} else {
None
};
let mut cull = b.add_pass(PassId::Cull, PassKind::Compute);
if let Some(h) = hiz_pyramid {
cull.read_texture(h);
}
let da = cull.write_buffer(draw_args);
let cs = cull_status.map(|h| cull.write_buffer(h));
(Some(da), cs)
} else {
(None, None)
};
let gbuffer_v1 = if inputs.unified_gbuffer_prepass
&& (inputs.ssr_prepass_enabled || inputs.ssao_enabled || inputs.velocity_enabled)
{
let normal_depth =
b.create_texture("gbuffer_normal_depth", gbuffer_normal_depth_desc(inputs));
let roughness = b.create_texture("gbuffer_roughness", gbuffer_roughness_desc(inputs));
let velocity = b.create_texture("gbuffer_velocity", velocity_desc(inputs));
let depth = b.create_texture("gbuffer_depth", gbuffer_depth_desc(inputs));
let mut gb = b.add_pass(PassId::GBufferPrepass, PassKind::Render);
if let Some(h) = draw_args_v1 {
gb.read_buffer(h);
}
Some(GBufferHandles {
normal_depth: gb.write_texture(normal_depth),
roughness: gb.write_texture(roughness),
velocity: gb.write_texture(velocity),
depth: gb.write_texture(depth),
})
} else {
None
};
let ssr_gbuffer_v1 = if let Some(g) = gbuffer_v1 {
Some(g.normal_depth)
} else if inputs.ssr_prepass_enabled {
let ssr_gbuffer = b.create_texture("ssr_gbuffer", ssr_gbuffer_desc(inputs));
Some(
b.add_pass(PassId::SsrPrepass, PassKind::Render)
.write_texture(ssr_gbuffer),
)
} else {
None
};
let ao_output_v1 = if inputs.ssao_enabled {
let ao_output = b.create_texture("ao_output", ao_output_desc(inputs));
let mut ssao = b.add_pass(PassId::SsaoBlur, PassKind::Render);
if let Some(h) = ssr_gbuffer_v1 {
ssao.read_texture(h);
}
Some(ssao.write_texture(ao_output))
} else {
None
};
let shadow_v1 = if inputs.shadow_enabled {
let shadow_map = b.import_texture("shadow_map", shadow_map_desc(inputs.shadow_map_size));
Some(
b.add_pass(PassId::Shadow, PassKind::Render)
.write_texture(shadow_map),
)
} else {
None
};
let cluster_lights_v1 = if inputs.clustered_lighting_enabled {
let cluster_lights = b.import_buffer("cluster_light_list", cluster_light_list_desc());
Some(
b.add_pass(PassId::LightCull, PassKind::Compute)
.write_buffer(cluster_lights),
)
} else {
None
};
let spot_shadow_v1 = if inputs.shadowed_spot_count > 0 {
let spot_map = b.import_texture(
"spot_shadow_map",
spot_shadow_map_desc(inputs.spot_shadow_slice_size, inputs.shadowed_spot_count),
);
Some(
b.add_pass(PassId::SpotShadow, PassKind::Render)
.write_texture(spot_map),
)
} else {
None
};
let (hdr_resolve_v1, hdr_depth_v1) = {
let mut main = b.add_pass(PassId::Main, PassKind::Render);
if let Some(h) = shadow_v1 {
main.read_texture(h);
}
if let Some(h) = spot_shadow_v1 {
main.read_texture(h);
}
if let Some(h) = draw_args_v1 {
main.read_buffer(h);
}
if let Some(h) = cluster_lights_v1 {
main.read_buffer(h);
}
if let Some(h) = ao_output_v1 {
main.read_texture(h);
}
if let Some(h) = hdr_color {
let _ = main.write_texture(h);
}
let depth_v1 = main.write_texture(hdr_depth);
let resolve_v1 = main.write_texture(hdr_resolve);
(resolve_v1, depth_v1)
};
let mut hiz_cur = hiz_pyramid;
let mut depth_cur = hdr_depth_v1;
let hdr_resolve_head = if let (true, Some(hiz)) = (two_pass, hiz_pyramid) {
let mut hizb = b.add_pass(PassId::HizBuild, PassKind::Compute);
hizb.read_texture(depth_cur);
let hiz_v1 = hizb.write_texture(hiz);
hiz_cur = Some(hiz_v1);
let draw_args2 = b.import_buffer("draw_args2", draw_args_desc());
let mut cull2 = b.add_pass(PassId::Cull2, PassKind::Compute);
cull2.read_texture(hiz_v1);
if let Some(cs) = cull_status_v1 {
cull2.read_buffer(cs);
}
let draw_args2_v1 = cull2.write_buffer(draw_args2);
let mut main2 = b.add_pass(PassId::Main2, PassKind::Render);
main2.read_buffer(draw_args2_v1);
depth_cur = main2.write_texture(depth_cur);
if let Some(h) = hdr_color {
let _ = main2.write_texture(h);
}
main2.write_texture(hdr_resolve_v1)
} else {
hdr_resolve_v1
};
if inputs.auto_exposure_enabled {
b.add_pass(PassId::AutoExposure, PassKind::Compute)
.read_texture(hdr_resolve_head);
}
let velocity_v1 = if let Some(g) = gbuffer_v1 {
Some(g.velocity)
} else if inputs.velocity_enabled {
let velocity = b.create_texture("velocity", velocity_desc(inputs));
Some(
b.add_pass(PassId::Velocity, PassKind::Render)
.write_texture(velocity),
)
} else {
None
};
let mut h = hdr_resolve_head;
if inputs.raymarch_enabled {
let mut rm = b.add_pass(PassId::Raymarch, PassKind::Render);
rm.read_texture(h);
h = rm.write_texture(h);
depth_cur = rm.write_texture(depth_cur);
}
if inputs.ssgi_enabled {
let mut ssgi = b.add_pass(PassId::Ssgi, PassKind::Render);
ssgi.read_texture(h);
if let Some(g) = ssr_gbuffer_v1 {
ssgi.read_texture(g);
}
h = ssgi.write_texture(h);
}
if inputs.decals_enabled {
let mut decals = b.add_pass(PassId::Decals, PassKind::Render);
decals.read_texture(depth_cur);
h = decals.write_texture(h);
}
if inputs.fog_enabled {
let froxel_v0 = b.import_texture("fog_froxel_volume", froxel_volume_desc(inputs));
let mut froxel = b.add_pass(PassId::FogFroxel, PassKind::Compute);
if let Some(h) = shadow_v1 {
froxel.read_texture(h);
}
let froxel_v1 = froxel.write_texture(froxel_v0);
let mut fog_pass = b.add_pass(PassId::Fog, PassKind::Render);
fog_pass.read_texture(froxel_v1);
fog_pass.read_texture(depth_cur);
h = fog_pass.write_texture(h);
}
if inputs.particles_enabled {
h = b
.add_pass(PassId::ParticlesDraw, PassKind::Render)
.write_texture(h);
}
if inputs.lines_enabled {
let mut lines = b.add_pass(PassId::Lines, PassKind::Render);
lines.read_texture(depth_cur);
h = lines.write_texture(h);
}
let hdr_resolve_cur = h;
let scene_pre_taa_cur = if inputs.rt_reflections_enabled || inputs.ssr_enabled {
let scene_pre_taa = b.import_texture("scene_pre_taa", scene_color_desc(inputs));
let mut current = if inputs.rt_reflections_enabled {
let mut rt = b.add_pass(PassId::RtReflections, PassKind::Render);
rt.read_texture(hdr_resolve_cur);
if let Some(g) = ssr_gbuffer_v1 {
rt.read_texture(g);
}
if let Some(g) = gbuffer_v1 {
rt.read_texture(g.roughness);
}
rt.write_texture(scene_pre_taa)
} else {
let mut ssr = b.add_pass(PassId::SsrResolve, PassKind::Render);
ssr.read_texture(hdr_resolve_cur);
if let Some(g) = ssr_gbuffer_v1 {
ssr.read_texture(g);
}
if let Some(g) = gbuffer_v1 {
ssr.read_texture(g.roughness);
}
ssr.write_texture(scene_pre_taa)
};
if inputs.transparent_enabled {
let mut trans = b.add_pass(PassId::Transparent, PassKind::Render);
trans.read_texture(hdr_resolve_cur);
trans.read_texture(depth_cur);
trans.read_texture(current);
current = trans.write_texture(current);
}
current
} else if inputs.transparent_enabled {
let mut trans = b.add_pass(PassId::Transparent, PassKind::Render);
trans.read_texture(depth_cur);
trans.read_texture(hdr_resolve_cur);
trans.write_texture(hdr_resolve_cur)
} else {
hdr_resolve_cur
};
let scene_color_cur = if inputs.upscale_enabled {
let scene_color = b.import_texture("scene_color", scene_color_desc(inputs));
let mut up = b.add_pass(PassId::Upscale, PassKind::Compute);
up.read_texture(scene_pre_taa_cur);
if let Some(v) = velocity_v1 {
up.read_texture(v);
}
if let Some(g) = gbuffer_v1 {
up.read_texture(g.depth);
}
up.write_texture(scene_color)
} else if inputs.taa_enabled {
let scene_color = b.import_texture("scene_color", scene_color_desc(inputs));
let mut taa = b.add_pass(PassId::TaaResolve, PassKind::Render);
taa.read_texture(scene_pre_taa_cur);
if let Some(v) = velocity_v1 {
taa.read_texture(v);
}
taa.write_texture(scene_color)
} else {
scene_pre_taa_cur
};
let bloom_top_v1 = if inputs.bloom_enabled {
let bloom_top = b.create_texture("bloom_top", bloom_top_desc(inputs));
Some(
b.add_pass(PassId::Bloom, PassKind::Render)
.read_texture(scene_color_cur)
.write_texture(bloom_top),
)
} else {
None
};
if let (true, Some(hiz)) = (inputs.hiz_build_enabled, hiz_cur) {
let mut hizf = b.add_pass(PassId::HizFinal, PassKind::Compute);
hizf.read_texture(depth_cur);
let _ = hizf.write_texture(hiz);
}
{
let mut composite = b.add_pass(PassId::Composite, PassKind::Render);
composite.read_texture(scene_color_cur);
if let Some(h) = bloom_top_v1 {
composite.read_texture(h);
}
if inputs.composite_reads_ao
&& let Some(h) = ao_output_v1
{
composite.read_texture(h);
}
composite.presents();
}
b.compile()
}
fn froxel_volume_desc(inputs: &FrameGraphInputs) -> TextureDesc {
let _ = inputs;
TextureDesc::volume_3d(
TextureSize::Absolute(FOG_FROXEL_X),
TextureSize::Absolute(FOG_FROXEL_Y),
FOG_FROXEL_Z,
PixelFormat::Rgba16Float,
TextureUsage::STORAGE.union(TextureUsage::SHADER_READ),
)
}
pub const FOG_FROXEL_X: u32 = 80;
pub const FOG_FROXEL_Y: u32 = 45;
pub const FOG_FROXEL_Z: u32 = 64;
fn shadow_map_desc(size: u32) -> TextureDesc {
TextureDesc::texture_2d(
TextureSize::Absolute(size.max(1)),
TextureSize::Absolute(size.max(1)),
PixelFormat::Depth32Float,
TextureUsage::DEPTH_STENCIL.union(TextureUsage::SHADER_READ),
)
.with_array_layers(NUM_SHADOW_CASCADES as u32)
}
fn spot_shadow_map_desc(slice_size: u32, slices: u32) -> TextureDesc {
TextureDesc::texture_2d(
TextureSize::Absolute(slice_size.max(1)),
TextureSize::Absolute(slice_size.max(1)),
PixelFormat::Depth32Float,
TextureUsage::DEPTH_STENCIL.union(TextureUsage::SHADER_READ),
)
.with_array_layers(slices.max(1))
}
fn hdr_color_desc(inputs: &FrameGraphInputs) -> TextureDesc {
render_res_2d(
inputs,
PixelFormat::Rgba16Float,
TextureUsage::RENDER_TARGET,
)
.with_sample_count(inputs.hdr_sample_count.max(1))
}
fn hdr_depth_desc(inputs: &FrameGraphInputs) -> TextureDesc {
render_res_2d(
inputs,
PixelFormat::Depth32Float,
TextureUsage::DEPTH_STENCIL.union(TextureUsage::SHADER_READ),
)
.with_sample_count(inputs.hdr_sample_count.max(1))
}
fn render_res_2d(
inputs: &FrameGraphInputs,
format: PixelFormat,
usage: TextureUsage,
) -> TextureDesc {
TextureDesc::texture_2d(
TextureSize::Absolute(inputs.hdr_width.max(1)),
TextureSize::Absolute(inputs.hdr_height.max(1)),
format,
usage,
)
}
fn draw_args_desc() -> BufferDesc {
BufferDesc {
size_bytes: None,
usage: BufferUsage::STORAGE.union(BufferUsage::INDIRECT),
}
}
fn cull_status_desc() -> BufferDesc {
BufferDesc {
size_bytes: None,
usage: BufferUsage::STORAGE.union(BufferUsage::UNORDERED),
}
}
fn cluster_light_list_desc() -> BufferDesc {
BufferDesc {
size_bytes: None,
usage: BufferUsage::STORAGE,
}
}
fn hiz_pyramid_desc(inputs: &FrameGraphInputs) -> TextureDesc {
render_res_2d(
inputs,
PixelFormat::R32Float,
TextureUsage::STORAGE.union(TextureUsage::SHADER_READ),
)
.with_mip_levels(full_mip_levels(
inputs.hdr_width.max(1),
inputs.hdr_height.max(1),
))
}
fn gbuffer_normal_depth_desc(inputs: &FrameGraphInputs) -> TextureDesc {
render_res_2d(
inputs,
PixelFormat::Rgba16Float,
TextureUsage::RENDER_TARGET.union(TextureUsage::SHADER_READ),
)
}
fn gbuffer_roughness_desc(inputs: &FrameGraphInputs) -> TextureDesc {
render_res_2d(
inputs,
PixelFormat::R8Unorm,
TextureUsage::RENDER_TARGET.union(TextureUsage::SHADER_READ),
)
.with_clear_color([1.0, 0.0, 0.0, 0.0])
}
fn gbuffer_depth_desc(inputs: &FrameGraphInputs) -> TextureDesc {
render_res_2d(
inputs,
PixelFormat::Depth32Float,
TextureUsage::DEPTH_STENCIL.union(TextureUsage::SHADER_READ),
)
}
fn ssr_gbuffer_desc(inputs: &FrameGraphInputs) -> TextureDesc {
render_res_2d(
inputs,
PixelFormat::Rgba16Float,
TextureUsage::RENDER_TARGET.union(TextureUsage::SHADER_READ),
)
}
fn ao_output_desc(inputs: &FrameGraphInputs) -> TextureDesc {
render_res_2d(
inputs,
PixelFormat::R8Unorm,
TextureUsage::RENDER_TARGET.union(TextureUsage::SHADER_READ),
)
}
fn velocity_desc(inputs: &FrameGraphInputs) -> TextureDesc {
render_res_2d(
inputs,
PixelFormat::Rg16Float,
TextureUsage::RENDER_TARGET.union(TextureUsage::SHADER_READ),
)
}
fn bloom_top_desc(inputs: &FrameGraphInputs) -> TextureDesc {
let _ = inputs;
TextureDesc::texture_2d(
TextureSize::DrawableScaled(0.5),
TextureSize::DrawableScaled(0.5),
PixelFormat::Rgba16Float,
TextureUsage::RENDER_TARGET.union(TextureUsage::SHADER_READ),
)
}
fn hdr_resolve_desc(inputs: &FrameGraphInputs) -> TextureDesc {
render_res_2d(
inputs,
PixelFormat::Rgba16Float,
TextureUsage::RENDER_TARGET.union(TextureUsage::SHADER_READ),
)
}
fn scene_color_desc(inputs: &FrameGraphInputs) -> TextureDesc {
render_res_2d(inputs, PixelFormat::Rgba16Float, TextureUsage::SHADER_READ)
}
#[cfg(test)]
mod tests {
use super::*;
fn all_off() -> FrameGraphInputs {
FrameGraphInputs {
shadow_enabled: false,
shadow_map_size: 2048,
hdr_width: 1280,
hdr_height: 720,
hdr_sample_count: 4,
bindless_cull_enabled: false,
auto_exposure_enabled: false,
bloom_enabled: false,
velocity_enabled: false,
taa_enabled: false,
ssr_enabled: false,
particles_enabled: false,
fog_enabled: false,
decals_enabled: false,
ssr_prepass_enabled: false,
ssao_enabled: false,
upscale_enabled: false,
transparent_enabled: false,
lines_enabled: false,
raymarch_enabled: false,
two_pass_occlusion_enabled: false,
ssgi_enabled: false,
rt_reflections_enabled: false,
unified_gbuffer_prepass: false,
world_hidden: false,
clustered_lighting_enabled: false,
composite_reads_ao: false,
shadowed_spot_count: 0,
spot_shadow_slice_size: 512,
hiz_build_enabled: false,
}
}
#[test]
fn minimum_graph_is_main_then_composite() {
let g = build_frame_graph(&all_off()).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert_eq!(order, vec![PassId::Main, PassId::Composite]);
assert!(g.passes[1].presents);
}
#[test]
fn world_hidden_collapses_to_minimum_graph() {
let mut i = all_off();
i.shadow_enabled = true;
i.bindless_cull_enabled = true;
i.ssr_prepass_enabled = true;
i.ssao_enabled = true;
i.ssgi_enabled = true;
i.rt_reflections_enabled = true;
i.bloom_enabled = true;
i.taa_enabled = true;
i.velocity_enabled = true;
i.two_pass_occlusion_enabled = true;
i.world_hidden = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert_eq!(order, vec![PassId::Main, PassId::Composite]);
assert!(g.passes[1].presents);
}
#[test]
fn shadow_orders_before_main() {
let mut i = all_off();
i.shadow_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert_eq!(order, vec![PassId::Shadow, PassId::Main, PassId::Composite]);
}
#[test]
fn cull_orders_before_main_via_draw_args() {
let mut i = all_off();
i.bindless_cull_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert_eq!(order, vec![PassId::Cull, PassId::Main, PassId::Composite]);
assert_eq!(g.passes[0].kind, PassKind::Compute);
}
#[test]
fn two_pass_inserts_phase2_chain_after_main() {
let mut i = all_off();
i.bindless_cull_enabled = true;
i.two_pass_occlusion_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert_eq!(
order,
vec![
PassId::Cull,
PassId::Main,
PassId::HizBuild,
PassId::Cull2,
PassId::Main2,
PassId::Composite,
]
);
assert_eq!(g.passes[2].kind, PassKind::Compute); assert_eq!(g.passes[3].kind, PassKind::Compute); assert_eq!(g.passes[4].kind, PassKind::Render); }
fn resource_of(g: &CompiledGraph, label: &str) -> usize {
g.resources
.iter()
.position(|r| r.label == label)
.unwrap_or_else(|| panic!("{label} missing from the graph"))
}
#[test]
fn hiz_final_closes_the_frame_over_the_last_depth_version() {
let mut i = all_off();
i.hiz_build_enabled = true;
i.raymarch_enabled = true;
i.lines_enabled = true;
i.transparent_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
let pos = |p: PassId| order.iter().position(|x| *x == p).expect("present");
assert!(pos(PassId::Lines) < pos(PassId::HizFinal));
assert!(pos(PassId::Transparent) < pos(PassId::HizFinal));
assert!(pos(PassId::HizFinal) < pos(PassId::Composite));
let depth = resource_of(&g, "hdr_depth");
let hizf = &g.passes[pos(PassId::HizFinal)];
let read = hizf
.reads
.iter()
.find(|r| r.resource_index() == depth)
.expect("HizFinal reads depth");
assert_eq!(read.version(), 2, "Main writes v1, Raymarch bumps to v2");
assert_eq!(g.resources[depth].lifetime.last, pos(PassId::HizFinal));
}
#[test]
fn cull_reads_the_pyramid_the_terminal_build_overwrites() {
let mut i = all_off();
i.hiz_build_enabled = true;
i.bindless_cull_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
let pos = |p: PassId| order.iter().position(|x| *x == p).expect("present");
let hiz = resource_of(&g, "hiz_pyramid");
assert!(
g.passes[pos(PassId::Cull)]
.reads
.iter()
.any(|r| r.resource_index() == hiz)
);
assert!(pos(PassId::Cull) < pos(PassId::HizFinal));
}
#[test]
fn hiz_final_off_means_no_pyramid_in_the_graph() {
let g = build_frame_graph(&all_off()).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert!(!order.contains(&PassId::HizFinal));
assert!(!g.resources.iter().any(|r| r.label == "hiz_pyramid"));
}
#[test]
fn a_hidden_world_still_rebuilds_the_pyramid() {
let mut i = all_off();
i.hiz_build_enabled = true;
i.bindless_cull_enabled = true;
i.world_hidden = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert_eq!(
order,
vec![PassId::Main, PassId::HizFinal, PassId::Composite]
);
}
#[test]
fn depth_readers_all_sample_the_post_raymarch_version() {
let mut i = all_off();
i.raymarch_enabled = true;
i.decals_enabled = true;
i.fog_enabled = true;
i.lines_enabled = true;
i.transparent_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
let pos = |p: PassId| order.iter().position(|x| *x == p).expect("present");
let depth = resource_of(&g, "hdr_depth");
for pass in [
PassId::Decals,
PassId::Fog,
PassId::Lines,
PassId::Transparent,
] {
let read = g.passes[pos(pass)]
.reads
.iter()
.find(|r| r.resource_index() == depth)
.unwrap_or_else(|| panic!("{pass:?} reads depth"));
assert_eq!(read.version(), 2, "{pass:?}");
}
assert!(pos(PassId::Raymarch) < pos(PassId::Decals));
}
#[test]
fn the_msaa_colour_attachment_is_declared_only_when_multisampled() {
let mut i = all_off();
i.hdr_sample_count = 1;
let g = build_frame_graph(&i).expect("compiles");
assert!(!g.resources.iter().any(|r| r.label == "hdr_color"));
assert!(g.resources.iter().any(|r| r.label == "hdr_resolve"));
i.hdr_sample_count = 4;
let g = build_frame_graph(&i).expect("compiles");
assert!(g.resources.iter().any(|r| r.label == "hdr_color"));
assert!(g.resources.iter().any(|r| r.label == "hdr_resolve"));
}
#[test]
fn dropping_the_msaa_attachment_keeps_the_phase_order() {
let mut i = all_off();
i.hdr_sample_count = 1;
i.bindless_cull_enabled = true;
i.two_pass_occlusion_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert_eq!(
order,
vec![
PassId::Cull,
PassId::Main,
PassId::HizBuild,
PassId::Cull2,
PassId::Main2,
PassId::Composite,
]
);
}
#[test]
fn two_pass_without_bindless_cull_is_noop() {
let mut i = all_off();
i.two_pass_occlusion_enabled = true; let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert_eq!(order, vec![PassId::Main, PassId::Composite]);
assert!(!order.contains(&PassId::HizBuild));
assert!(!order.contains(&PassId::Cull2));
assert!(!order.contains(&PassId::Main2));
}
#[test]
fn two_pass_shifts_post_chain_head_to_main2() {
let mut i = all_off();
i.bindless_cull_enabled = true;
i.two_pass_occlusion_enabled = true;
i.auto_exposure_enabled = true;
i.decals_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
let pos = |p: PassId| order.iter().position(|x| *x == p).expect("present");
assert!(pos(PassId::Main2) < pos(PassId::AutoExposure));
assert!(pos(PassId::AutoExposure) < pos(PassId::Decals));
let main2 = &g.passes[pos(PassId::Main2)];
assert_eq!(main2.writes.last().unwrap().version(), 2);
let decals = &g.passes[pos(PassId::Decals)];
assert_eq!(decals.writes[0].version(), 3);
}
#[test]
fn ssao_orders_before_main_via_ao_output() {
let mut i = all_off();
i.ssao_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert_eq!(
order,
vec![PassId::SsaoBlur, PassId::Main, PassId::Composite]
);
}
#[test]
fn ao_output_barriers_are_graph_driven() {
use super::super::ResourceState;
let mut i = all_off();
i.ssao_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let pass = |id: PassId| g.passes.iter().find(|p| p.id == id).expect("present");
let ssao = g.pass_barriers_for(pass(PassId::SsaoBlur), &["ao_output"]);
assert_eq!(ssao.len(), 1, "SsaoBlur has exactly one ao_output barrier");
assert_eq!(ssao[0].1.source_state(), ResourceState::Undefined);
assert_eq!(ssao[0].1.to_state(), ResourceState::Write);
let main = g.pass_barriers_for(pass(PassId::Main), &["ao_output"]);
assert_eq!(main.len(), 1, "Main has exactly one ao_output barrier");
assert_eq!(main[0].1.source_state(), ResourceState::Write);
assert_eq!(main[0].1.to_state(), ResourceState::Read);
}
#[test]
fn shadow_map_barriers_are_graph_driven() {
use super::super::ResourceState;
let mut i = all_off();
i.shadow_enabled = true;
i.ssao_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let pass = |id: PassId| g.passes.iter().find(|p| p.id == id).expect("present");
let shadow = g.pass_barriers_for(pass(PassId::Shadow), &["shadow_map"]);
assert_eq!(shadow.len(), 1, "Shadow has exactly one shadow_map barrier");
assert_eq!(shadow[0].1.source_state(), ResourceState::Undefined);
assert_eq!(shadow[0].1.to_state(), ResourceState::Write);
let main = g.pass_barriers_for(pass(PassId::Main), &["shadow_map"]);
assert_eq!(main.len(), 1, "Main has exactly one shadow_map barrier");
assert_eq!(main[0].1.source_state(), ResourceState::Write);
assert_eq!(main[0].1.to_state(), ResourceState::Read);
let both = g.pass_barriers_for(pass(PassId::Main), &["shadow_map", "ao_output"]);
assert_eq!(
both.len(),
2,
"Main carries shadow_map + ao_output barriers"
);
}
#[test]
fn fog_froxel_volume_barriers_are_graph_driven() {
use super::super::ResourceState;
let mut i = all_off();
i.fog_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let pass = |id: PassId| g.passes.iter().find(|p| p.id == id).expect("present");
let froxel = g.pass_barriers_for(pass(PassId::FogFroxel), &["fog_froxel_volume"]);
assert_eq!(
froxel.len(),
1,
"FogFroxel has exactly one fog_froxel_volume barrier"
);
assert_eq!(froxel[0].1.source_state(), ResourceState::Undefined);
assert_eq!(froxel[0].1.to_state(), ResourceState::Write);
let fog = g.pass_barriers_for(pass(PassId::Fog), &["fog_froxel_volume"]);
assert_eq!(
fog.len(),
1,
"Fog has exactly one fog_froxel_volume barrier"
);
assert_eq!(fog[0].1.source_state(), ResourceState::Write);
assert_eq!(fog[0].1.to_state(), ResourceState::Read);
}
#[test]
fn ssr_prepass_and_ssao_share_gbuffer_pinning_order() {
let mut i = all_off();
i.ssr_prepass_enabled = true;
i.ssao_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert_eq!(
order,
vec![
PassId::SsrPrepass,
PassId::SsaoBlur,
PassId::Main,
PassId::Composite,
]
);
}
#[test]
fn unified_gbuffer_prepass_replaces_ssr_and_velocity() {
let mut i = all_off();
i.unified_gbuffer_prepass = true;
i.ssr_prepass_enabled = true;
i.ssao_enabled = true;
i.velocity_enabled = true;
i.taa_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert!(order.contains(&PassId::GBufferPrepass));
assert!(!order.contains(&PassId::SsrPrepass));
assert!(!order.contains(&PassId::Velocity));
let gb = order
.iter()
.position(|p| *p == PassId::GBufferPrepass)
.unwrap();
let ssao = order.iter().position(|p| *p == PassId::SsaoBlur).unwrap();
let main = order.iter().position(|p| *p == PassId::Main).unwrap();
let taa = order.iter().position(|p| *p == PassId::TaaResolve).unwrap();
assert!(
gb < ssao && ssao < main,
"GBufferPrepass before SsaoBlur+Main"
);
assert!(gb < taa, "GBufferPrepass before TaaResolve");
}
#[test]
fn unified_gbuffer_prepass_runs_for_ssao_only() {
let mut i = all_off();
i.unified_gbuffer_prepass = true;
i.ssao_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert_eq!(
order,
vec![
PassId::GBufferPrepass,
PassId::SsaoBlur,
PassId::Main,
PassId::Composite,
]
);
}
#[test]
fn unified_gbuffer_prepass_runs_for_velocity_only() {
let mut i = all_off();
i.unified_gbuffer_prepass = true;
i.velocity_enabled = true;
i.taa_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert!(order.contains(&PassId::GBufferPrepass));
assert!(!order.contains(&PassId::Velocity));
}
#[test]
fn gbuffer_prepass_orders_after_cull_via_draw_args() {
let mut i = all_off();
i.bindless_cull_enabled = true;
i.unified_gbuffer_prepass = true;
i.ssao_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
let cull = order.iter().position(|p| *p == PassId::Cull).unwrap();
let gb = order
.iter()
.position(|p| *p == PassId::GBufferPrepass)
.unwrap();
let main = order.iter().position(|p| *p == PassId::Main).unwrap();
assert!(cull < gb, "Cull before GBufferPrepass");
assert!(gb < main, "GBufferPrepass before Main");
}
#[test]
fn unified_gbuffer_prepass_omitted_when_no_consumers() {
let mut i = all_off();
i.unified_gbuffer_prepass = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert_eq!(order, vec![PassId::Main, PassId::Composite]);
}
#[test]
fn auto_exposure_war_pinned_before_first_hdr_writer() {
let mut i = all_off();
i.auto_exposure_enabled = true;
i.decals_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert_eq!(
order,
vec![
PassId::Main,
PassId::AutoExposure,
PassId::Decals,
PassId::Composite,
]
);
}
#[test]
fn full_hdr_chain_orders_decals_fog_particles_then_ssr() {
let mut i = all_off();
i.decals_enabled = true;
i.fog_enabled = true;
i.particles_enabled = true;
i.ssr_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert_eq!(
order,
vec![
PassId::Main,
PassId::Decals,
PassId::FogFroxel,
PassId::Fog,
PassId::ParticlesDraw,
PassId::SsrResolve,
PassId::Composite,
]
);
let decals = &g.passes[1];
assert_eq!(decals.writes[0].version(), 2);
let fog = &g.passes[3];
assert_eq!(fog.writes[0].version(), 3);
let particles = &g.passes[4];
assert_eq!(particles.writes[0].version(), 4);
let ssr = &g.passes[5];
assert_eq!(ssr.reads[0].version(), 4);
}
#[test]
fn upscale_replaces_taa_and_pins_after_velocity() {
let mut i = all_off();
i.velocity_enabled = true;
i.upscale_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert!(order.contains(&PassId::Upscale));
assert!(!order.contains(&PassId::TaaResolve));
assert!(
order.iter().position(|p| *p == PassId::Velocity).unwrap()
< order.iter().position(|p| *p == PassId::Upscale).unwrap()
);
}
#[test]
fn upscale_takes_precedence_when_both_taa_and_upscale_requested() {
let mut i = all_off();
i.velocity_enabled = true;
i.taa_enabled = true;
i.upscale_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert!(order.contains(&PassId::Upscale));
assert!(!order.contains(&PassId::TaaResolve));
}
#[test]
fn velocity_taa_pinned_via_explicit_read() {
let mut i = all_off();
i.velocity_enabled = true;
i.taa_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert!(
order.iter().position(|p| *p == PassId::Velocity).unwrap()
< order.iter().position(|p| *p == PassId::TaaResolve).unwrap()
);
}
#[test]
fn transparent_pinned_between_ssr_resolve_and_taa() {
let mut i = all_off();
i.ssr_enabled = true;
i.taa_enabled = true;
i.velocity_enabled = true;
i.transparent_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
let pos = |p: PassId| order.iter().position(|x| *x == p).expect("present");
assert!(pos(PassId::SsrResolve) < pos(PassId::Transparent));
assert!(pos(PassId::Transparent) < pos(PassId::TaaResolve));
}
#[test]
fn transparent_works_without_ssr() {
let mut i = all_off();
i.taa_enabled = true;
i.velocity_enabled = true;
i.transparent_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert!(order.contains(&PassId::Transparent));
assert!(!order.contains(&PassId::SsrResolve));
let pos = |p: PassId| order.iter().position(|x| *x == p).expect("present");
assert!(pos(PassId::Main) < pos(PassId::Transparent));
assert!(pos(PassId::Transparent) < pos(PassId::TaaResolve));
}
#[test]
fn no_reflection_resolve_means_no_scene_pre_taa_resource() {
let mut i = all_off();
i.taa_enabled = true;
i.velocity_enabled = true;
i.transparent_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
assert!(
!g.resources.iter().any(|r| r.label == "scene_pre_taa"),
"scene_pre_taa is hdr_resolve here; it must not be declared twice"
);
i.ssr_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
assert!(g.resources.iter().any(|r| r.label == "scene_pre_taa"));
}
#[test]
fn no_temporal_pass_means_no_scene_color_resource() {
let mut i = all_off();
i.ssr_enabled = true;
i.bloom_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
assert!(
!g.resources.iter().any(|r| r.label == "scene_color"),
"scene_color is the pre-TAA scene here; it must not be declared twice"
);
let pre_taa = resource_of(&g, "scene_pre_taa");
let bloom = g
.passes
.iter()
.find(|p| p.id == PassId::Bloom)
.expect("bloom present");
assert!(
bloom.reads.iter().any(|r| r.resource_index() == pre_taa),
"Bloom reads the resolve output directly"
);
i.taa_enabled = true;
i.velocity_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
assert!(g.resources.iter().any(|r| r.label == "scene_color"));
}
#[test]
fn glass_without_a_reflection_resolve_extends_the_hdr_chain() {
let mut i = all_off();
i.transparent_enabled = true;
i.decals_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let hdr = resource_of(&g, "hdr_resolve");
let trans = g
.passes
.iter()
.find(|p| p.id == PassId::Transparent)
.expect("transparent present");
let write = trans
.writes
.iter()
.find(|w| w.resource_index() == hdr)
.expect("Transparent writes hdr_resolve");
let read = trans
.reads
.iter()
.find(|r| r.resource_index() == hdr)
.expect("Transparent reads hdr_resolve");
assert_eq!(
write.version(),
read.version() + 1,
"the glass blend extends the chain it read"
);
let composite = g
.passes
.iter()
.find(|p| p.id == PassId::Composite)
.expect("composite present");
assert!(
composite
.reads
.iter()
.any(|r| r.resource_index() == hdr && r.version() == write.version()),
"Composite reads the post-glass version"
);
}
#[test]
fn transparent_off_means_no_slot() {
let mut i = all_off();
i.ssr_enabled = true;
i.taa_enabled = true;
i.velocity_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert!(!order.contains(&PassId::Transparent));
}
#[test]
fn lines_close_the_hdr_decoration_chain() {
let mut i = all_off();
i.decals_enabled = true;
i.particles_enabled = true;
i.ssr_enabled = true;
i.lines_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
let pos = |p: PassId| order.iter().position(|x| *x == p).expect("present");
assert!(pos(PassId::Decals) < pos(PassId::Lines));
assert!(pos(PassId::ParticlesDraw) < pos(PassId::Lines));
assert!(pos(PassId::Lines) < pos(PassId::SsrResolve));
}
#[test]
fn lines_off_means_no_slot() {
let g = build_frame_graph(&all_off()).expect("compiles");
assert!(!g.passes.iter().any(|p| p.id == PassId::Lines));
}
#[test]
fn a_hidden_world_drops_the_lines() {
let mut i = all_off();
i.lines_enabled = true;
i.world_hidden = true;
let g = build_frame_graph(&i).expect("compiles");
assert!(!g.passes.iter().any(|p| p.id == PassId::Lines));
}
#[test]
fn ssgi_off_means_no_slot() {
let g = build_frame_graph(&all_off()).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert!(!order.contains(&PassId::Ssgi));
}
#[test]
fn rt_reflections_off_means_no_slot() {
let g = build_frame_graph(&all_off()).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert!(!order.contains(&PassId::RtReflections));
}
#[test]
fn rt_reflections_occupy_the_ssr_resolve_slot() {
let mut i = all_off();
i.rt_reflections_enabled = true;
i.particles_enabled = true;
i.taa_enabled = true;
i.velocity_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
let pos = |p: PassId| order.iter().position(|x| *x == p).expect("present");
assert!(order.contains(&PassId::RtReflections));
assert!(pos(PassId::ParticlesDraw) < pos(PassId::RtReflections));
assert!(pos(PassId::RtReflections) < pos(PassId::TaaResolve));
}
#[test]
fn rt_reflections_take_precedence_over_ssr_resolve() {
let mut i = all_off();
i.rt_reflections_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert!(order.contains(&PassId::RtReflections));
assert!(!order.contains(&PassId::SsrResolve));
let mut both = all_off();
both.ssr_enabled = true;
both.rt_reflections_enabled = true;
let g = build_frame_graph(&both).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert!(order.contains(&PassId::RtReflections));
assert!(!order.contains(&PassId::SsrResolve));
}
#[test]
fn ssgi_pinned_between_auto_exposure_and_decals() {
let mut i = all_off();
i.auto_exposure_enabled = true;
i.ssgi_enabled = true;
i.decals_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert_eq!(
order,
vec![
PassId::Main,
PassId::AutoExposure,
PassId::Ssgi,
PassId::Decals,
PassId::Composite,
]
);
let ssgi = &g.passes[2];
assert_eq!(ssgi.writes[0].version(), 2);
let decals = &g.passes[3];
assert_eq!(decals.writes[0].version(), 3);
}
#[test]
fn ssgi_after_raymarch_on_the_chain() {
let mut i = all_off();
i.raymarch_enabled = true;
i.ssgi_enabled = true;
i.ssr_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
let pos = |p: PassId| order.iter().position(|x| *x == p).expect("present");
assert!(pos(PassId::Raymarch) < pos(PassId::Ssgi));
assert!(pos(PassId::Ssgi) < pos(PassId::SsrResolve));
let ssgi = &g.passes[pos(PassId::Ssgi)];
assert_eq!(ssgi.writes[0].version(), 3);
}
#[test]
fn raymarch_off_means_no_slot() {
let g = build_frame_graph(&all_off()).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert!(!order.contains(&PassId::Raymarch));
}
#[test]
fn raymarch_pinned_between_auto_exposure_and_decals() {
let mut i = all_off();
i.auto_exposure_enabled = true;
i.raymarch_enabled = true;
i.decals_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert_eq!(
order,
vec![
PassId::Main,
PassId::AutoExposure,
PassId::Raymarch,
PassId::Decals,
PassId::Composite,
]
);
let raymarch = &g.passes[2];
assert_eq!(raymarch.writes[0].version(), 2);
let decals = &g.passes[3];
assert_eq!(decals.writes[0].version(), 3);
}
#[test]
fn raymarch_works_without_auto_exposure_or_decals() {
let mut i = all_off();
i.raymarch_enabled = true;
i.ssr_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert_eq!(
order,
vec![
PassId::Main,
PassId::Raymarch,
PassId::SsrResolve,
PassId::Composite,
]
);
let raymarch = &g.passes[1];
assert_eq!(raymarch.writes[0].version(), 2);
let ssr = &g.passes[2];
assert_eq!(ssr.reads[0].version(), 2);
}
#[test]
fn full_graph_orders_all_passes_correctly() {
let mut i = all_off();
i.shadow_enabled = true;
i.bindless_cull_enabled = true;
i.auto_exposure_enabled = true;
i.bloom_enabled = true;
i.velocity_enabled = true;
i.taa_enabled = true;
i.ssr_enabled = true;
i.particles_enabled = true;
i.fog_enabled = true;
i.decals_enabled = true;
i.ssr_prepass_enabled = true;
i.ssao_enabled = true;
i.transparent_enabled = true;
i.raymarch_enabled = true;
let g = build_frame_graph(&i).expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
fn idx(order: &[PassId], p: PassId) -> usize {
order.iter().position(|x| *x == p).expect("pass present")
}
assert!(idx(&order, PassId::Cull) < idx(&order, PassId::Main));
assert!(idx(&order, PassId::SsrPrepass) < idx(&order, PassId::Main));
assert!(idx(&order, PassId::SsaoBlur) < idx(&order, PassId::Main));
assert!(idx(&order, PassId::Shadow) < idx(&order, PassId::Main));
assert!(idx(&order, PassId::SsrPrepass) < idx(&order, PassId::SsaoBlur));
assert!(idx(&order, PassId::Main) < idx(&order, PassId::AutoExposure));
assert!(idx(&order, PassId::AutoExposure) < idx(&order, PassId::Raymarch));
assert!(idx(&order, PassId::Raymarch) < idx(&order, PassId::Decals));
assert!(idx(&order, PassId::Decals) < idx(&order, PassId::Fog));
assert!(idx(&order, PassId::Fog) < idx(&order, PassId::ParticlesDraw));
assert!(idx(&order, PassId::ParticlesDraw) < idx(&order, PassId::SsrResolve));
assert!(idx(&order, PassId::FogFroxel) < idx(&order, PassId::Fog));
assert!(idx(&order, PassId::Velocity) < idx(&order, PassId::TaaResolve));
assert!(idx(&order, PassId::SsrResolve) < idx(&order, PassId::Transparent));
assert!(idx(&order, PassId::Transparent) < idx(&order, PassId::TaaResolve));
assert!(idx(&order, PassId::TaaResolve) < idx(&order, PassId::Bloom));
assert!(idx(&order, PassId::Bloom) < idx(&order, PassId::Composite));
assert_eq!(order.last(), Some(&PassId::Composite));
assert!(g.passes.last().unwrap().presents);
}
}