diff --git a/crates/renderer_rendergraph/benches/compile.rs b/crates/renderer_rendergraph/benches/compile.rs index 168ded2..c7ea6b0 100644 --- a/crates/renderer_rendergraph/benches/compile.rs +++ b/crates/renderer_rendergraph/benches/compile.rs @@ -1,17 +1,22 @@ //! These graph shapes compare command-level batching with render-pass span //! contraction and barrier hoisting. Each has two disjoint buffer access sets -//! joined by a shared-buffer dependency across the passes. +//! joined by a shared-buffer dependency across the passes. The resolve benchmarks +//! include device-dependent placement, alias-barrier rebuilding, and pass hoisting +//! in the timed region. -use std::{hint::black_box, time::Duration}; +use std::{hint::black_box, sync::Arc, time::Duration}; use ash::vk; use criterion::{BatchSize, Criterion, criterion_group, criterion_main}; +use renderer_device::device::{MemoryLocation, QueueFlags}; +use renderer_device::testing::TestVulkan; use renderer_rendergraph::{ Buffer, BufferRange, GraphBufferDesc, RenderGraph, commands::{BeginRendering, Command, EndRendering}, recorder::{CommandIndex, CommandRecorder, RecordError, RecordedCommand, SideEffectMap}, - resources::{SubresourceScope, SubresourceScopeInner}, + resources::{ResourceState, SubresourceScope, SubresourceScopeInner}, }; +use renderer_resources::{buffers::Buffer as DeviceBuffer, buffers::BufferDesc}; struct Use { buffer: Buffer, @@ -46,6 +51,37 @@ impl RecordedCommand for Use { } } +struct UseWithBridge { + buffer: Buffer, + bridge: Buffer, +} + +impl Command for UseWithBridge { + fn side_effects(&self, command_index: CommandIndex, map: &mut SideEffectMap) { + for buffer in [self.buffer, self.bridge] { + map.insert( + command_index, + buffer.id(), + SubresourceScope { + stage: vk::PipelineStageFlags2::COMPUTE_SHADER, + access: vk::AccessFlags2::SHADER_WRITE, + subresource: SubresourceScopeInner::Buffer(BufferRange::full()), + }, + ); + } + } +} + +impl RecordedCommand for UseWithBridge { + fn allowed_in_render_pass(&self) -> bool { + true + } + + fn apply(&mut self, _recorder: &mut CommandRecorder<'_>) -> Result<(), RecordError> { + Ok(()) + } +} + fn build(n_groups: usize, per: usize, pass: bool) -> RenderGraph { let mut graph = RenderGraph::new(); let a: Vec = (0..n_groups) @@ -113,6 +149,120 @@ fn build(n_groups: usize, per: usize, pass: bool) -> RenderGraph { graph } +fn build_overlapping(n_groups: usize, per: usize) -> RenderGraph { + let mut graph = RenderGraph::new(); + let buffers: Vec = (0..n_groups) + .map(|_| { + graph.create_buffer(GraphBufferDesc { + size: 4096, + ..Default::default() + }) + }) + .collect(); + let shared = graph.create_buffer(GraphBufferDesc { + size: 4096, + ..Default::default() + }); + + graph.push(BeginRendering { + color: vec![], + depth: None, + }); + for _ in 0..per { + for &buffer in &buffers { + graph.push(Use { + buffer, + write: true, + }); + } + } + graph.push(Use { + buffer: shared, + write: true, + }); + graph.push(EndRendering); + + graph.push(BeginRendering { + color: vec![], + depth: None, + }); + for _ in 0..per { + for &buffer in &buffers { + graph.push(Use { + buffer, + write: true, + }); + } + } + graph.push(Use { + buffer: shared, + write: false, + }); + graph.push(EndRendering); + + graph +} + +fn build_aliasing( + n_groups: usize, + per: usize, + bridge: &Arc, +) -> RenderGraph { + let mut graph = RenderGraph::new(); + let a: Vec = (0..n_groups) + .map(|_| { + graph.create_buffer(GraphBufferDesc { + size: 4096, + ..Default::default() + }) + }) + .collect(); + let b: Vec = (0..n_groups) + .map(|_| { + graph.create_buffer(GraphBufferDesc { + size: 4096, + ..Default::default() + }) + }) + .collect(); + let bridge = graph + .import_buffer(bridge.clone(), ResourceState::default()) + .unwrap(); + + // Keep the first set outside the pass; render-pass resources interfere with + // each other by design and otherwise prevent placement reuse. + for _ in 0..per { + for &buffer in &a { + graph.push(Use { + buffer, + write: true, + }); + } + } + for &buffer in &a { + graph.push(UseWithBridge { buffer, bridge }); + } + + graph.push(BeginRendering { + color: vec![], + depth: None, + }); + for &buffer in &b { + graph.push(UseWithBridge { buffer, bridge }); + } + for _ in 0..per { + for &buffer in &b { + graph.push(Use { + buffer, + write: true, + }); + } + } + graph.push(EndRendering); + + graph +} + fn compile_benchmarks(criterion: &mut Criterion) { let mut group = criterion.benchmark_group("compile"); group @@ -137,5 +287,95 @@ fn compile_benchmarks(criterion: &mut Criterion) { group.finish(); } -criterion_group!(benches, compile_benchmarks); +fn resolve_benchmarks(criterion: &mut Criterion, device: &renderer_device::Device) { + let bridge = Arc::new( + DeviceBuffer::new( + device.clone(), + BufferDesc { + size: 4096, + usage: vk::BufferUsageFlags::STORAGE_BUFFER, + queue_families: QueueFlags::empty(), + mem_location: MemoryLocation::GpuOnly, + ..Default::default() + }, + ) + .expect("create alias benchmark bridge buffer"), + ); + + let mut group = criterion.benchmark_group("compile_resolve"); + group + .measurement_time(Duration::from_secs(4)) + .sample_size(20); + + for (name, overlapping) in [("alias", false), ("no_alias", true)] { + for per in [20, 50, 100] { + let graph = if overlapping { + build_overlapping(50, per) + } else { + build_aliasing(50, per, &bridge) + }; + let mut probe = graph.compile().unwrap(); + probe.resolve(device).unwrap(); + assert_eq!( + probe + .resource_lifetimes() + .values() + .any(|lifetime| lifetime.placement.aliased), + !overlapping, + "unexpected placement aliasing in {name}/per_{per}" + ); + let alias_barriers = probe + .barriers() + .iter() + .flat_map(|barrier| &barrier.resources) + .filter(|resource| { + probe + .resource_lifetimes() + .get(&resource.id) + .is_some_and(|lifetime| lifetime.placement.aliased) + && resource.origin != renderer_rendergraph::BarrierOrigin::Initial + }) + .count(); + assert_eq!( + alias_barriers > 0, + !overlapping, + "unexpected alias barriers in {name}/per_{per}" + ); + + group.bench_function(format!("{name}/per_{per}"), |bencher| { + bencher.iter_batched( + || { + if overlapping { + build_overlapping(50, per) + } else { + build_aliasing(50, per, &bridge) + } + }, + |graph| { + let mut compiled = graph.compile().unwrap(); + compiled.resolve(device).unwrap(); + black_box(compiled.barriers().len()) + }, + BatchSize::LargeInput, + ); + }); + } + } + + group.finish(); +} + +fn benchmarks(criterion: &mut Criterion) { + let test = TestVulkan::acquire(); + + compile_benchmarks(criterion); + + if let Some(test) = &test { + resolve_benchmarks(criterion, test.device()); + } else { + eprintln!("Skipping compile_resolve benchmarks: no Vulkan device is available"); + } +} + +criterion_group!(benches, benchmarks); criterion_main!(benches); diff --git a/crates/renderer_rendergraph/src/graph.rs b/crates/renderer_rendergraph/src/graph.rs index cf81e2c..4d891a0 100644 --- a/crates/renderer_rendergraph/src/graph.rs +++ b/crates/renderer_rendergraph/src/graph.rs @@ -2357,6 +2357,7 @@ impl<'a> GraphCompiler<'a> { // for this barrier; prior barrier masks are not carried forward. let read_only = crate::resources::access_flag_is_read(current.scope.access) && !access_flag_is_write(current.scope.access); + let can_skip_history_scan = read_only && prior_access_summaries[current.resource.index()] .as_ref() @@ -2370,6 +2371,7 @@ impl<'a> GraphCompiler<'a> { } } }); + let previous = (!can_skip_history_scan) .then(|| &prior_accesses_by_resource[current.resource.index()]) .and_then(|prior_accesses| {