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.
This commit is contained in:
Nicolas Silva 2024-06-24 11:20:10 +02:00 committed by GitHub
parent afc8e38fc1
commit 1de04926b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 579 additions and 32 deletions

View File

@ -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 = []

View File

@ -2403,6 +2403,21 @@ impl Global {
}
}
pub fn device_get_internal_counters<A: HalApi>(
&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<A: HalApi>(&self, queue_id: QueueId) {
profiling::scope!("Queue::drop");
api_log!("Queue::drop {queue_id:?}");

View File

@ -404,6 +404,7 @@ impl<A: HalApi> Device<A> {
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<A: HalApi> Device<A> {
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<A: HalApi> Device<A> {

View File

@ -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<super::TextureView, DeviceError> {
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<super::ShaderModule, crate::ShaderError> {
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<super::Fence, DeviceError> {
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()
}
}

View File

@ -261,6 +261,7 @@ pub struct Device {
null_rtv_handle: descriptor::Handle,
mem_allocator: Option<Mutex<suballocation::GpuAllocatorWrapper>>,
dxc_container: Option<Arc<shader_compilation::DxcContainer>>,
counters: wgt::HalCounters,
}
unsafe impl Send for Device {}

View File

@ -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<GpuAllocatorWrapper>,
) {
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<GpuAllocatorWrapper>,
) {
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<GpuAllocatorWrapper>,
) {
@ -360,6 +381,7 @@ mod committed {
#[allow(unused)]
pub(crate) fn free_texture_allocation(
_device: &crate::dx12::Device,
_allocation: AllocationWrapper,
_allocator: &Mutex<GpuAllocatorWrapper>,
) {

View File

@ -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 {

View File

@ -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),

View File

@ -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<super::TextureView, crate::DeviceError> {
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<super::Api>,
) -> Result<super::CommandEncoder, crate::DeviceError> {
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<super::BindGroupLayout, crate::DeviceError> {
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<super::ShaderModule, crate::ShaderError> {
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<super::Fence, crate::DeviceError> {
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)]

View File

@ -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 {

View File

@ -884,6 +884,8 @@ pub trait Device: WasmNotSendSync {
&self,
acceleration_structure: <Self::A as Api>::AccelerationStructure,
);
fn get_internal_counters(&self) -> wgt::HalCounters;
}
pub trait Queue: WasmNotSendSync {

View File

@ -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)),

View File

@ -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<super::Api>,
) -> Result<super::CommandEncoder, crate::DeviceError> {
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<super::BindGroupLayout> {
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<super::ShaderModule, crate::ShaderError> {
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<super::Fence> {
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<crate::FenceValue> {
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()
}
}

View File

@ -339,6 +339,7 @@ impl Queue {
pub struct Device {
shared: Arc<AdapterShared>,
features: wgt::Features,
counters: wgt::HalCounters,
}
pub struct Surface {

View File

@ -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 })

View File

@ -312,7 +312,10 @@ impl gpu_alloc::MemoryDevice<vk::DeviceMemory> 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<vk::DeviceMemory> 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<super::Fence, crate::DeviceError> {
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 {

View File

@ -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<rustc_hash::FxHashMap<RenderPassKey, vk::RenderPass>>,
framebuffers: Mutex<rustc_hash::FxHashMap<FramebufferKey, vk::Framebuffer>>,
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.

View File

@ -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"

153
wgpu-types/src/counters.rs Normal file
View File

@ -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,
}

View File

@ -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

View File

@ -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.

View File

@ -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,

View File

@ -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,

View File

@ -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 = <T::DeviceId>::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,

View File

@ -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,