diff --git a/Cargo.toml b/Cargo.toml index 5b6c236c..6bc3fa1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,7 +71,6 @@ winit = { workspace = true } blade-render = { version = "0.3", path = "blade-render" } [dev-dependencies] -egui_plot = "0.28" blade-macros = { version = "0.3", path = "blade-macros" } bytemuck = { workspace = true } choir = { workspace = true } diff --git a/blade-graphics/src/gles/command.rs b/blade-graphics/src/gles/command.rs index 0e9a7097..95c58d39 100644 --- a/blade-graphics/src/gles/command.rs +++ b/blade-graphics/src/gles/command.rs @@ -111,13 +111,6 @@ impl super::CommandEncoder { } } - pub fn start(&mut self) { - self.commands.clear(); - self.plain_data.clear(); - self.string_data.clear(); - self.has_present = false; - } - pub(super) fn finish(&mut self, gl: &glow::Context) { use glow::HasContext as _; #[allow(trivial_casts)] @@ -152,19 +145,13 @@ impl super::CommandEncoder { ); } let time = Duration::from_nanos(result - prev); - self.timings.push((pass_name, time)); + *self.timings.entry(pass_name).or_default() += time; prev = result } } } } - pub fn init_texture(&mut self, _texture: super::Texture) {} - - pub fn present(&mut self, _frame: super::Frame) { - self.has_present = true; - } - pub fn transfer(&mut self, label: &str) -> super::PassEncoder<()> { self.begin_pass(label); self.pass(super::PassKind::Transfer) @@ -260,8 +247,27 @@ impl super::CommandEncoder { pass.invalidate_attachments = invalidate_attachments; pass } +} + +#[hidden_trait::expose] +impl crate::traits::CommandEncoder for super::CommandEncoder { + type Texture = super::Texture; + type Frame = super::Frame; + + fn start(&mut self) { + self.commands.clear(); + self.plain_data.clear(); + self.string_data.clear(); + self.has_present = false; + } + + fn init_texture(&mut self, _texture: super::Texture) {} + + fn present(&mut self, _frame: super::Frame) { + self.has_present = true; + } - pub fn timings(&self) -> &[(String, Duration)] { + fn timings(&self) -> &crate::Timings { &self.timings } } diff --git a/blade-graphics/src/gles/mod.rs b/blade-graphics/src/gles/mod.rs index d3f6dca6..de7efdda 100644 --- a/blade-graphics/src/gles/mod.rs +++ b/blade-graphics/src/gles/mod.rs @@ -5,7 +5,7 @@ mod pipeline; mod platform; mod resource; -use std::{marker::PhantomData, mem, ops::Range, time::Duration}; +use std::{marker::PhantomData, mem, ops::Range}; type BindTarget = u32; const DEBUG_ID: u32 = 0; @@ -135,6 +135,7 @@ pub struct RenderPipeline { topology: crate::PrimitiveTopology, } +#[derive(Debug)] pub struct Frame { texture: Texture, } @@ -370,7 +371,7 @@ pub struct CommandEncoder { has_present: bool, limits: Limits, timing_datas: Option>, - timings: Vec<(String, Duration)>, + timings: crate::Timings, } enum PassKind { @@ -477,7 +478,7 @@ impl crate::traits::CommandDevice for Context { has_present: false, limits: self.limits.clone(), timing_datas, - timings: Vec::new(), + timings: Default::default(), } } diff --git a/blade-graphics/src/lib.rs b/blade-graphics/src/lib.rs index d354aca1..fe3dea4b 100644 --- a/blade-graphics/src/lib.rs +++ b/blade-graphics/src/lib.rs @@ -1110,3 +1110,5 @@ pub struct ScissorRect { pub w: u32, pub h: u32, } + +pub type Timings = std::collections::HashMap; diff --git a/blade-graphics/src/metal/command.rs b/blade-graphics/src/metal/command.rs index b2d0f279..4f148d61 100644 --- a/blade-graphics/src/metal/command.rs +++ b/blade-graphics/src/metal/command.rs @@ -116,39 +116,6 @@ impl super::CommandEncoder { self.raw.take().unwrap() } - pub fn start(&mut self) { - if let Some(ref mut td_array) = self.timing_datas { - self.timings.clear(); - td_array.rotate_left(1); - let td = td_array.first_mut().unwrap(); - if !td.pass_names.is_empty() { - let counters = td - .sample_buffer - .resolve_counter_range(metal::NSRange::new(0, td.pass_names.len() as u64 * 2)); - for (name, chunk) in td.pass_names.drain(..).zip(counters.chunks(2)) { - let duration = Duration::from_nanos(chunk[1] - chunk[0]); - self.timings.push((name, duration)); - } - } - } - - let queue = self.queue.lock().unwrap(); - self.raw = Some(objc::rc::autoreleasepool(|| { - let cmd_buf = queue.new_command_buffer_with_unretained_references(); - if !self.name.is_empty() { - cmd_buf.set_label(&self.name); - } - cmd_buf.to_owned() - })); - self.has_open_debug_group = false; - } - - pub fn init_texture(&mut self, _texture: super::Texture) {} - - pub fn present(&mut self, frame: super::Frame) { - self.raw.as_mut().unwrap().present_drawable(&frame.drawable); - } - pub fn transfer(&mut self, label: &str) -> super::TransferCommandEncoder { self.begin_pass(label); let raw = objc::rc::autoreleasepool(|| { @@ -313,8 +280,47 @@ impl super::CommandEncoder { phantom: PhantomData, } } +} + +#[hidden_trait::expose] +impl crate::traits::CommandEncoder for super::CommandEncoder { + type Texture = super::Texture; + type Frame = super::Frame; + + fn start(&mut self) { + if let Some(ref mut td_array) = self.timing_datas { + self.timings.clear(); + td_array.rotate_left(1); + let td = td_array.first_mut().unwrap(); + if !td.pass_names.is_empty() { + let counters = td + .sample_buffer + .resolve_counter_range(metal::NSRange::new(0, td.pass_names.len() as u64 * 2)); + for (name, chunk) in td.pass_names.drain(..).zip(counters.chunks(2)) { + let duration = Duration::from_nanos(chunk[1] - chunk[0]); + *self.timings.entry(name).or_default() += duration; + } + } + } + + let queue = self.queue.lock().unwrap(); + self.raw = Some(objc::rc::autoreleasepool(|| { + let cmd_buf = queue.new_command_buffer_with_unretained_references(); + if !self.name.is_empty() { + cmd_buf.set_label(&self.name); + } + cmd_buf.to_owned() + })); + self.has_open_debug_group = false; + } + + fn init_texture(&mut self, _texture: super::Texture) {} + + fn present(&mut self, frame: super::Frame) { + self.raw.as_mut().unwrap().present_drawable(&frame.drawable); + } - pub fn timings(&self) -> &[(String, Duration)] { + fn timings(&self) -> &crate::Timings { &self.timings } } diff --git a/blade-graphics/src/metal/mod.rs b/blade-graphics/src/metal/mod.rs index b508f01f..28ea9518 100644 --- a/blade-graphics/src/metal/mod.rs +++ b/blade-graphics/src/metal/mod.rs @@ -22,6 +22,7 @@ struct Surface { unsafe impl Send for Surface {} unsafe impl Sync for Surface {} +#[derive(Debug)] pub struct Frame { drawable: metal::MetalDrawable, texture: metal::Texture, @@ -199,7 +200,7 @@ pub struct CommandEncoder { enable_dispatch_type: bool, has_open_debug_group: bool, timing_datas: Option>, - timings: Vec<(String, time::Duration)>, + timings: crate::Timings, } #[derive(Debug)] @@ -568,7 +569,7 @@ impl crate::traits::CommandDevice for Context { enable_dispatch_type: self.info.enable_dispatch_type, has_open_debug_group: false, timing_datas, - timings: Vec::new(), + timings: Default::default(), } } diff --git a/blade-graphics/src/traits.rs b/blade-graphics/src/traits.rs index 0f71b242..5ac23273 100644 --- a/blade-graphics/src/traits.rs +++ b/blade-graphics/src/traits.rs @@ -47,6 +47,15 @@ pub trait CommandDevice { fn wait_for(&self, sp: &Self::SyncPoint, timeout_ms: u32) -> bool; } +pub trait CommandEncoder { + type Texture: Send + Sync + Clone + Copy + Debug; + type Frame: Send + Sync + Debug; + fn start(&mut self); + fn init_texture(&mut self, texture: Self::Texture); + fn present(&mut self, frame: Self::Frame); + fn timings(&self) -> &super::Timings; +} + pub trait TransferEncoder { type BufferPiece: Send + Sync + Clone + Copy + Debug; type TexturePiece: Send + Sync + Clone + Copy + Debug; diff --git a/blade-graphics/src/vulkan/command.rs b/blade-graphics/src/vulkan/command.rs index 9de98774..1cdaddc5 100644 --- a/blade-graphics/src/vulkan/command.rs +++ b/blade-graphics/src/vulkan/command.rs @@ -266,60 +266,6 @@ impl super::CommandEncoder { } } - pub fn start(&mut self) { - self.buffers.rotate_left(1); - let cmd_buf = self.buffers.first_mut().unwrap(); - self.device - .reset_descriptor_pool(&mut cmd_buf.descriptor_pool); - - let vk_info = vk::CommandBufferBeginInfo { - flags: vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT, - ..Default::default() - }; - unsafe { - self.device - .core - .begin_command_buffer(cmd_buf.raw, &vk_info) - .unwrap(); - } - - if let Some(ref timing) = self.device.timing { - self.timings.clear(); - if !cmd_buf.timed_pass_names.is_empty() { - let mut timestamps = [0u64; super::QUERY_POOL_SIZE]; - unsafe { - self.device - .core - .get_query_pool_results( - cmd_buf.query_pool, - 0, - &mut timestamps[..cmd_buf.timed_pass_names.len() + 1], - vk::QueryResultFlags::TYPE_64, - ) - .unwrap(); - } - let mut prev = timestamps[0]; - for (name, &ts) in cmd_buf - .timed_pass_names - .drain(..) - .zip(timestamps[1..].iter()) - { - let diff = (ts - prev) as f32 * timing.period; - prev = ts; - self.timings.push((name, Duration::from_nanos(diff as _))); - } - } - unsafe { - self.device.core.cmd_reset_query_pool( - cmd_buf.raw, - cmd_buf.query_pool, - 0, - super::QUERY_POOL_SIZE as u32, - ); - } - } - } - pub(super) fn finish(&mut self) -> vk::CommandBuffer { self.barrier(); self.add_marker("finish"); @@ -361,72 +307,6 @@ impl super::CommandEncoder { } } - pub fn init_texture(&mut self, texture: super::Texture) { - let barrier = vk::ImageMemoryBarrier { - old_layout: vk::ImageLayout::UNDEFINED, - new_layout: vk::ImageLayout::GENERAL, - image: texture.raw, - subresource_range: vk::ImageSubresourceRange { - aspect_mask: super::map_aspects(texture.format.aspects()), - base_mip_level: 0, - level_count: vk::REMAINING_MIP_LEVELS, - base_array_layer: 0, - layer_count: vk::REMAINING_ARRAY_LAYERS, - }, - ..Default::default() - }; - unsafe { - self.device.core.cmd_pipeline_barrier( - self.buffers[0].raw, - vk::PipelineStageFlags::TOP_OF_PIPE, - vk::PipelineStageFlags::ALL_COMMANDS, - vk::DependencyFlags::empty(), - &[], - &[], - &[barrier], - ); - } - } - - pub fn present(&mut self, frame: super::Frame) { - if frame.acquire_semaphore == vk::Semaphore::null() { - return; - } - - assert_eq!(self.present, None); - let wa = &self.device.workarounds; - self.present = Some(super::Presentation { - image_index: frame.image_index, - acquire_semaphore: frame.acquire_semaphore, - }); - - let barrier = vk::ImageMemoryBarrier { - old_layout: vk::ImageLayout::GENERAL, - new_layout: vk::ImageLayout::PRESENT_SRC_KHR, - image: frame.image, - subresource_range: vk::ImageSubresourceRange { - aspect_mask: vk::ImageAspectFlags::COLOR, - base_mip_level: 0, - level_count: 1, - base_array_layer: 0, - layer_count: 1, - }, - src_access_mask: vk::AccessFlags::MEMORY_WRITE | wa.extra_sync_src_access, - ..Default::default() - }; - unsafe { - self.device.core.cmd_pipeline_barrier( - self.buffers[0].raw, - vk::PipelineStageFlags::ALL_COMMANDS, - vk::PipelineStageFlags::BOTTOM_OF_PIPE, - vk::DependencyFlags::empty(), - &[], - &[], - &[barrier], - ); - } - } - pub fn transfer(&mut self, label: &str) -> super::TransferCommandEncoder { self.begin_pass(label); super::TransferCommandEncoder { @@ -546,8 +426,134 @@ impl super::CommandEncoder { Err(other) => panic!("GPU error {}", other), } } +} + +#[hidden_trait::expose] +impl crate::traits::CommandEncoder for super::CommandEncoder { + type Texture = super::Texture; + type Frame = super::Frame; + + fn start(&mut self) { + self.buffers.rotate_left(1); + let cmd_buf = self.buffers.first_mut().unwrap(); + self.device + .reset_descriptor_pool(&mut cmd_buf.descriptor_pool); + + let vk_info = vk::CommandBufferBeginInfo { + flags: vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT, + ..Default::default() + }; + unsafe { + self.device + .core + .begin_command_buffer(cmd_buf.raw, &vk_info) + .unwrap(); + } + + if let Some(ref timing) = self.device.timing { + self.timings.clear(); + if !cmd_buf.timed_pass_names.is_empty() { + let mut timestamps = [0u64; super::QUERY_POOL_SIZE]; + unsafe { + self.device + .core + .get_query_pool_results( + cmd_buf.query_pool, + 0, + &mut timestamps[..cmd_buf.timed_pass_names.len() + 1], + vk::QueryResultFlags::TYPE_64, + ) + .unwrap(); + } + let mut prev = timestamps[0]; + for (name, &ts) in cmd_buf + .timed_pass_names + .drain(..) + .zip(timestamps[1..].iter()) + { + let diff = (ts - prev) as f32 * timing.period; + prev = ts; + *self.timings.entry(name).or_default() += Duration::from_nanos(diff as _); + } + } + unsafe { + self.device.core.cmd_reset_query_pool( + cmd_buf.raw, + cmd_buf.query_pool, + 0, + super::QUERY_POOL_SIZE as u32, + ); + } + } + } + + fn init_texture(&mut self, texture: super::Texture) { + let barrier = vk::ImageMemoryBarrier { + old_layout: vk::ImageLayout::UNDEFINED, + new_layout: vk::ImageLayout::GENERAL, + image: texture.raw, + subresource_range: vk::ImageSubresourceRange { + aspect_mask: super::map_aspects(texture.format.aspects()), + base_mip_level: 0, + level_count: vk::REMAINING_MIP_LEVELS, + base_array_layer: 0, + layer_count: vk::REMAINING_ARRAY_LAYERS, + }, + ..Default::default() + }; + unsafe { + self.device.core.cmd_pipeline_barrier( + self.buffers[0].raw, + vk::PipelineStageFlags::TOP_OF_PIPE, + vk::PipelineStageFlags::ALL_COMMANDS, + vk::DependencyFlags::empty(), + &[], + &[], + &[barrier], + ); + } + } + + fn present(&mut self, frame: super::Frame) { + if frame.acquire_semaphore == vk::Semaphore::null() { + return; + } + + assert_eq!(self.present, None); + let wa = &self.device.workarounds; + self.present = Some(super::Presentation { + image_index: frame.image_index, + acquire_semaphore: frame.acquire_semaphore, + }); + + let barrier = vk::ImageMemoryBarrier { + old_layout: vk::ImageLayout::GENERAL, + new_layout: vk::ImageLayout::PRESENT_SRC_KHR, + image: frame.image, + subresource_range: vk::ImageSubresourceRange { + aspect_mask: vk::ImageAspectFlags::COLOR, + base_mip_level: 0, + level_count: 1, + base_array_layer: 0, + layer_count: 1, + }, + src_access_mask: vk::AccessFlags::MEMORY_WRITE | wa.extra_sync_src_access, + ..Default::default() + }; + unsafe { + self.device.core.cmd_pipeline_barrier( + self.buffers[0].raw, + vk::PipelineStageFlags::ALL_COMMANDS, + vk::PipelineStageFlags::BOTTOM_OF_PIPE, + vk::DependencyFlags::empty(), + &[], + &[], + &[barrier], + ); + } + } - pub fn timings(&self) -> &[(String, Duration)] { + fn timings(&self) -> &crate::Timings { &self.timings } } diff --git a/blade-graphics/src/vulkan/mod.rs b/blade-graphics/src/vulkan/mod.rs index 136a8f5e..bd6456dc 100644 --- a/blade-graphics/src/vulkan/mod.rs +++ b/blade-graphics/src/vulkan/mod.rs @@ -1,5 +1,5 @@ use ash::{khr, vk}; -use std::{num::NonZeroU32, path::PathBuf, ptr, sync::Mutex, time::Duration}; +use std::{num::NonZeroU32, path::PathBuf, ptr, sync::Mutex}; mod command; mod descriptor; @@ -253,7 +253,7 @@ pub struct CommandEncoder { present: Option, crash_handler: Option, temp_label: Vec, - timings: Vec<(String, Duration)>, + timings: crate::Timings, } pub struct TransferCommandEncoder<'a> { raw: vk::CommandBuffer, @@ -398,7 +398,7 @@ impl crate::traits::CommandDevice for Context { present: None, crash_handler, temp_label: Vec::new(), - timings: Vec::new(), + timings: Default::default(), } } diff --git a/blade-render/src/util/frame_pacer.rs b/blade-render/src/util/frame_pacer.rs index 2228c9c9..7619a480 100644 --- a/blade-render/src/util/frame_pacer.rs +++ b/blade-render/src/util/frame_pacer.rs @@ -64,4 +64,8 @@ impl FramePacer { mem::swap(&mut self.prev_resources, &mut self.next_resources); self.prev_sync_point.as_ref().unwrap() } + + pub fn timings(&self) -> &blade_graphics::Timings { + self.command_encoder.timings() + } } diff --git a/examples/particle/main.rs b/examples/particle/main.rs index 8ae86d8d..273e1412 100644 --- a/examples/particle/main.rs +++ b/examples/particle/main.rs @@ -119,7 +119,7 @@ impl Example { ui.heading("Particle System"); self.particle_system.add_gui(ui); ui.heading("Timings"); - for &(ref name, time) in self.command_encoder.timings() { + for (name, time) in self.command_encoder.timings() { let millis = time.as_secs_f32() * 1000.0; ui.horizontal(|ui| { ui.label(name); diff --git a/examples/scene/main.rs b/examples/scene/main.rs index 11437619..aab349a9 100644 --- a/examples/scene/main.rs +++ b/examples/scene/main.rs @@ -4,14 +4,12 @@ use blade_graphics as gpu; use blade_helpers::ControlledCamera; use std::{ - collections::VecDeque, fmt, fs, path::{Path, PathBuf}, sync::Arc, time, }; -const FRAME_TIME_HISTORY: usize = 30; const RENDER_WHILE_LOADING: bool = true; const MAX_DEPTH: f32 = 1e9; @@ -156,8 +154,6 @@ struct Example { need_accumulation_reset: bool, is_point_selected: bool, is_file_hovered: bool, - last_render_time: time::Instant, - render_times: VecDeque, ray_config: blade_render::RayConfig, denoiser_enabled: bool, denoiser_config: blade_render::DenoiserConfig, @@ -254,8 +250,6 @@ impl Example { need_accumulation_reset: true, is_point_selected: false, is_file_hovered: false, - last_render_time: time::Instant::now(), - render_times: VecDeque::with_capacity(FRAME_TIME_HISTORY), ray_config: blade_helpers::default_ray_config(), denoiser_enabled: true, denoiser_config: blade_render::DenoiserConfig { @@ -556,13 +550,6 @@ impl Example { use blade_helpers::{populate_debug_selection, ExposeHud as _}; use strum::IntoEnumIterator as _; - let delta = self.last_render_time.elapsed(); - self.last_render_time += delta; - while self.render_times.len() >= FRAME_TIME_HISTORY { - self.render_times.pop_back(); - } - self.render_times.push_front(delta.as_millis() as u32); - if self.scene_load_task.is_some() { ui.horizontal(|ui| { ui.label("Loading..."); @@ -669,30 +656,6 @@ impl Example { egui::CollapsingHeader::new("Tone Map").show(ui, |ui| { self.post_proc_config.populate_hud(ui); }); - - egui::CollapsingHeader::new("Performance").show(ui, |ui| { - let times = self.render_times.as_slices(); - let fd_points = egui_plot::PlotPoints::from_iter( - times - .0 - .iter() - .chain(times.1.iter()) - .enumerate() - .map(|(x, &y)| [x as f64, y as f64]), - ); - let fd_line = egui_plot::Line::new(fd_points).name("last"); - egui_plot::Plot::new("Frame time") - .allow_zoom(false) - .allow_scroll(false) - .allow_drag(false) - .show_x(false) - .include_y(0.0) - .show_axes([false, true]) - .show(ui, |plot_ui| { - plot_ui.line(fd_line); - plot_ui.hline(egui_plot::HLine::new(1000.0 / 60.0).name("smooth")); - }); - }); } #[profiling::function] diff --git a/src/lib.rs b/src/lib.rs index ea293d2b..15eb66f8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -763,6 +763,16 @@ impl Engine { } }); + egui::CollapsingHeader::new("Performance").show(ui, |ui| { + for (name, time) in self.pacer.timings() { + let millis = time.as_secs_f32() * 1000.0; + ui.horizontal(|ui| { + ui.label(name); + ui.colored_label(egui::Color32::WHITE, format!("{:.2} ms", millis)); + }); + } + }); + egui::CollapsingHeader::new("Objects") .default_open(true) .show(ui, |ui| {