Skip to content

Commit

Permalink
Disable render pass statistics when the required features aren't avai…
Browse files Browse the repository at this point in the history
…lable.

Also, `StatisticsRecorder` is now created once and reused every frame.
  • Loading branch information
LeshaInc committed Jul 12, 2023
1 parent 2db4ad7 commit 675faca
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 80 deletions.
3 changes: 2 additions & 1 deletion crates/bevy_render/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ use bevy_window::{PrimaryWindow, RawHandleWrapper};
use globals::GlobalsPlugin;
use renderer::{
sync_render_statistics, RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue,
RenderStatistics, RenderStatisticsMutex,
RenderStatistics, RenderStatisticsMutex, StatisticsRecorder,
};
use wgpu::Instance;

Expand Down Expand Up @@ -380,6 +380,7 @@ impl Plugin for RenderPlugin {
render_app
.insert_resource(RenderInstance(instance))
.insert_resource(PipelineCache::new(device.clone()))
.insert_resource(StatisticsRecorder::new(&device, &queue))
.insert_resource(device)
.insert_resource(queue)
.insert_resource(render_adapter)
Expand Down
13 changes: 8 additions & 5 deletions crates/bevy_render/src/renderer/graph_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::{
renderer::{RenderContext, RenderDevice},
};

use super::RenderStatisticsMutex;
use super::{RenderStatisticsMutex, StatisticsRecorder};

pub(crate) struct RenderGraphRunner;

Expand Down Expand Up @@ -57,11 +57,14 @@ impl RenderGraphRunner {
pub fn run(
graph: &RenderGraph,
render_device: RenderDevice,
mut statistics_recorder: StatisticsRecorder,
queue: &wgpu::Queue,
world: &World,
finalizer: impl FnOnce(&mut wgpu::CommandEncoder),
) -> Result<(), RenderGraphRunnerError> {
let mut render_context = RenderContext::new(render_device, queue);
) -> Result<StatisticsRecorder, RenderGraphRunnerError> {
statistics_recorder.begin_frame();

let mut render_context = RenderContext::new(render_device, statistics_recorder);
Self::run_graph(graph, None, &mut render_context, world, &[], None)?;
finalizer(render_context.command_encoder());

Expand All @@ -72,11 +75,11 @@ impl RenderGraphRunner {
}

let render_statistics_mutex = world.resource::<RenderStatisticsMutex>().0.clone();
render_context.download_statistics(queue, move |statistics| {
let statistics_recorder = render_context.download_statistics(queue, move |statistics| {
*render_statistics_mutex.lock() = Some(statistics);
});

Ok(())
Ok(statistics_recorder)
}

fn run_graph(
Expand Down
46 changes: 24 additions & 22 deletions crates/bevy_render/src/renderer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,32 +28,41 @@ pub fn render_system(world: &mut World) {
world.resource_scope(|world, mut graph: Mut<RenderGraph>| {
graph.update(world);
});

let statistics_recorder = world.remove_resource::<StatisticsRecorder>().unwrap();

let graph = world.resource::<RenderGraph>();
let render_device = world.resource::<RenderDevice>();
let render_queue = world.resource::<RenderQueue>();

if let Err(e) = RenderGraphRunner::run(
match RenderGraphRunner::run(
graph,
render_device.clone(), // TODO: is this clone really necessary?
statistics_recorder,
&render_queue.0,
world,
|encoder| {
crate::view::screenshot::submit_screenshot_commands(world, encoder);
},
) {
error!("Error running render graph:");
{
let mut src: &dyn std::error::Error = &e;
loop {
error!("> {}", src);
match src.source() {
Some(s) => src = s,
None => break,
Ok(statistics_recorder) => {
world.insert_resource(statistics_recorder);
}
Err(e) => {
error!("Error running render graph:");
{
let mut src: &dyn std::error::Error = &e;
loop {
error!("> {}", src);
match src.source() {
Some(s) => src = s,
None => break,
}
}
}
}

panic!("Error running render graph: {e}");
panic!("Error running render graph: {e}");
}
}

{
Expand Down Expand Up @@ -298,8 +307,7 @@ pub struct RenderContext {

impl RenderContext {
/// Creates a new [`RenderContext`] from a [`RenderDevice`].
pub fn new(render_device: RenderDevice, queue: &Queue) -> Self {
let statistics_recorder = StatisticsRecorder::new(&render_device, queue);
pub fn new(render_device: RenderDevice, statistics_recorder: StatisticsRecorder) -> Self {
Self {
render_device,
command_encoder: None,
Expand All @@ -313,11 +321,6 @@ impl RenderContext {
&self.render_device
}

/// Gets the underlying [`StatisticsRecorder`].
pub fn statistics_encoder(&mut self) -> &mut StatisticsRecorder {
&mut self.statistics_recorder
}

/// Gets the current [`CommandEncoder`].
pub fn command_encoder(&mut self) -> &mut CommandEncoder {
self.command_encoder.get_or_insert_with(|| {
Expand Down Expand Up @@ -368,15 +371,14 @@ impl RenderContext {

/// Downloads [`RenderStatistics`] from GPU, asynchronously calling the callback
/// when the data is available. Should be caled after `finish`.
///
/// If the statistics aren't available, the callback won't be invoked.
pub fn download_statistics(
&mut self,
mut self,
queue: &Queue,
callback: impl FnOnce(RenderStatistics) + Send + 'static,
) {
) -> StatisticsRecorder {
self.statistics_recorder
.download(&self.render_device, queue, callback);
self.statistics_recorder
}

fn flush_encoder(&mut self) {
Expand Down
154 changes: 102 additions & 52 deletions crates/bevy_render/src/renderer/statistics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use bevy_ecs::system::{Res, ResMut, Resource};
use bevy_utils::{Duration, HashMap, Instant};
use parking_lot::Mutex;
use wgpu::{
util::DownloadBuffer, Buffer, BufferDescriptor, BufferUsages, CommandEncoder,
util::DownloadBuffer, Buffer, BufferDescriptor, BufferUsages, CommandEncoder, Features,
PipelineStatisticsTypes, QuerySet, QuerySetDescriptor, QueryType, Queue, RenderPass,
RenderPassDescriptor,
};
Expand Down Expand Up @@ -54,11 +54,12 @@ struct PassRecord {

/// Records statistics into [`QuerySet`]'s keeping track of the mapping between
/// render passes and indices to the corresponding statistics in the [`QuerySet`].
#[derive(Resource)]
pub struct StatisticsRecorder {
timestamp_period: f32,
timestamps_query_set: QuerySet,
timestamps_query_set: Option<QuerySet>,
num_timestamps: u32,
pipeline_statistics_query_set: QuerySet,
pipeline_statistics_query_set: Option<QuerySet>,
num_pipeline_statistics: u32,
pass_records: HashMap<String, PassRecord>,
buffer: Option<Buffer>,
Expand All @@ -67,20 +68,34 @@ pub struct StatisticsRecorder {
impl StatisticsRecorder {
/// Creates the new `StatisticRecorder`
pub fn new(device: &RenderDevice, queue: &Queue) -> StatisticsRecorder {
let timestamp_period = queue.get_timestamp_period();
let features = device.features();

let timestamps_query_set = device.wgpu_device().create_query_set(&QuerySetDescriptor {
label: Some("timestamps_query_set"),
ty: QueryType::Timestamp,
count: MAX_TIMESTAMP_QUERIES,
});
let timestamp_period = if features.contains(Features::TIMESTAMP_QUERY) {
queue.get_timestamp_period()
} else {
0.0
};

let timestamps_query_set = if features.contains(Features::TIMESTAMP_QUERY_INSIDE_PASSES) {
Some(device.wgpu_device().create_query_set(&QuerySetDescriptor {
label: Some("timestamps_query_set"),
ty: QueryType::Timestamp,
count: MAX_TIMESTAMP_QUERIES,
}))
} else {
None
};

let pipeline_statistics_query_set =
device.wgpu_device().create_query_set(&QuerySetDescriptor {
label: Some("pipeline_statistics_query_set"),
ty: QueryType::PipelineStatistics(PipelineStatisticsTypes::all()),
count: MAX_PIPELINE_STATISTICS,
});
if features.contains(Features::PIPELINE_STATISTICS_QUERY) {
Some(device.wgpu_device().create_query_set(&QuerySetDescriptor {
label: Some("pipeline_statistics_query_set"),
ty: QueryType::PipelineStatistics(PipelineStatisticsTypes::all()),
count: MAX_PIPELINE_STATISTICS,
}))
} else {
None
};

StatisticsRecorder {
timestamp_period,
Expand All @@ -93,29 +108,39 @@ impl StatisticsRecorder {
}
}

/// Begins recording statistics for a new frame.
pub fn begin_frame(&mut self) {
self.num_timestamps = 0;
self.num_pipeline_statistics = 0;
self.pass_records.clear();
self.buffer = None;
}

fn pass_record(&mut self, name: &str) -> &mut PassRecord {
self.pass_records.entry(name.into()).or_default()
}

fn begin_render_pass(&mut self, pass: &mut RenderPass, name: &str) {
let begin_instant = Instant::now();

let begin_timestamp_index = if self.num_timestamps < MAX_TIMESTAMP_QUERIES {
let index = self.num_timestamps;
pass.write_timestamp(&self.timestamps_query_set, index);
self.num_timestamps += 1;
Some(index)
} else {
None
let begin_timestamp_index = match &self.timestamps_query_set {
Some(set) if self.num_timestamps < MAX_TIMESTAMP_QUERIES => {
let index = self.num_timestamps;
pass.write_timestamp(set, index);
self.num_timestamps += 1;
Some(index)
}
_ => None,
};

let pipeline_statistics_index = if self.num_pipeline_statistics < MAX_PIPELINE_STATISTICS {
let index = self.num_pipeline_statistics;
pass.begin_pipeline_statistics_query(&self.pipeline_statistics_query_set, index);
self.num_pipeline_statistics += 1;
Some(index)
} else {
None
let pipeline_statistics_index = match &self.pipeline_statistics_query_set {
Some(set) if self.num_pipeline_statistics < MAX_PIPELINE_STATISTICS => {
let index = self.num_pipeline_statistics;
pass.begin_pipeline_statistics_query(set, index);
self.num_pipeline_statistics += 1;
Some(index)
}
_ => None,
};

let record = self.pass_record(name);
Expand All @@ -125,13 +150,14 @@ impl StatisticsRecorder {
}

fn end_render_pass(&mut self, pass: &mut RenderPass, name: &str) {
let end_timestamp_index = if self.num_timestamps < MAX_TIMESTAMP_QUERIES {
let index = self.num_timestamps;
pass.write_timestamp(&self.timestamps_query_set, index);
self.num_timestamps += 1;
Some(index)
} else {
None
let end_timestamp_index = match &self.timestamps_query_set {
Some(set) if self.num_timestamps < MAX_TIMESTAMP_QUERIES => {
let index = self.num_timestamps;
pass.write_timestamp(set, index);
self.num_timestamps += 1;
Some(index)
}
_ => None,
};

let record = self.pass_record(name);
Expand Down Expand Up @@ -161,6 +187,10 @@ impl StatisticsRecorder {

/// Copies data from [`QuerySet`]'s to a buffer, after which it can be downloaded to CPU.
pub fn resolve(&mut self, encoder: &mut CommandEncoder, device: &RenderDevice) {
if self.timestamps_query_set.is_none() && self.pipeline_statistics_query_set.is_none() {
return;
}

let (buffer_size, pipeline_statistics_offset) = self.buffer_size();

let buffer = device.wgpu_device().create_buffer(&BufferDescriptor {
Expand All @@ -170,30 +200,29 @@ impl StatisticsRecorder {
mapped_at_creation: false,
});

if self.num_timestamps > 0 {
encoder.resolve_query_set(
&self.timestamps_query_set,
0..self.num_timestamps,
&buffer,
0,
);
match &self.timestamps_query_set {
Some(set) if self.num_timestamps > 0 => {
encoder.resolve_query_set(&set, 0..self.num_timestamps, &buffer, 0);
}
_ => {}
}

if self.num_pipeline_statistics > 0 {
encoder.resolve_query_set(
&self.pipeline_statistics_query_set,
0..self.num_pipeline_statistics,
&buffer,
pipeline_statistics_offset,
);
match &self.pipeline_statistics_query_set {
Some(set) if self.num_pipeline_statistics > 0 => {
encoder.resolve_query_set(
&set,
0..self.num_pipeline_statistics,
&buffer,
pipeline_statistics_offset,
);
}
_ => {}
}

self.buffer = Some(buffer);
}

/// Downloads the statistics from GPU, asynchronously calling the callback when the data is available.
///
/// If the statistics aren't available, the callback won't be invoked.
pub fn download(
&mut self,
device: &RenderDevice,
Expand All @@ -206,7 +235,23 @@ impl StatisticsRecorder {
let num_pipeline_statistics = self.num_pipeline_statistics;
let pass_records = std::mem::take(&mut self.pass_records);

let Some(buffer) = &self.buffer else { return };
let Some(buffer) = &self.buffer else {
// we still have cpu timings, so let's use them

let statistics = pass_records.into_iter().map(|(name, record)| {
let mut statistics = RenderPassStatistics::default();

if let (Some(begin), Some(end)) = (record.begin_instant, record.end_instant) {
statistics.elapsed_cpu = Some(end - begin);
}

(name, statistics)
});

callback(RenderStatistics(statistics.collect()));
return;
};

DownloadBuffer::read_buffer(device.wgpu_device(), queue, &buffer.slice(..), move |res| {
let buffer = match res {
Ok(v) => v,
Expand Down Expand Up @@ -316,9 +361,14 @@ impl std::fmt::Debug for MeasuredRenderPass<'_> {
}
}

/// Stores [`RenderStatistics`] shared between render app and main app.
///
/// This mutex is locked twice per frame: in `PreUpdate`, during [`sync_render_statistics`],
/// and after rendering has finished and statistics have been downloaded from GPU.
#[derive(Debug, Default, Clone, Resource)]
pub struct RenderStatisticsMutex(pub Arc<Mutex<Option<RenderStatistics>>>);

/// Copies fresh [`RenderStatistics`] from [`RenderStatisticsMutex`].
pub fn sync_render_statistics(
mutex: Res<RenderStatisticsMutex>,
mut statistics: ResMut<RenderStatistics>,
Expand Down

0 comments on commit 675faca

Please sign in to comment.