From 1de04926b1e12c9070af216ef108d08635f6f362 Mon Sep 17 00:00:00 2001 From: Nicolas Silva Date: Mon, 24 Jun 2024 11:20:10 +0200 Subject: [PATCH] Add infrastructure for counting and reporting internal resources (#5708) * Add an optional system for counting and reporting internal resources and events * Count API objects in wgpu-hal * Expose internal counters in wgpu-core and wgpu. --- wgpu-core/Cargo.toml | 5 + wgpu-core/src/device/global.rs | 15 +++ wgpu-core/src/device/resource.rs | 8 ++ wgpu-hal/src/dx12/device.rs | 80 +++++++++++++-- wgpu-hal/src/dx12/mod.rs | 1 + wgpu-hal/src/dx12/suballocation.rs | 22 +++++ wgpu-hal/src/empty.rs | 4 + wgpu-hal/src/gles/adapter.rs | 1 + wgpu-hal/src/gles/device.rs | 77 +++++++++++++-- wgpu-hal/src/gles/mod.rs | 1 + wgpu-hal/src/lib.rs | 2 + wgpu-hal/src/metal/adapter.rs | 1 + wgpu-hal/src/metal/device.rs | 90 ++++++++++++++--- wgpu-hal/src/metal/mod.rs | 1 + wgpu-hal/src/vulkan/adapter.rs | 2 + wgpu-hal/src/vulkan/device.rs | 86 ++++++++++++++-- wgpu-hal/src/vulkan/mod.rs | 3 + wgpu-types/Cargo.toml | 2 + wgpu-types/src/counters.rs | 153 +++++++++++++++++++++++++++++ wgpu-types/src/lib.rs | 3 + wgpu/Cargo.toml | 5 + wgpu/src/backend/webgpu.rs | 8 ++ wgpu/src/backend/wgpu_core.rs | 8 ++ wgpu/src/context.rs | 23 +++++ wgpu/src/lib.rs | 10 ++ 25 files changed, 579 insertions(+), 32 deletions(-) create mode 100644 wgpu-types/src/counters.rs diff --git a/wgpu-core/Cargo.toml b/wgpu-core/Cargo.toml index f8c28b879..03a632276 100644 --- a/wgpu-core/Cargo.toml +++ b/wgpu-core/Cargo.toml @@ -32,6 +32,11 @@ ignored = ["cfg_aliases"] [lib] [features] +## Internally count resources and events for debugging purposes. If the counters +## feature is disabled, the counting infrastructure is removed from the build and +## the exposed counters always return 0. +counters = ["wgt/counters"] + ## Log all API entry points at info instead of trace level. api_log_info = [] diff --git a/wgpu-core/src/device/global.rs b/wgpu-core/src/device/global.rs index bb3207f30..a646071be 100644 --- a/wgpu-core/src/device/global.rs +++ b/wgpu-core/src/device/global.rs @@ -2403,6 +2403,21 @@ impl Global { } } + pub fn device_get_internal_counters( + &self, + device_id: DeviceId, + ) -> wgt::InternalCounters { + let hub = A::hub(self); + if let Ok(device) = hub.devices.get(device_id) { + wgt::InternalCounters { + hal: device.get_hal_counters(), + core: wgt::CoreCounters {}, + } + } else { + Default::default() + } + } + pub fn queue_drop(&self, queue_id: QueueId) { profiling::scope!("Queue::drop"); api_log!("Queue::drop {queue_id:?}"); diff --git a/wgpu-core/src/device/resource.rs b/wgpu-core/src/device/resource.rs index 0f85a0d34..e6eb06199 100644 --- a/wgpu-core/src/device/resource.rs +++ b/wgpu-core/src/device/resource.rs @@ -404,6 +404,7 @@ impl Device { snatch_guard: SnatchGuard, ) -> Result<(UserClosures, bool), WaitIdleError> { profiling::scope!("Device::maintain"); + let fence = fence_guard.as_ref().unwrap(); let last_done_index = if maintain.is_wait() { let index_to_wait_for = match maintain { @@ -3641,6 +3642,13 @@ impl Device { pub(crate) fn new_usage_scope(&self) -> UsageScope<'_, A> { UsageScope::new_pooled(&self.usage_scopes, &self.tracker_indices) } + + pub fn get_hal_counters(&self) -> wgt::HalCounters { + self.raw + .as_ref() + .map(|raw| raw.get_internal_counters()) + .unwrap_or_default() + } } impl Device { diff --git a/wgpu-hal/src/dx12/device.rs b/wgpu-hal/src/dx12/device.rs index 83e5dde58..6e96a67f9 100644 --- a/wgpu-hal/src/dx12/device.rs +++ b/wgpu-hal/src/dx12/device.rs @@ -181,6 +181,7 @@ impl super::Device { null_rtv_handle, mem_allocator, dxc_container, + counters: Default::default(), }) } @@ -377,6 +378,8 @@ impl crate::Device for super::Device { unsafe { resource.SetName(cwstr.as_ptr()) }; } + self.counters.buffers.add(1); + Ok(super::Buffer { resource, size, @@ -388,11 +391,14 @@ impl crate::Device for super::Device { // Only happens when it's using the windows_rs feature and there's an allocation if let Some(alloc) = buffer.allocation.take() { super::suballocation::free_buffer_allocation( + self, alloc, // SAFETY: for allocations to exist, the allocator must exist unsafe { self.mem_allocator.as_ref().unwrap_unchecked() }, ); } + + self.counters.buffers.sub(1); } unsafe fn map_buffer( @@ -459,6 +465,8 @@ impl crate::Device for super::Device { unsafe { resource.SetName(cwstr.as_ptr()) }; } + self.counters.textures.add(1); + Ok(super::Texture { resource, format: desc.format, @@ -473,11 +481,14 @@ impl crate::Device for super::Device { unsafe fn destroy_texture(&self, mut texture: super::Texture) { if let Some(alloc) = texture.allocation.take() { super::suballocation::free_texture_allocation( + self, alloc, // SAFETY: for allocations to exist, the allocator must exist unsafe { self.mem_allocator.as_ref().unwrap_unchecked() }, ); } + + self.counters.textures.sub(1); } unsafe fn create_texture_view( @@ -487,6 +498,8 @@ impl crate::Device for super::Device { ) -> Result { let view_desc = desc.to_internal(texture); + self.counters.texture_views.add(1); + Ok(super::TextureView { raw_format: view_desc.rtv_dsv_format, aspects: view_desc.aspects, @@ -583,6 +596,7 @@ impl crate::Device for super::Device { }, }) } + unsafe fn destroy_texture_view(&self, view: super::TextureView) { if view.handle_srv.is_some() || view.handle_uav.is_some() { let mut pool = self.srv_uav_pool.lock(); @@ -605,6 +619,8 @@ impl crate::Device for super::Device { pool.free_handle(handle); } } + + self.counters.texture_views.sub(1); } unsafe fn create_sampler( @@ -643,10 +659,14 @@ impl crate::Device for super::Device { desc.lod_clamp.clone(), ); + self.counters.samplers.add(1); + Ok(super::Sampler { handle }) } + unsafe fn destroy_sampler(&self, sampler: super::Sampler) { self.sampler_pool.lock().free_handle(sampler.handle); + self.counters.samplers.sub(1); } unsafe fn create_command_encoder( @@ -663,6 +683,8 @@ impl crate::Device for super::Device { unsafe { allocator.SetName(cwstr.as_ptr()) }; } + self.counters.command_encoders.add(1); + Ok(super::CommandEncoder { allocator, device: self.raw.clone(), @@ -675,7 +697,10 @@ impl crate::Device for super::Device { end_of_pass_timer_query: None, }) } - unsafe fn destroy_command_encoder(&self, _encoder: super::CommandEncoder) {} + + unsafe fn destroy_command_encoder(&self, _encoder: super::CommandEncoder) { + self.counters.command_encoders.sub(1); + } unsafe fn create_bind_group_layout( &self, @@ -698,6 +723,8 @@ impl crate::Device for super::Device { } } + self.counters.bind_group_layouts.add(1); + let num_views = num_buffer_views + num_texture_views; Ok(super::BindGroupLayout { entries: desc.entries.to_vec(), @@ -724,7 +751,10 @@ impl crate::Device for super::Device { copy_counts: vec![1; num_views.max(num_samplers) as usize], }) } - unsafe fn destroy_bind_group_layout(&self, _bg_layout: super::BindGroupLayout) {} + + unsafe fn destroy_bind_group_layout(&self, _bg_layout: super::BindGroupLayout) { + self.counters.bind_group_layouts.sub(1); + } unsafe fn create_pipeline_layout( &self, @@ -1063,6 +1093,8 @@ impl crate::Device for super::Device { unsafe { raw.SetName(cwstr.as_ptr()) }; } + self.counters.pipeline_layouts.add(1); + Ok(super::PipelineLayout { shared: super::PipelineLayoutShared { signature: raw, @@ -1081,7 +1113,10 @@ impl crate::Device for super::Device { }, }) } - unsafe fn destroy_pipeline_layout(&self, _pipeline_layout: super::PipelineLayout) {} + + unsafe fn destroy_pipeline_layout(&self, _pipeline_layout: super::PipelineLayout) { + self.counters.pipeline_layouts.sub(1); + } unsafe fn create_bind_group( &self, @@ -1253,12 +1288,15 @@ impl crate::Device for super::Device { None => None, }; + self.counters.bind_groups.add(1); + Ok(super::BindGroup { handle_views, handle_samplers, dynamic_buffers, }) } + unsafe fn destroy_bind_group(&self, group: super::BindGroup) { if let Some(dual) = group.handle_views { self.shared.heap_views.free_slice(dual); @@ -1266,6 +1304,8 @@ impl crate::Device for super::Device { if let Some(dual) = group.handle_samplers { self.shared.heap_samplers.free_slice(dual); } + + self.counters.bind_groups.sub(1); } unsafe fn create_shader_module( @@ -1273,6 +1313,8 @@ impl crate::Device for super::Device { desc: &crate::ShaderModuleDescriptor, shader: crate::ShaderInput, ) -> Result { + self.counters.shader_modules.add(1); + let raw_name = desc.label.and_then(|label| ffi::CString::new(label).ok()); match shader { crate::ShaderInput::Naga(naga) => Ok(super::ShaderModule { naga, raw_name }), @@ -1282,6 +1324,7 @@ impl crate::Device for super::Device { } } unsafe fn destroy_shader_module(&self, _module: super::ShaderModule) { + self.counters.shader_modules.sub(1); // just drop } @@ -1463,6 +1506,8 @@ impl crate::Device for super::Device { unsafe { raw.SetName(cwstr.as_ptr()) }; } + self.counters.render_pipelines.add(1); + Ok(super::RenderPipeline { raw, layout: desc.layout.shared.clone(), @@ -1470,7 +1515,9 @@ impl crate::Device for super::Device { vertex_strides, }) } - unsafe fn destroy_render_pipeline(&self, _pipeline: super::RenderPipeline) {} + unsafe fn destroy_render_pipeline(&self, _pipeline: super::RenderPipeline) { + self.counters.render_pipelines.sub(1); + } unsafe fn create_compute_pipeline( &self, @@ -1502,12 +1549,17 @@ impl crate::Device for super::Device { unsafe { raw.SetName(cwstr.as_ptr()) }; } + self.counters.compute_pipelines.add(1); + Ok(super::ComputePipeline { raw, layout: desc.layout.shared.clone(), }) } - unsafe fn destroy_compute_pipeline(&self, _pipeline: super::ComputePipeline) {} + + unsafe fn destroy_compute_pipeline(&self, _pipeline: super::ComputePipeline) { + self.counters.compute_pipelines.sub(1); + } unsafe fn create_pipeline_cache( &self, @@ -1548,9 +1600,14 @@ impl crate::Device for super::Device { unsafe { raw.SetName(cwstr.as_ptr()) }; } + self.counters.query_sets.add(1); + Ok(super::QuerySet { raw, raw_ty }) } - unsafe fn destroy_query_set(&self, _set: super::QuerySet) {} + + unsafe fn destroy_query_set(&self, _set: super::QuerySet) { + self.counters.query_sets.sub(1); + } unsafe fn create_fence(&self) -> Result { let mut raw = d3d12::Fence::null(); @@ -1565,9 +1622,14 @@ impl crate::Device for super::Device { hr.into_device_result("Fence creation")?; null_comptr_check(&raw)?; + self.counters.fences.add(1); + Ok(super::Fence { raw }) } - unsafe fn destroy_fence(&self, _fence: super::Fence) {} + unsafe fn destroy_fence(&self, _fence: super::Fence) { + self.counters.fences.sub(1); + } + unsafe fn get_fence_value( &self, fence: &super::Fence, @@ -1711,4 +1773,8 @@ impl crate::Device for super::Device { // Destroy a D3D12 resource as per-usual. todo!() } + + fn get_internal_counters(&self) -> wgt::HalCounters { + self.counters.clone() + } } diff --git a/wgpu-hal/src/dx12/mod.rs b/wgpu-hal/src/dx12/mod.rs index 9d5f62f91..b71f051e3 100644 --- a/wgpu-hal/src/dx12/mod.rs +++ b/wgpu-hal/src/dx12/mod.rs @@ -261,6 +261,7 @@ pub struct Device { null_rtv_handle: descriptor::Handle, mem_allocator: Option>, dxc_container: Option>, + counters: wgt::HalCounters, } unsafe impl Send for Device {} diff --git a/wgpu-hal/src/dx12/suballocation.rs b/wgpu-hal/src/dx12/suballocation.rs index bd047b389..35204a1b9 100644 --- a/wgpu-hal/src/dx12/suballocation.rs +++ b/wgpu-hal/src/dx12/suballocation.rs @@ -118,6 +118,11 @@ mod placed { null_comptr_check(resource)?; + device + .counters + .buffer_memory + .add(allocation.size() as isize); + Ok((hr, Some(AllocationWrapper { allocation }))) } @@ -167,13 +172,23 @@ mod placed { null_comptr_check(resource)?; + device + .counters + .texture_memory + .add(allocation.size() as isize); + Ok((hr, Some(AllocationWrapper { allocation }))) } pub(crate) fn free_buffer_allocation( + device: &crate::dx12::Device, allocation: AllocationWrapper, allocator: &Mutex, ) { + device + .counters + .buffer_memory + .sub(allocation.allocation.size() as isize); match allocator.lock().allocator.free(allocation.allocation) { Ok(_) => (), // TODO: Don't panic here @@ -182,9 +197,14 @@ mod placed { } pub(crate) fn free_texture_allocation( + device: &crate::dx12::Device, allocation: AllocationWrapper, allocator: &Mutex, ) { + device + .counters + .texture_memory + .sub(allocation.allocation.size() as isize); match allocator.lock().allocator.free(allocation.allocation) { Ok(_) => (), // TODO: Don't panic here @@ -352,6 +372,7 @@ mod committed { #[allow(unused)] pub(crate) fn free_buffer_allocation( + _device: &crate::dx12::Device, _allocation: AllocationWrapper, _allocator: &Mutex, ) { @@ -360,6 +381,7 @@ mod committed { #[allow(unused)] pub(crate) fn free_texture_allocation( + _device: &crate::dx12::Device, _allocation: AllocationWrapper, _allocator: &Mutex, ) { diff --git a/wgpu-hal/src/empty.rs b/wgpu-hal/src/empty.rs index 8cba9d063..65116199f 100644 --- a/wgpu-hal/src/empty.rs +++ b/wgpu-hal/src/empty.rs @@ -276,6 +276,10 @@ impl crate::Device for Context { Default::default() } unsafe fn destroy_acceleration_structure(&self, _acceleration_structure: Resource) {} + + fn get_internal_counters(&self) -> wgt::HalCounters { + Default::default() + } } impl crate::CommandEncoder for Encoder { diff --git a/wgpu-hal/src/gles/adapter.rs b/wgpu-hal/src/gles/adapter.rs index 926b5afbc..3c2a55e6a 100644 --- a/wgpu-hal/src/gles/adapter.rs +++ b/wgpu-hal/src/gles/adapter.rs @@ -967,6 +967,7 @@ impl crate::Adapter for super::Adapter { main_vao, #[cfg(all(native, feature = "renderdoc"))] render_doc: Default::default(), + counters: Default::default(), }, queue: super::Queue { shared: Arc::clone(&self.shared), diff --git a/wgpu-hal/src/gles/device.rs b/wgpu-hal/src/gles/device.rs index afdc6ad7c..34201fcbd 100644 --- a/wgpu-hal/src/gles/device.rs +++ b/wgpu-hal/src/gles/device.rs @@ -632,6 +632,8 @@ impl crate::Device for super::Device { None }; + self.counters.buffers.add(1); + Ok(super::Buffer { raw, target, @@ -640,11 +642,14 @@ impl crate::Device for super::Device { data, }) } + unsafe fn destroy_buffer(&self, buffer: super::Buffer) { if let Some(raw) = buffer.raw { let gl = &self.shared.context.lock(); unsafe { gl.delete_buffer(raw) }; } + + self.counters.buffers.sub(1); } unsafe fn map_buffer( @@ -941,6 +946,8 @@ impl crate::Device for super::Device { super::TextureInner::Texture { raw, target } }; + self.counters.textures.add(1); + Ok(super::Texture { inner, drop_guard: None, @@ -951,6 +958,7 @@ impl crate::Device for super::Device { copy_size: desc.copy_extent(), }) } + unsafe fn destroy_texture(&self, texture: super::Texture) { if texture.drop_guard.is_none() { let gl = &self.shared.context.lock(); @@ -970,6 +978,8 @@ impl crate::Device for super::Device { // For clarity, we explicitly drop the drop guard. Although this has no real semantic effect as the // end of the scope will drop the drop guard since this function takes ownership of the texture. drop(texture.drop_guard); + + self.counters.textures.sub(1); } unsafe fn create_texture_view( @@ -977,6 +987,7 @@ impl crate::Device for super::Device { texture: &super::Texture, desc: &crate::TextureViewDescriptor, ) -> Result { + self.counters.texture_views.add(1); Ok(super::TextureView { //TODO: use `conv::map_view_dimension(desc.dimension)`? inner: texture.inner.clone(), @@ -986,7 +997,10 @@ impl crate::Device for super::Device { format: texture.format, }) } - unsafe fn destroy_texture_view(&self, _view: super::TextureView) {} + + unsafe fn destroy_texture_view(&self, _view: super::TextureView) { + self.counters.texture_views.sub(1); + } unsafe fn create_sampler( &self, @@ -1080,34 +1094,47 @@ impl crate::Device for super::Device { } } + self.counters.samplers.add(1); + Ok(super::Sampler { raw }) } + unsafe fn destroy_sampler(&self, sampler: super::Sampler) { let gl = &self.shared.context.lock(); unsafe { gl.delete_sampler(sampler.raw) }; + self.counters.samplers.sub(1); } unsafe fn create_command_encoder( &self, _desc: &crate::CommandEncoderDescriptor, ) -> Result { + self.counters.command_encoders.add(1); + Ok(super::CommandEncoder { cmd_buffer: super::CommandBuffer::default(), state: Default::default(), private_caps: self.shared.private_caps, }) } - unsafe fn destroy_command_encoder(&self, _encoder: super::CommandEncoder) {} + + unsafe fn destroy_command_encoder(&self, _encoder: super::CommandEncoder) { + self.counters.command_encoders.sub(1); + } unsafe fn create_bind_group_layout( &self, desc: &crate::BindGroupLayoutDescriptor, ) -> Result { + self.counters.bind_group_layouts.add(1); Ok(super::BindGroupLayout { entries: Arc::from(desc.entries), }) } - unsafe fn destroy_bind_group_layout(&self, _bg_layout: super::BindGroupLayout) {} + + unsafe fn destroy_bind_group_layout(&self, _bg_layout: super::BindGroupLayout) { + self.counters.bind_group_layouts.sub(1); + } unsafe fn create_pipeline_layout( &self, @@ -1184,6 +1211,8 @@ impl crate::Device for super::Device { }); } + self.counters.pipeline_layouts.add(1); + Ok(super::PipelineLayout { group_infos: group_infos.into_boxed_slice(), naga_options: glsl::Options { @@ -1194,7 +1223,10 @@ impl crate::Device for super::Device { }, }) } - unsafe fn destroy_pipeline_layout(&self, _pipeline_layout: super::PipelineLayout) {} + + unsafe fn destroy_pipeline_layout(&self, _pipeline_layout: super::PipelineLayout) { + self.counters.pipeline_layouts.sub(1); + } unsafe fn create_bind_group( &self, @@ -1270,17 +1302,24 @@ impl crate::Device for super::Device { contents.push(binding); } + self.counters.bind_groups.add(1); + Ok(super::BindGroup { contents: contents.into_boxed_slice(), }) } - unsafe fn destroy_bind_group(&self, _group: super::BindGroup) {} + + unsafe fn destroy_bind_group(&self, _group: super::BindGroup) { + self.counters.bind_groups.sub(1); + } unsafe fn create_shader_module( &self, desc: &crate::ShaderModuleDescriptor, shader: crate::ShaderInput, ) -> Result { + self.counters.shader_modules.add(1); + Ok(super::ShaderModule { naga: match shader { crate::ShaderInput::SpirV(_) => { @@ -1292,7 +1331,10 @@ impl crate::Device for super::Device { id: self.shared.next_shader_id.fetch_add(1, Ordering::Relaxed), }) } - unsafe fn destroy_shader_module(&self, _module: super::ShaderModule) {} + + unsafe fn destroy_shader_module(&self, _module: super::ShaderModule) { + self.counters.shader_modules.sub(1); + } unsafe fn create_render_pipeline( &self, @@ -1341,6 +1383,8 @@ impl crate::Device for super::Device { targets.into_boxed_slice() }; + self.counters.render_pipelines.add(1); + Ok(super::RenderPipeline { inner, primitive: desc.primitive, @@ -1363,6 +1407,7 @@ impl crate::Device for super::Device { alpha_to_coverage_enabled: desc.multisample.alpha_to_coverage_enabled, }) } + unsafe fn destroy_render_pipeline(&self, pipeline: super::RenderPipeline) { let mut program_cache = self.shared.program_cache.lock(); // If the pipeline only has 2 strong references remaining, they're `pipeline` and `program_cache` @@ -1377,6 +1422,8 @@ impl crate::Device for super::Device { let gl = &self.shared.context.lock(); unsafe { gl.delete_program(pipeline.inner.program) }; } + + self.counters.render_pipelines.sub(1); } unsafe fn create_compute_pipeline( @@ -1388,8 +1435,11 @@ impl crate::Device for super::Device { shaders.push((naga::ShaderStage::Compute, &desc.stage)); let inner = unsafe { self.create_pipeline(gl, shaders, desc.layout, desc.label, None) }?; + self.counters.compute_pipelines.add(1); + Ok(super::ComputePipeline { inner }) } + unsafe fn destroy_compute_pipeline(&self, pipeline: super::ComputePipeline) { let mut program_cache = self.shared.program_cache.lock(); // If the pipeline only has 2 strong references remaining, they're `pipeline` and `program_cache`` @@ -1404,6 +1454,8 @@ impl crate::Device for super::Device { let gl = &self.shared.context.lock(); unsafe { gl.delete_program(pipeline.inner.program) }; } + + self.counters.compute_pipelines.sub(1); } unsafe fn create_pipeline_cache( @@ -1437,6 +1489,8 @@ impl crate::Device for super::Device { queries.push(query); } + self.counters.query_sets.add(1); + Ok(super::QuerySet { queries: queries.into_boxed_slice(), target: match desc.ty { @@ -1446,24 +1500,31 @@ impl crate::Device for super::Device { }, }) } + unsafe fn destroy_query_set(&self, set: super::QuerySet) { let gl = &self.shared.context.lock(); for &query in set.queries.iter() { unsafe { gl.delete_query(query) }; } + self.counters.query_sets.sub(1); } + unsafe fn create_fence(&self) -> Result { + self.counters.fences.add(1); Ok(super::Fence { last_completed: 0, pending: Vec::new(), }) } + unsafe fn destroy_fence(&self, fence: super::Fence) { let gl = &self.shared.context.lock(); for (_, sync) in fence.pending { unsafe { gl.delete_sync(sync) }; } + self.counters.fences.sub(1); } + unsafe fn get_fence_value( &self, fence: &super::Fence, @@ -1542,6 +1603,10 @@ impl crate::Device for super::Device { unimplemented!() } unsafe fn destroy_acceleration_structure(&self, _acceleration_structure: ()) {} + + fn get_internal_counters(&self) -> wgt::HalCounters { + self.counters.clone() + } } #[cfg(send_sync)] diff --git a/wgpu-hal/src/gles/mod.rs b/wgpu-hal/src/gles/mod.rs index 058bdcf6f..73915d53e 100644 --- a/wgpu-hal/src/gles/mod.rs +++ b/wgpu-hal/src/gles/mod.rs @@ -268,6 +268,7 @@ pub struct Device { main_vao: glow::VertexArray, #[cfg(all(native, feature = "renderdoc"))] render_doc: crate::auxil::renderdoc::RenderDoc, + counters: wgt::HalCounters, } pub struct ShaderClearProgram { diff --git a/wgpu-hal/src/lib.rs b/wgpu-hal/src/lib.rs index 8d65bde8f..8af2e3a84 100644 --- a/wgpu-hal/src/lib.rs +++ b/wgpu-hal/src/lib.rs @@ -884,6 +884,8 @@ pub trait Device: WasmNotSendSync { &self, acceleration_structure: ::AccelerationStructure, ); + + fn get_internal_counters(&self) -> wgt::HalCounters; } pub trait Queue: WasmNotSendSync { diff --git a/wgpu-hal/src/metal/adapter.rs b/wgpu-hal/src/metal/adapter.rs index 33de70f71..4a1caf91a 100644 --- a/wgpu-hal/src/metal/adapter.rs +++ b/wgpu-hal/src/metal/adapter.rs @@ -62,6 +62,7 @@ impl crate::Adapter for super::Adapter { device: super::Device { shared: Arc::clone(&self.shared), features, + counters: Default::default(), }, queue: super::Queue { raw: Arc::new(Mutex::new(queue)), diff --git a/wgpu-hal/src/metal/device.rs b/wgpu-hal/src/metal/device.rs index 77ea8a0d8..6d32b864a 100644 --- a/wgpu-hal/src/metal/device.rs +++ b/wgpu-hal/src/metal/device.rs @@ -305,6 +305,7 @@ impl super::Device { super::Device { shared: Arc::new(super::AdapterShared::new(raw)), features, + counters: Default::default(), } } @@ -345,13 +346,16 @@ impl crate::Device for super::Device { if let Some(label) = desc.label { raw.set_label(label); } + self.counters.buffers.add(1); Ok(super::Buffer { raw, size: desc.size, }) }) } - unsafe fn destroy_buffer(&self, _buffer: super::Buffer) {} + unsafe fn destroy_buffer(&self, _buffer: super::Buffer) { + self.counters.buffers.sub(1); + } unsafe fn map_buffer( &self, @@ -418,6 +422,8 @@ impl crate::Device for super::Device { raw.set_label(label); } + self.counters.textures.add(1); + Ok(super::Texture { raw, format: desc.format, @@ -429,7 +435,9 @@ impl crate::Device for super::Device { }) } - unsafe fn destroy_texture(&self, _texture: super::Texture) {} + unsafe fn destroy_texture(&self, _texture: super::Texture) { + self.counters.textures.sub(1); + } unsafe fn create_texture_view( &self, @@ -489,9 +497,14 @@ impl crate::Device for super::Device { }) }; + self.counters.texture_views.add(1); + Ok(super::TextureView { raw, aspects }) } - unsafe fn destroy_texture_view(&self, _view: super::TextureView) {} + + unsafe fn destroy_texture_view(&self, _view: super::TextureView) { + self.counters.texture_views.sub(1); + } unsafe fn create_sampler( &self, @@ -548,15 +561,20 @@ impl crate::Device for super::Device { } let raw = self.shared.device.lock().new_sampler(&descriptor); + self.counters.samplers.add(1); + Ok(super::Sampler { raw }) }) } - unsafe fn destroy_sampler(&self, _sampler: super::Sampler) {} + unsafe fn destroy_sampler(&self, _sampler: super::Sampler) { + self.counters.samplers.sub(1); + } unsafe fn create_command_encoder( &self, desc: &crate::CommandEncoderDescriptor, ) -> Result { + self.counters.command_encoders.add(1); Ok(super::CommandEncoder { shared: Arc::clone(&self.shared), raw_queue: Arc::clone(&desc.queue.raw), @@ -565,17 +583,25 @@ impl crate::Device for super::Device { temp: super::Temp::default(), }) } - unsafe fn destroy_command_encoder(&self, _encoder: super::CommandEncoder) {} + + unsafe fn destroy_command_encoder(&self, _encoder: super::CommandEncoder) { + self.counters.command_encoders.sub(1); + } unsafe fn create_bind_group_layout( &self, desc: &crate::BindGroupLayoutDescriptor, ) -> DeviceResult { + self.counters.bind_group_layouts.add(1); + Ok(super::BindGroupLayout { entries: Arc::from(desc.entries), }) } - unsafe fn destroy_bind_group_layout(&self, _bg_layout: super::BindGroupLayout) {} + + unsafe fn destroy_bind_group_layout(&self, _bg_layout: super::BindGroupLayout) { + self.counters.bind_group_layouts.sub(1); + } unsafe fn create_pipeline_layout( &self, @@ -736,6 +762,8 @@ impl crate::Device for super::Device { resources: info.resources, }); + self.counters.pipeline_layouts.add(1); + Ok(super::PipelineLayout { bind_group_infos, push_constants_infos, @@ -744,7 +772,10 @@ impl crate::Device for super::Device { per_stage_map, }) } - unsafe fn destroy_pipeline_layout(&self, _pipeline_layout: super::PipelineLayout) {} + + unsafe fn destroy_pipeline_layout(&self, _pipeline_layout: super::PipelineLayout) { + self.counters.pipeline_layouts.sub(1); + } unsafe fn create_bind_group( &self, @@ -831,16 +862,22 @@ impl crate::Device for super::Device { } } + self.counters.bind_groups.add(1); + Ok(bg) } - unsafe fn destroy_bind_group(&self, _group: super::BindGroup) {} + unsafe fn destroy_bind_group(&self, _group: super::BindGroup) { + self.counters.bind_groups.sub(1); + } unsafe fn create_shader_module( &self, desc: &crate::ShaderModuleDescriptor, shader: crate::ShaderInput, ) -> Result { + self.counters.shader_modules.add(1); + match shader { crate::ShaderInput::Naga(naga) => Ok(super::ShaderModule { naga, @@ -851,7 +888,10 @@ impl crate::Device for super::Device { } } } - unsafe fn destroy_shader_module(&self, _module: super::ShaderModule) {} + + unsafe fn destroy_shader_module(&self, _module: super::ShaderModule) { + self.counters.shader_modules.sub(1); + } unsafe fn create_render_pipeline( &self, @@ -1094,6 +1134,8 @@ impl crate::Device for super::Device { ) })?; + self.counters.render_pipelines.add(1); + Ok(super::RenderPipeline { raw, vs_lib, @@ -1117,7 +1159,10 @@ impl crate::Device for super::Device { }) }) } - unsafe fn destroy_render_pipeline(&self, _pipeline: super::RenderPipeline) {} + + unsafe fn destroy_render_pipeline(&self, _pipeline: super::RenderPipeline) { + self.counters.render_pipelines.sub(1); + } unsafe fn create_compute_pipeline( &self, @@ -1165,6 +1210,8 @@ impl crate::Device for super::Device { ) })?; + self.counters.compute_pipelines.add(1); + Ok(super::ComputePipeline { raw, cs_info, @@ -1174,7 +1221,10 @@ impl crate::Device for super::Device { }) }) } - unsafe fn destroy_compute_pipeline(&self, _pipeline: super::ComputePipeline) {} + + unsafe fn destroy_compute_pipeline(&self, _pipeline: super::ComputePipeline) { + self.counters.compute_pipelines.sub(1); + } unsafe fn create_pipeline_cache( &self, @@ -1237,6 +1287,8 @@ impl crate::Device for super::Device { } }; + self.counters.query_sets.add(1); + Ok(super::QuerySet { raw_buffer: destination_buffer, counter_sample_buffer: Some(counter_sample_buffer), @@ -1249,15 +1301,23 @@ impl crate::Device for super::Device { } }) } - unsafe fn destroy_query_set(&self, _set: super::QuerySet) {} + + unsafe fn destroy_query_set(&self, _set: super::QuerySet) { + self.counters.query_sets.add(1); + } unsafe fn create_fence(&self) -> DeviceResult { + self.counters.fences.add(1); Ok(super::Fence { completed_value: Arc::new(atomic::AtomicU64::new(0)), pending_command_buffers: Vec::new(), }) } - unsafe fn destroy_fence(&self, _fence: super::Fence) {} + + unsafe fn destroy_fence(&self, _fence: super::Fence) { + self.counters.fences.sub(1); + } + unsafe fn get_fence_value(&self, fence: &super::Fence) -> DeviceResult { let mut max_value = fence.completed_value.load(atomic::Ordering::Acquire); for &(value, ref cmd_buf) in fence.pending_command_buffers.iter() { @@ -1348,4 +1408,8 @@ impl crate::Device for super::Device { ) { unimplemented!() } + + fn get_internal_counters(&self) -> wgt::HalCounters { + self.counters.clone() + } } diff --git a/wgpu-hal/src/metal/mod.rs b/wgpu-hal/src/metal/mod.rs index b944bb6e9..24f3fed80 100644 --- a/wgpu-hal/src/metal/mod.rs +++ b/wgpu-hal/src/metal/mod.rs @@ -339,6 +339,7 @@ impl Queue { pub struct Device { shared: Arc, features: wgt::Features, + counters: wgt::HalCounters, } pub struct Surface { diff --git a/wgpu-hal/src/vulkan/adapter.rs b/wgpu-hal/src/vulkan/adapter.rs index d3c0d4246..995930a76 100644 --- a/wgpu-hal/src/vulkan/adapter.rs +++ b/wgpu-hal/src/vulkan/adapter.rs @@ -1819,6 +1819,7 @@ impl super::Adapter { workarounds: self.workarounds, render_passes: Mutex::new(Default::default()), framebuffers: Mutex::new(Default::default()), + memory_allocations_counter: Default::default(), }); let relay_semaphores = super::RelaySemaphores::new(&shared)?; @@ -1881,6 +1882,7 @@ impl super::Adapter { naga_options, #[cfg(feature = "renderdoc")] render_doc: Default::default(), + counters: Default::default(), }; Ok(crate::OpenDevice { device, queue }) diff --git a/wgpu-hal/src/vulkan/device.rs b/wgpu-hal/src/vulkan/device.rs index ebb6d001d..d08831460 100644 --- a/wgpu-hal/src/vulkan/device.rs +++ b/wgpu-hal/src/vulkan/device.rs @@ -312,7 +312,10 @@ impl gpu_alloc::MemoryDevice for super::DeviceShared { } match unsafe { self.raw.allocate_memory(&info, None) } { - Ok(memory) => Ok(memory), + Ok(memory) => { + self.memory_allocations_counter.add(1); + Ok(memory) + } Err(vk::Result::ERROR_OUT_OF_DEVICE_MEMORY) => { Err(gpu_alloc::OutOfMemory::OutOfDeviceMemory) } @@ -325,6 +328,8 @@ impl gpu_alloc::MemoryDevice for super::DeviceShared { } unsafe fn deallocate_memory(&self, memory: vk::DeviceMemory) { + self.memory_allocations_counter.sub(1); + unsafe { self.raw.free_memory(memory, None) }; } @@ -910,6 +915,9 @@ impl crate::Device for super::Device { unsafe { self.shared.set_object_name(raw, label) }; } + self.counters.buffer_memory.add(block.size() as isize); + self.counters.buffers.add(1); + Ok(super::Buffer { raw, block: Some(Mutex::new(block)), @@ -918,12 +926,12 @@ impl crate::Device for super::Device { unsafe fn destroy_buffer(&self, buffer: super::Buffer) { unsafe { self.shared.raw.destroy_buffer(buffer.raw, None) }; if let Some(block) = buffer.block { - unsafe { - self.mem_allocator - .lock() - .dealloc(&*self.shared, block.into_inner()) - }; + let block = block.into_inner(); + self.counters.buffer_memory.sub(block.size() as isize); + unsafe { self.mem_allocator.lock().dealloc(&*self.shared, block) }; } + + self.counters.buffers.sub(1); } unsafe fn map_buffer( @@ -1049,6 +1057,8 @@ impl crate::Device for super::Device { )? }; + self.counters.texture_memory.add(block.size() as isize); + unsafe { self.shared .raw @@ -1059,6 +1069,8 @@ impl crate::Device for super::Device { unsafe { self.shared.set_object_name(raw, label) }; } + self.counters.textures.add(1); + Ok(super::Texture { raw, drop_guard: None, @@ -1075,8 +1087,12 @@ impl crate::Device for super::Device { unsafe { self.shared.raw.destroy_image(texture.raw, None) }; } if let Some(block) = texture.block { + self.counters.texture_memory.sub(block.size() as isize); + unsafe { self.mem_allocator.lock().dealloc(&*self.shared, block) }; } + + self.counters.textures.sub(1); } unsafe fn create_texture_view( @@ -1126,6 +1142,8 @@ impl crate::Device for super::Device { .collect(), }; + self.counters.texture_views.add(1); + Ok(super::TextureView { raw, layers, @@ -1143,6 +1161,8 @@ impl crate::Device for super::Device { fbuf_lock.retain(|key, _| !key.attachments.iter().any(|at| at.raw == view.raw)); } unsafe { self.shared.raw.destroy_image_view(view.raw, None) }; + + self.counters.texture_views.sub(1); } unsafe fn create_sampler( @@ -1184,10 +1204,14 @@ impl crate::Device for super::Device { unsafe { self.shared.set_object_name(raw, label) }; } + self.counters.samplers.add(1); + Ok(super::Sampler { raw }) } unsafe fn destroy_sampler(&self, sampler: super::Sampler) { unsafe { self.shared.raw.destroy_sampler(sampler.raw, None) }; + + self.counters.samplers.sub(1); } unsafe fn create_command_encoder( @@ -1199,6 +1223,8 @@ impl crate::Device for super::Device { .flags(vk::CommandPoolCreateFlags::TRANSIENT); let raw = unsafe { self.shared.raw.create_command_pool(&vk_info, None)? }; + self.counters.command_encoders.add(1); + Ok(super::CommandEncoder { raw, device: Arc::clone(&self.shared), @@ -1219,6 +1245,8 @@ impl crate::Device for super::Device { // fields. self.shared.raw.destroy_command_pool(cmd_encoder.raw, None); } + + self.counters.command_encoders.sub(1); } unsafe fn create_bind_group_layout( @@ -1339,6 +1367,8 @@ impl crate::Device for super::Device { unsafe { self.shared.set_object_name(raw, label) }; } + self.counters.bind_group_layouts.add(1); + Ok(super::BindGroupLayout { raw, desc_count, @@ -1352,6 +1382,8 @@ impl crate::Device for super::Device { .raw .destroy_descriptor_set_layout(bg_layout.raw, None) }; + + self.counters.bind_group_layouts.sub(1); } unsafe fn create_pipeline_layout( @@ -1403,6 +1435,8 @@ impl crate::Device for super::Device { } } + self.counters.pipeline_layouts.add(1); + Ok(super::PipelineLayout { raw, binding_arrays, @@ -1414,6 +1448,8 @@ impl crate::Device for super::Device { .raw .destroy_pipeline_layout(pipeline_layout.raw, None) }; + + self.counters.pipeline_layouts.sub(1); } unsafe fn create_bind_group( @@ -1596,14 +1632,20 @@ impl crate::Device for super::Device { } unsafe { self.shared.raw.update_descriptor_sets(&writes, &[]) }; + + self.counters.bind_groups.add(1); + Ok(super::BindGroup { set }) } + unsafe fn destroy_bind_group(&self, group: super::BindGroup) { unsafe { self.desc_allocator .lock() .free(&*self.shared, Some(group.set)) }; + + self.counters.bind_groups.sub(1); } unsafe fn create_shader_module( @@ -1661,8 +1703,11 @@ impl crate::Device for super::Device { unsafe { self.shared.set_object_name(raw, label) }; } + self.counters.shader_modules.add(1); + Ok(super::ShaderModule::Raw(raw)) } + unsafe fn destroy_shader_module(&self, module: super::ShaderModule) { match module { super::ShaderModule::Raw(raw) => { @@ -1670,6 +1715,8 @@ impl crate::Device for super::Device { } super::ShaderModule::Intermediate { .. } => {} } + + self.counters.shader_modules.sub(1); } unsafe fn create_render_pipeline( @@ -1900,10 +1947,14 @@ impl crate::Device for super::Device { unsafe { self.shared.raw.destroy_shader_module(raw_module, None) }; } + self.counters.render_pipelines.add(1); + Ok(super::RenderPipeline { raw }) } unsafe fn destroy_render_pipeline(&self, pipeline: super::RenderPipeline) { unsafe { self.shared.raw.destroy_pipeline(pipeline.raw, None) }; + + self.counters.render_pipelines.sub(1); } unsafe fn create_compute_pipeline( @@ -1946,10 +1997,15 @@ impl crate::Device for super::Device { unsafe { self.shared.raw.destroy_shader_module(raw_module, None) }; } + self.counters.compute_pipelines.add(1); + Ok(super::ComputePipeline { raw }) } + unsafe fn destroy_compute_pipeline(&self, pipeline: super::ComputePipeline) { unsafe { self.shared.raw.destroy_pipeline(pipeline.raw, None) }; + + self.counters.compute_pipelines.sub(1); } unsafe fn create_pipeline_cache( @@ -2001,18 +2057,26 @@ impl crate::Device for super::Device { unsafe { self.shared.set_object_name(raw, label) }; } + self.counters.query_sets.add(1); + Ok(super::QuerySet { raw }) } + unsafe fn destroy_query_set(&self, set: super::QuerySet) { unsafe { self.shared.raw.destroy_query_pool(set.raw, None) }; + + self.counters.query_sets.sub(1); } unsafe fn create_fence(&self) -> Result { + self.counters.fences.add(1); + Ok(if self.shared.private_caps.timeline_semaphores { let mut sem_type_info = vk::SemaphoreTypeCreateInfo::default().semaphore_type(vk::SemaphoreType::TIMELINE); let vk_info = vk::SemaphoreCreateInfo::default().push_next(&mut sem_type_info); let raw = unsafe { self.shared.raw.create_semaphore(&vk_info, None) }?; + super::Fence::TimelineSemaphore(raw) } else { super::Fence::FencePool { @@ -2040,6 +2104,8 @@ impl crate::Device for super::Device { } } } + + self.counters.fences.sub(1); } unsafe fn get_fence_value( &self, @@ -2320,6 +2386,14 @@ impl crate::Device for super::Device { .dealloc(&*self.shared, acceleration_structure.block.into_inner()); } } + + fn get_internal_counters(&self) -> wgt::HalCounters { + self.counters + .memory_allocations + .set(self.shared.memory_allocations_counter.read()); + + self.counters.clone() + } } impl super::DeviceShared { diff --git a/wgpu-hal/src/vulkan/mod.rs b/wgpu-hal/src/vulkan/mod.rs index 40e7a2cb4..f0d881614 100644 --- a/wgpu-hal/src/vulkan/mod.rs +++ b/wgpu-hal/src/vulkan/mod.rs @@ -43,6 +43,7 @@ use std::{ use arrayvec::ArrayVec; use ash::{ext, khr, vk}; use parking_lot::{Mutex, RwLock}; +use wgt::InternalCounter; const MILLIS_TO_NANOS: u64 = 1_000_000; const MAX_TOTAL_ATTACHMENTS: usize = crate::MAX_COLOR_ATTACHMENTS * 2 + 1; @@ -527,6 +528,7 @@ struct DeviceShared { features: wgt::Features, render_passes: Mutex>, framebuffers: Mutex>, + memory_allocations_counter: InternalCounter, } pub struct Device { @@ -538,6 +540,7 @@ pub struct Device { naga_options: naga::back::spv::Options<'static>, #[cfg(feature = "renderdoc")] render_doc: crate::auxil::renderdoc::RenderDoc, + counters: wgt::HalCounters, } /// Semaphores for forcing queue submissions to run in order. diff --git a/wgpu-types/Cargo.toml b/wgpu-types/Cargo.toml index ea18e6b33..c505aea5e 100644 --- a/wgpu-types/Cargo.toml +++ b/wgpu-types/Cargo.toml @@ -30,6 +30,8 @@ targets = [ [features] strict_asserts = [] fragile-send-sync-non-atomic-wasm = [] +# Enables some internal instrumentation for debugging purposes. +counters = [] [dependencies] bitflags = "2" diff --git a/wgpu-types/src/counters.rs b/wgpu-types/src/counters.rs new file mode 100644 index 000000000..d869b872c --- /dev/null +++ b/wgpu-types/src/counters.rs @@ -0,0 +1,153 @@ +#[cfg(feature = "counters")] +use std::sync::atomic::{AtomicIsize, Ordering}; + +/// An internal counter for debugging purposes +/// +/// Internally represented as an atomic isize if the `counters` feature is enabled, +/// or compiles to nothing otherwise. +pub struct InternalCounter { + #[cfg(feature = "counters")] + value: AtomicIsize, +} + +impl InternalCounter { + /// Creates a counter with value 0. + #[inline] + pub const fn new() -> Self { + InternalCounter { + #[cfg(feature = "counters")] + value: AtomicIsize::new(0), + } + } + + /// Get the counter's value. + #[cfg(feature = "counters")] + #[inline] + pub fn read(&self) -> isize { + self.value.load(Ordering::Relaxed) + } + + /// Get the counter's value. + /// + /// Always returns 0 if the `counters` feature is not enabled. + #[cfg(not(feature = "counters"))] + #[inline] + pub fn read(&self) -> isize { + 0 + } + + /// Get and reset the counter's value. + /// + /// Always returns 0 if the `counters` feature is not enabled. + #[cfg(feature = "counters")] + #[inline] + pub fn take(&self) -> isize { + self.value.swap(0, Ordering::Relaxed) + } + + /// Get and reset the counter's value. + /// + /// Always returns 0 if the `counters` feature is not enabled. + #[cfg(not(feature = "counters"))] + #[inline] + pub fn take(&self) -> isize { + 0 + } + + /// Increment the counter by the provided amount. + #[inline] + pub fn add(&self, _val: isize) { + #[cfg(feature = "counters")] + self.value.fetch_add(_val, Ordering::Relaxed); + } + + /// Decrement the counter by the provided amount. + #[inline] + pub fn sub(&self, _val: isize) { + #[cfg(feature = "counters")] + self.value.fetch_add(-_val, Ordering::Relaxed); + } + + /// Sets the counter to the provided value. + #[inline] + pub fn set(&self, _val: isize) { + #[cfg(feature = "counters")] + self.value.store(_val, Ordering::Relaxed); + } +} + +impl Clone for InternalCounter { + fn clone(&self) -> Self { + InternalCounter { + #[cfg(feature = "counters")] + value: AtomicIsize::new(self.read()), + } + } +} + +impl Default for InternalCounter { + fn default() -> Self { + Self::new() + } +} + +impl std::fmt::Debug for InternalCounter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.read().fmt(f) + } +} + +/// `wgpu-hal`'s internal counters. +#[derive(Clone, Default)] +pub struct HalCounters { + // API objects + /// + pub buffers: InternalCounter, + /// + pub textures: InternalCounter, + /// + pub texture_views: InternalCounter, + /// + pub bind_groups: InternalCounter, + /// + pub bind_group_layouts: InternalCounter, + /// + pub render_pipelines: InternalCounter, + /// + pub compute_pipelines: InternalCounter, + /// + pub pipeline_layouts: InternalCounter, + /// + pub samplers: InternalCounter, + /// + pub command_encoders: InternalCounter, + /// + pub shader_modules: InternalCounter, + /// + pub query_sets: InternalCounter, + /// + pub fences: InternalCounter, + + // Resources + /// Amount of allocated gpu memory attributed to buffers, in bytes. + pub buffer_memory: InternalCounter, + /// Amount of allocated gpu memory attributed to textures, in bytes. + pub texture_memory: InternalCounter, + /// Number of gpu memory allocations. + pub memory_allocations: InternalCounter, +} + +/// `wgpu-core`'s internal counters. +#[derive(Clone, Default)] +pub struct CoreCounters { + // TODO +} + +/// All internal counters, exposed for debugging purposes. +#[derive(Clone, Default)] +pub struct InternalCounters { + /// `wgpu-core` counters. + pub core: CoreCounters, + /// `wgpu-hal` counters. + pub hal: HalCounters, +} diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index b1037c931..a345a5743 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -18,8 +18,11 @@ use std::path::PathBuf; use std::{num::NonZeroU32, ops::Range}; pub mod assertions; +mod counters; pub mod math; +pub use counters::*; + // Use this macro instead of the one provided by the bitflags_serde_shim crate // because the latter produces an error when deserializing bits that are not // specified in the bitflags, while we want deserialization to succeed and diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 81927f0a6..d8538a6ed 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -97,6 +97,11 @@ replay = ["serde", "wgc/replay"] #! ### Other # -------------------------------------------------------------------- +## Internally count resources and events for debugging purposes. If the counters +## feature is disabled, the counting infrastructure is removed from the build and +## the exposed counters always return 0. +counters = ["wgc/counters"] + ## Implement `Send` and `Sync` on Wasm, but only if atomics are not enabled. ## ## WebGL/WebGPU objects can not be shared between threads. diff --git a/wgpu/src/backend/webgpu.rs b/wgpu/src/backend/webgpu.rs index ac663df89..fc727003a 100644 --- a/wgpu/src/backend/webgpu.rs +++ b/wgpu/src/backend/webgpu.rs @@ -2978,6 +2978,14 @@ impl crate::context::Context for ContextWebGpu { fn device_start_capture(&self, _device: &Self::DeviceId, _device_data: &Self::DeviceData) {} fn device_stop_capture(&self, _device: &Self::DeviceId, _device_data: &Self::DeviceData) {} + fn device_get_internal_counters( + &self, + _device: &Self::DeviceId, + _device_data: &Self::DeviceData, + ) -> wgt::InternalCounters { + Default::default() + } + fn pipeline_cache_get_data( &self, _: &Self::PipelineCacheId, diff --git a/wgpu/src/backend/wgpu_core.rs b/wgpu/src/backend/wgpu_core.rs index e00bd4a38..4553dc03c 100644 --- a/wgpu/src/backend/wgpu_core.rs +++ b/wgpu/src/backend/wgpu_core.rs @@ -2370,6 +2370,14 @@ impl crate::Context for ContextWgpuCore { wgc::gfx_select!(device => self.0.device_stop_capture(*device)); } + fn device_get_internal_counters( + &self, + device: &Self::DeviceId, + _device_data: &Self::DeviceData, + ) -> wgt::InternalCounters { + wgc::gfx_select!(device => self.0.device_get_internal_counters(*device)) + } + fn pipeline_cache_get_data( &self, cache: &Self::PipelineCacheId, diff --git a/wgpu/src/context.rs b/wgpu/src/context.rs index 14bc60316..cba2dbb14 100644 --- a/wgpu/src/context.rs +++ b/wgpu/src/context.rs @@ -611,6 +611,13 @@ pub trait Context: Debug + WasmNotSendSync + Sized { fn device_start_capture(&self, device: &Self::DeviceId, device_data: &Self::DeviceData); fn device_stop_capture(&self, device: &Self::DeviceId, device_data: &Self::DeviceData); + + fn device_get_internal_counters( + &self, + device: &Self::DeviceId, + _device_data: &Self::DeviceData, + ) -> wgt::InternalCounters; + fn pipeline_cache_get_data( &self, cache: &Self::PipelineCacheId, @@ -1604,6 +1611,12 @@ pub(crate) trait DynContext: Debug + WasmNotSendSync { fn device_start_capture(&self, device: &ObjectId, data: &crate::Data); fn device_stop_capture(&self, device: &ObjectId, data: &crate::Data); + fn device_get_internal_counters( + &self, + device: &ObjectId, + device_data: &crate::Data, + ) -> wgt::InternalCounters; + fn pipeline_cache_get_data( &self, cache: &ObjectId, @@ -3078,6 +3091,16 @@ where Context::device_stop_capture(self, &device, device_data) } + fn device_get_internal_counters( + &self, + device: &ObjectId, + device_data: &crate::Data, + ) -> wgt::InternalCounters { + let device = ::from(*device); + let device_data = downcast_ref(device_data); + Context::device_get_internal_counters(self, &device, device_data) + } + fn pipeline_cache_get_data( &self, cache: &ObjectId, diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 593e904fd..6670716c8 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -3167,6 +3167,16 @@ impl Device { DynContext::device_stop_capture(&*self.context, &self.id, self.data.as_ref()) } + /// Query internal counters from the native backend for debugging purposes. + /// + /// Some backends may not set all counters, or may not set any counter at all. + /// The `counters` cargo feature must be enabled for any counter to be set. + /// + /// If a counter is not set, its contains its default value (zero). + pub fn get_internal_counters(&self) -> wgt::InternalCounters { + DynContext::device_get_internal_counters(&*self.context, &self.id, self.data.as_ref()) + } + /// Apply a callback to this `Device`'s underlying backend device. /// /// If this `Device` is implemented by the backend API given by `A` (Vulkan,