use std::hint::black_box;
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
use myth::renderer::graph::core::{
ExecuteContext, FrameArena, GraphStorage, PassBuilder, PassNode, RenderGraph, TextureDesc,
TextureNodeId,
};
struct MockNode {
tag: u32,
}
impl<'a> PassNode<'a> for MockNode {
fn execute(&self, _ctx: &ExecuteContext, _encoder: &mut wgpu::CommandEncoder) {
black_box(self.tag);
}
}
struct BorrowingMockNode<'a> {
data_ref: &'a u32,
}
impl<'a> PassNode<'a> for BorrowingMockNode<'a> {
fn execute(&self, _ctx: &ExecuteContext, _encoder: &mut wgpu::CommandEncoder) {
black_box(self.data_ref);
}
}
#[inline]
fn bench_desc() -> TextureDesc {
TextureDesc::new_2d(
1920,
1080,
wgpu::TextureFormat::Rgba16Float,
wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
)
}
fn bench_linear_chain_build_and_compile(c: &mut Criterion) {
let mut group = c.benchmark_group("LinearChain_BuildAndCompile");
for &pass_count in &[10, 50, 100, 200, 500] {
group.bench_with_input(
BenchmarkId::from_parameter(pass_count),
&pass_count,
|b, &count| {
let mut storage = GraphStorage::new();
let mut arena = FrameArena::new();
b.iter(|| {
arena.reset();
let mut graph = RenderGraph::new(&mut storage, &arena);
let backbuffer = graph.register_resource("Backbuffer", bench_desc(), true);
let mut current_tex = graph.add_pass("Pass_0", |builder: &mut PassBuilder| {
let out = builder.create_texture("Tex_0", bench_desc());
(MockNode { tag: 0 }, out)
});
for i in 1..count {
let prev = current_tex;
current_tex = graph.add_pass("Pass_N", |builder: &mut PassBuilder| {
builder.read_texture(prev);
let out = builder.create_texture("Tex_N", bench_desc());
(MockNode { tag: i as u32 }, out)
});
}
graph.add_pass("Final", |builder: &mut PassBuilder| {
builder.read_texture(current_tex);
builder.write_texture(backbuffer);
(MockNode { tag: u32::MAX }, ())
});
graph.compile_topology();
black_box(&graph);
});
},
);
}
group.finish();
}
fn bench_fan_in_topology(c: &mut Criterion) {
let mut group = c.benchmark_group("FanIn_Topology");
for &producer_count in &[10, 50, 100, 200, 500] {
group.bench_with_input(
BenchmarkId::from_parameter(producer_count),
&producer_count,
|b, &count| {
let mut storage = GraphStorage::new();
let mut arena = FrameArena::new();
b.iter(|| {
arena.reset();
let mut graph = RenderGraph::new(&mut storage, &arena);
let backbuffer = graph.register_resource("Backbuffer", bench_desc(), true);
let mut textures = Vec::with_capacity(count);
for i in 0..count {
let tex = graph.add_pass("Producer", |builder: &mut PassBuilder| {
let out = builder.create_texture("ProdTex", bench_desc());
(MockNode { tag: i as u32 }, out)
});
textures.push(tex);
}
graph.add_pass("Composite", |builder: &mut PassBuilder| {
for &tex in &textures {
builder.read_texture(tex);
}
builder.write_texture(backbuffer);
(MockNode { tag: u32::MAX }, ())
});
graph.compile_topology();
black_box(&graph);
});
},
);
}
group.finish();
}
fn bench_diamond_dag(c: &mut Criterion) {
let mut group = c.benchmark_group("DiamondDAG_Realistic");
for &repetitions in &[1, 5, 10, 20] {
group.bench_with_input(
BenchmarkId::new("pipelines", repetitions),
&repetitions,
|b, &reps| {
let mut storage = GraphStorage::new();
let mut arena = FrameArena::new();
b.iter(|| {
arena.reset();
let mut graph = RenderGraph::new(&mut storage, &arena);
let backbuffer = graph.register_resource("Backbuffer", bench_desc(), true);
let mut final_color = TextureNodeId(0);
for r in 0..reps {
let depth = graph.add_pass("Prepass", |builder: &mut PassBuilder| {
let out = builder.create_texture("Depth", bench_desc());
(MockNode { tag: r * 10 }, out)
});
let ssao = graph.add_pass("SSAO", |builder: &mut PassBuilder| {
builder.read_texture(depth);
let out = builder.create_texture("SSAO", bench_desc());
(MockNode { tag: r * 10 + 1 }, out)
});
let scene_color = graph.add_pass("Opaque", |builder: &mut PassBuilder| {
builder.read_texture(depth);
builder.read_texture(ssao);
let out = builder.create_texture("SceneColor", bench_desc());
(MockNode { tag: r * 10 + 2 }, out)
});
let bloom_tex = graph.add_pass("Bloom", |builder: &mut PassBuilder| {
builder.read_texture(scene_color);
let out = builder.create_texture("BloomTex", bench_desc());
(MockNode { tag: r * 10 + 3 }, out)
});
final_color = graph.add_pass("ToneMap", |builder: &mut PassBuilder| {
builder.read_texture(scene_color);
builder.read_texture(bloom_tex);
let out = builder.create_texture("Final", bench_desc());
(MockNode { tag: r * 10 + 4 }, out)
});
}
graph.add_pass("Present", |builder: &mut PassBuilder| {
builder.read_texture(final_color);
builder.write_texture(backbuffer);
(MockNode { tag: u32::MAX }, ())
});
graph.compile_topology();
black_box(&graph);
});
},
);
}
group.finish();
}
fn bench_alias_relay_chain(c: &mut Criterion) {
let mut group = c.benchmark_group("AliasRelayChain");
for &relay_count in &[5, 10, 50, 100, 200] {
group.bench_with_input(
BenchmarkId::from_parameter(relay_count),
&relay_count,
|b, &count| {
let mut storage = GraphStorage::new();
let mut arena = FrameArena::new();
b.iter(|| {
arena.reset();
let mut graph = RenderGraph::new(&mut storage, &arena);
let backbuffer = graph.register_resource("Backbuffer", bench_desc(), true);
let mut current = graph.add_pass("Opaque", |builder: &mut PassBuilder| {
let out = builder.create_texture("SceneColor_v0", bench_desc());
(MockNode { tag: 0 }, out)
});
for i in 1..count {
let prev = current;
current = graph.add_pass("Relay", |builder: &mut PassBuilder| {
let out = builder.mutate_texture(prev, "SceneColor_vN");
(MockNode { tag: i as u32 }, out)
});
}
graph.add_pass("ToneMap", |builder: &mut PassBuilder| {
builder.read_texture(current);
builder.write_texture(backbuffer);
(MockNode { tag: u32::MAX }, ())
});
graph.compile_topology();
black_box(&graph);
});
},
);
}
group.finish();
}
fn bench_dead_pass_culling(c: &mut Criterion) {
let mut group = c.benchmark_group("DeadPassCulling");
for &total_count in &[50, 100, 200, 500] {
group.bench_with_input(
BenchmarkId::new("total_passes", total_count),
&total_count,
|b, &count| {
let mut storage = GraphStorage::new();
let mut arena = FrameArena::new();
let alive_count = count / 10;
b.iter(|| {
arena.reset();
let mut graph = RenderGraph::new(&mut storage, &arena);
let backbuffer = graph.register_resource("Backbuffer", bench_desc(), true);
for i in 0..(count - alive_count) {
graph.add_pass("DeadPass", |builder: &mut PassBuilder| {
let _out = builder.create_texture("DeadTex", bench_desc());
(MockNode { tag: i as u32 }, ())
});
}
let mut current = graph.add_pass("AliveStart", |builder: &mut PassBuilder| {
let out = builder.create_texture("AliveTex_0", bench_desc());
(MockNode { tag: 1000 }, out)
});
for i in 1..alive_count {
let prev = current;
current = graph.add_pass("AliveN", |builder: &mut PassBuilder| {
builder.read_texture(prev);
let out = builder.create_texture("AliveTex_N", bench_desc());
(
MockNode {
tag: 1000 + i as u32,
},
out,
)
});
}
graph.add_pass("AliveEnd", |builder: &mut PassBuilder| {
builder.read_texture(current);
builder.write_texture(backbuffer);
(MockNode { tag: u32::MAX }, ())
});
graph.compile_topology();
black_box(&graph);
});
},
);
}
group.finish();
}
fn bench_arena_allocation(c: &mut Criterion) {
let mut group = c.benchmark_group("FrameArena_Allocation");
for &alloc_count in &[100, 500, 1000, 5000] {
group.bench_with_input(
BenchmarkId::from_parameter(alloc_count),
&alloc_count,
|b, &count| {
let mut arena = FrameArena::new();
b.iter(|| {
arena.reset();
for i in 0..count {
let ptr = arena.alloc(MockNode { tag: i as u32 });
black_box(ptr);
}
black_box(arena.allocated_bytes());
});
},
);
}
group.finish();
}
fn bench_arena_borrowing_nodes(c: &mut Criterion) {
let shared_data: Vec<u32> = (0..5000).collect();
c.bench_function("FrameArena_BorrowingNodes_1000", |b| {
let mut arena = FrameArena::new();
b.iter(|| {
arena.reset();
for i in 0..1000 {
let ptr = arena.alloc(BorrowingMockNode {
data_ref: &shared_data[i],
});
black_box(ptr);
}
black_box(arena.allocated_bytes());
});
});
}
fn bench_multi_frame_capacity_reuse(c: &mut Criterion) {
let mut group = c.benchmark_group("MultiFrame_CapacityReuse");
let pass_count = 100;
let mut storage = GraphStorage::new();
let mut arena = FrameArena::new();
{
arena.reset();
let mut graph = RenderGraph::new(&mut storage, &arena);
let bb = graph.register_resource("Backbuffer", bench_desc(), true);
let mut cur = graph.add_pass("P0", |builder: &mut PassBuilder| {
let out = builder.create_texture("T0", bench_desc());
(MockNode { tag: 0 }, out)
});
for i in 1..pass_count {
let prev = cur;
cur = graph.add_pass("PN", |builder: &mut PassBuilder| {
builder.read_texture(prev);
let out = builder.create_texture("TN", bench_desc());
(MockNode { tag: i }, out)
});
}
graph.add_pass("Final", |builder: &mut PassBuilder| {
builder.read_texture(cur);
builder.write_texture(bb);
(MockNode { tag: u32::MAX }, ())
});
graph.compile_topology();
}
group.bench_function("steady_state_100_passes", |b| {
b.iter(|| {
arena.reset();
let mut graph = RenderGraph::new(&mut storage, &arena);
let bb = graph.register_resource("Backbuffer", bench_desc(), true);
let mut cur = graph.add_pass("P0", |builder: &mut PassBuilder| {
let out = builder.create_texture("T0", bench_desc());
(MockNode { tag: 0 }, out)
});
for i in 1..pass_count {
let prev = cur;
cur = graph.add_pass("PN", |builder: &mut PassBuilder| {
builder.read_texture(prev);
let out = builder.create_texture("TN", bench_desc());
(MockNode { tag: i }, out)
});
}
graph.add_pass("Final", |builder: &mut PassBuilder| {
builder.read_texture(cur);
builder.write_texture(bb);
(MockNode { tag: u32::MAX }, ())
});
graph.compile_topology();
black_box(&graph);
});
});
group.finish();
}
fn bench_side_effect_passes(c: &mut Criterion) {
let mut group = c.benchmark_group("SideEffect_Passes");
for &count in &[10, 50, 100, 200] {
group.bench_with_input(BenchmarkId::from_parameter(count), &count, |b, &n| {
let mut storage = GraphStorage::new();
let mut arena = FrameArena::new();
b.iter(|| {
arena.reset();
let mut graph = RenderGraph::new(&mut storage, &arena);
let backbuffer = graph.register_resource("Backbuffer", bench_desc(), true);
for i in 0..n / 2 {
graph.add_pass("ShadowPass", |builder: &mut PassBuilder| {
builder.mark_side_effect();
(MockNode { tag: i as u32 }, ())
});
}
let mut cur = graph.add_pass("Opaque", |builder: &mut PassBuilder| {
let out = builder.create_texture("Color", bench_desc());
(MockNode { tag: 100 }, out)
});
for i in 1..(n / 2) {
let prev = cur;
cur = graph.add_pass("PostFX", |builder: &mut PassBuilder| {
builder.read_texture(prev);
let out = builder.create_texture("PostTex", bench_desc());
(
MockNode {
tag: 100 + i as u32,
},
out,
)
});
}
graph.add_pass("Present", |builder: &mut PassBuilder| {
builder.read_texture(cur);
builder.write_texture(backbuffer);
(MockNode { tag: u32::MAX }, ())
});
graph.compile_topology();
black_box(&graph);
});
});
}
group.finish();
}
fn bench_high_fidelity_pipeline(c: &mut Criterion) {
c.bench_function("HighFidelity_FullPipeline", |b| {
let mut storage = GraphStorage::new();
let mut arena = FrameArena::new();
b.iter(|| {
arena.reset();
let mut graph = RenderGraph::new(&mut storage, &arena);
let backbuffer = graph.register_resource("Backbuffer", bench_desc(), true);
graph.add_pass("Shadow", |builder: &mut PassBuilder| {
builder.mark_side_effect();
(MockNode { tag: 1 }, ())
});
let (depth, normals) = graph.add_pass("Prepass", |builder: &mut PassBuilder| {
let depth = builder.create_texture("Scene_Depth", bench_desc());
let normals = builder.create_texture("Scene_Normals", bench_desc());
(MockNode { tag: 2 }, (depth, normals))
});
let ssao_raw = graph.add_pass("SSAO_Raw", |builder: &mut PassBuilder| {
builder.read_texture(depth);
builder.read_texture(normals);
let out = builder.create_texture("SSAO_Raw", bench_desc());
(MockNode { tag: 3 }, out)
});
let ssao = graph.add_pass("SSAO_Blur", |builder: &mut PassBuilder| {
builder.read_texture(ssao_raw);
let out = builder.create_texture("SSAO", bench_desc());
(MockNode { tag: 4 }, out)
});
let scene_color = graph.add_pass("Opaque", |builder: &mut PassBuilder| {
builder.read_texture(depth);
builder.read_texture(ssao);
let out = builder.create_texture("Scene_Color_HDR", bench_desc());
(MockNode { tag: 5 }, out)
});
let scene_color_sky = graph.add_pass("Skybox", |builder: &mut PassBuilder| {
let out = builder.mutate_texture(scene_color, "Scene_Color_Sky");
(MockNode { tag: 6 }, out)
});
let transmission = graph.add_pass("TransmissionCopy", |builder: &mut PassBuilder| {
builder.read_texture(scene_color_sky);
let out = builder.create_texture("Transmission", bench_desc());
(MockNode { tag: 7 }, out)
});
let scene_color_transparent =
graph.add_pass("Transparent", |builder: &mut PassBuilder| {
let out = builder.mutate_texture(scene_color_sky, "Scene_Color_Transparent");
builder.read_texture(transmission);
builder.read_texture(depth);
(MockNode { tag: 8 }, out)
});
let mut bloom_mips = Vec::with_capacity(5);
let mut bloom_src = scene_color_transparent;
for i in 0..5 {
let src = bloom_src;
let mip = graph.add_pass("Bloom_Down", |builder: &mut PassBuilder| {
builder.read_texture(src);
let out = builder.create_texture("Bloom_Mip", bench_desc());
(MockNode { tag: 10 + i }, out)
});
bloom_mips.push(mip);
bloom_src = mip;
}
let mut bloom_up = *bloom_mips.last().unwrap();
for i in (0..4).rev() {
let up = bloom_up;
let down = bloom_mips[i];
bloom_up = graph.add_pass("Bloom_Up", |builder: &mut PassBuilder| {
builder.read_texture(up);
builder.read_texture(down);
let out = builder.create_texture("Bloom_Up", bench_desc());
(MockNode { tag: 20 + i as u32 }, out)
});
}
let bloom_result = graph.add_pass("Bloom_Composite", |builder: &mut PassBuilder| {
builder.read_texture(scene_color_transparent);
builder.read_texture(bloom_up);
let out = builder.create_texture("Bloom_Result", bench_desc());
(MockNode { tag: 30 }, out)
});
let tonemapped = graph.add_pass("ToneMapping", |builder: &mut PassBuilder| {
builder.read_texture(bloom_result);
let out = builder.create_texture("ToneMapped", bench_desc());
(MockNode { tag: 31 }, out)
});
graph.add_pass("FXAA", |builder: &mut PassBuilder| {
builder.read_texture(tonemapped);
builder.write_texture(backbuffer);
(MockNode { tag: 32 }, ())
});
graph.compile_topology();
black_box(&graph);
});
});
}
fn bench_build_only(c: &mut Criterion) {
let mut group = c.benchmark_group("Isolated_BuildOnly");
for &pass_count in &[50, 100, 500] {
group.bench_with_input(
BenchmarkId::from_parameter(pass_count),
&pass_count,
|b, &count| {
let mut storage = GraphStorage::new();
let mut arena = FrameArena::new();
b.iter(|| {
arena.reset();
let mut graph = RenderGraph::new(&mut storage, &arena);
let backbuffer = graph.register_resource("Backbuffer", bench_desc(), true);
let mut cur = graph.add_pass("P0", |builder: &mut PassBuilder| {
let out = builder.create_texture("T0", bench_desc());
(MockNode { tag: 0 }, out)
});
for i in 1..count {
let prev = cur;
cur = graph.add_pass("PN", |builder: &mut PassBuilder| {
builder.read_texture(prev);
let out = builder.create_texture("TN", bench_desc());
(MockNode { tag: i as u32 }, out)
});
}
graph.add_pass("Final", |builder: &mut PassBuilder| {
builder.read_texture(cur);
builder.write_texture(backbuffer);
(MockNode { tag: u32::MAX }, ())
});
black_box(&graph);
});
},
);
}
group.finish();
}
fn bench_compile_only(c: &mut Criterion) {
let mut group = c.benchmark_group("Isolated_CompileOnly");
for &pass_count in &[50, 100, 500] {
group.bench_with_input(
BenchmarkId::from_parameter(pass_count),
&pass_count,
|b, &count| {
let mut storage = GraphStorage::new();
let mut arena = FrameArena::new();
b.iter(|| {
arena.reset();
let mut graph = RenderGraph::new(&mut storage, &arena);
let backbuffer = graph.register_resource("Backbuffer", bench_desc(), true);
let mut cur = graph.add_pass("P0", |builder: &mut PassBuilder| {
let out = builder.create_texture("T0", bench_desc());
(MockNode { tag: 0 }, out)
});
for i in 1..count {
let prev = cur;
cur = graph.add_pass("PN", |builder: &mut PassBuilder| {
builder.read_texture(prev);
let out = builder.create_texture("TN", bench_desc());
(MockNode { tag: i as u32 }, out)
});
}
graph.add_pass("Final", |builder: &mut PassBuilder| {
builder.read_texture(cur);
builder.write_texture(backbuffer);
(MockNode { tag: u32::MAX }, ())
});
graph.compile_topology();
black_box(&graph);
});
},
);
}
group.finish();
}
criterion_group!(
benches,
bench_linear_chain_build_and_compile,
bench_fan_in_topology,
bench_diamond_dag,
bench_alias_relay_chain,
bench_dead_pass_culling,
bench_arena_allocation,
bench_arena_borrowing_nodes,
bench_multi_frame_capacity_reuse,
bench_side_effect_passes,
bench_high_fidelity_pipeline,
bench_build_only,
bench_compile_only,
);
criterion_main!(benches);