Skip to content

Commit bc7622f

Browse files
nicalteoxoy
authored andcommitted
Expose GPU allocation reports in wgpu, wgpu-core and wgpu-hal
1 parent 20973d1 commit bc7622f

File tree

9 files changed

+195
-1
lines changed

9 files changed

+195
-1
lines changed

wgpu-core/src/device/global.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2458,6 +2458,17 @@ impl Global {
24582458
}
24592459
}
24602460

2461+
pub fn device_generate_allocator_report<A: HalApi>(
2462+
&self,
2463+
device_id: DeviceId,
2464+
) -> Option<wgt::AllocatorReport> {
2465+
let hub = A::hub(self);
2466+
hub.devices
2467+
.get(device_id)
2468+
.ok()
2469+
.and_then(|device| device.generate_allocator_report())
2470+
}
2471+
24612472
pub fn queue_drop<A: HalApi>(&self, queue_id: QueueId) {
24622473
profiling::scope!("Queue::drop");
24632474
api_log!("Queue::drop {queue_id:?}");

wgpu-core/src/device/resource.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3598,6 +3598,13 @@ impl<A: HalApi> Device<A> {
35983598
.map(|raw| raw.get_internal_counters())
35993599
.unwrap_or_default()
36003600
}
3601+
3602+
pub fn generate_allocator_report(&self) -> Option<wgt::AllocatorReport> {
3603+
self.raw
3604+
.as_ref()
3605+
.map(|raw| raw.generate_allocator_report())
3606+
.unwrap_or_default()
3607+
}
36013608
}
36023609

36033610
impl<A: HalApi> Device<A> {

wgpu-hal/src/dx12/device.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1801,4 +1801,41 @@ impl crate::Device for super::Device {
18011801
fn get_internal_counters(&self) -> wgt::HalCounters {
18021802
self.counters.clone()
18031803
}
1804+
1805+
#[cfg(feature = "windows_rs")]
1806+
fn generate_allocator_report(&self) -> Option<wgt::AllocatorReport> {
1807+
let mut upstream = {
1808+
self.mem_allocator
1809+
.as_ref()?
1810+
.lock()
1811+
.allocator
1812+
.generate_report()
1813+
};
1814+
1815+
let allocations = upstream
1816+
.allocations
1817+
.iter_mut()
1818+
.map(|alloc| wgt::AllocationReport {
1819+
name: std::mem::take(&mut alloc.name),
1820+
offset: alloc.offset,
1821+
size: alloc.size,
1822+
})
1823+
.collect();
1824+
1825+
let blocks = upstream
1826+
.blocks
1827+
.iter()
1828+
.map(|block| wgt::MemoryBlockReport {
1829+
size: block.size,
1830+
allocations: block.allocations.clone(),
1831+
})
1832+
.collect();
1833+
1834+
Some(wgt::AllocatorReport {
1835+
allocations,
1836+
blocks,
1837+
total_allocated_bytes: upstream.total_allocated_bytes,
1838+
total_reserved_bytes: upstream.total_reserved_bytes,
1839+
})
1840+
}
18041841
}

wgpu-hal/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -894,6 +894,10 @@ pub trait Device: WasmNotSendSync {
894894
);
895895

896896
fn get_internal_counters(&self) -> wgt::HalCounters;
897+
898+
fn generate_allocator_report(&self) -> Option<wgt::AllocatorReport> {
899+
None
900+
}
897901
}
898902

899903
pub trait Queue: WasmNotSendSync {

wgpu-types/src/counters.rs

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#[cfg(feature = "counters")]
22
use std::sync::atomic::{AtomicIsize, Ordering};
3+
use std::{fmt, ops::Range};
34

45
/// An internal counter for debugging purposes
56
///
@@ -128,7 +129,7 @@ pub struct HalCounters {
128129
/// `wgpu-core`'s internal counters.
129130
#[derive(Clone, Default)]
130131
pub struct CoreCounters {
131-
// TODO
132+
// TODO #[cfg(features=)]
132133
}
133134

134135
/// All internal counters, exposed for debugging purposes.
@@ -139,3 +140,90 @@ pub struct InternalCounters {
139140
/// `wgpu-hal` counters.
140141
pub hal: HalCounters,
141142
}
143+
144+
/// Describes an allocation in the [`AllocatorReport`].
145+
#[derive(Clone)]
146+
pub struct AllocationReport {
147+
/// The name provided to the `allocate()` function.
148+
pub name: String,
149+
/// The offset in bytes of the allocation in its memory block.
150+
pub offset: u64,
151+
/// The size in bytes of the allocation.
152+
pub size: u64,
153+
}
154+
155+
/// Describes a memory block in the [`AllocatorReport`].
156+
#[derive(Clone)]
157+
pub struct MemoryBlockReport {
158+
/// The size in bytes of this memory block.
159+
pub size: u64,
160+
/// The range of allocations in [`AllocatorReport::allocations`] that are associated
161+
/// to this memory block.
162+
pub allocations: Range<usize>,
163+
}
164+
165+
/// A report that can be generated for informational purposes using `Allocator::generate_report()`.
166+
#[derive(Clone)]
167+
pub struct AllocatorReport {
168+
/// All live allocations, sub-allocated from memory blocks.
169+
pub allocations: Vec<AllocationReport>,
170+
/// All memory blocks.
171+
pub blocks: Vec<MemoryBlockReport>,
172+
/// Sum of the memory used by all allocations, in bytes.
173+
pub total_allocated_bytes: u64,
174+
/// Sum of the memory reserved by all memory blocks including unallocated regions, in bytes.
175+
pub total_reserved_bytes: u64,
176+
}
177+
178+
impl fmt::Debug for AllocationReport {
179+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180+
let name = if !self.name.is_empty() {
181+
self.name.as_str()
182+
} else {
183+
"--"
184+
};
185+
write!(f, "{name:?}: {}", FmtBytes(self.size))
186+
}
187+
}
188+
189+
impl fmt::Debug for AllocatorReport {
190+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191+
let mut allocations = self.allocations.clone();
192+
allocations.sort_by_key(|alloc| std::cmp::Reverse(alloc.size));
193+
194+
let max_num_allocations_to_print = f.precision().unwrap_or(usize::MAX);
195+
allocations.truncate(max_num_allocations_to_print);
196+
197+
f.debug_struct("AllocatorReport")
198+
.field(
199+
"summary",
200+
&std::format_args!(
201+
"{} / {}",
202+
FmtBytes(self.total_allocated_bytes),
203+
FmtBytes(self.total_reserved_bytes)
204+
),
205+
)
206+
.field("blocks", &self.blocks.len())
207+
.field("allocations", &self.allocations.len())
208+
.field("largest", &allocations.as_slice())
209+
.finish()
210+
}
211+
}
212+
213+
struct FmtBytes(u64);
214+
215+
impl fmt::Display for FmtBytes {
216+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217+
const SUFFIX: [&str; 5] = ["B", "KB", "MB", "GB", "TB"];
218+
let mut idx = 0;
219+
let mut amount = self.0 as f64;
220+
loop {
221+
if amount < 1024.0 || idx == SUFFIX.len() - 1 {
222+
return write!(f, "{:.2} {}", amount, SUFFIX[idx]);
223+
}
224+
225+
amount /= 1024.0;
226+
idx += 1;
227+
}
228+
}
229+
}

wgpu/src/backend/webgpu.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2986,6 +2986,14 @@ impl crate::context::Context for ContextWebGpu {
29862986
Default::default()
29872987
}
29882988

2989+
fn device_generate_allocator_report(
2990+
&self,
2991+
_device: &Self::DeviceId,
2992+
_device_data: &Self::DeviceData,
2993+
) -> Option<wgt::AllocatorReport> {
2994+
None
2995+
}
2996+
29892997
fn pipeline_cache_get_data(
29902998
&self,
29912999
_: &Self::PipelineCacheId,

wgpu/src/backend/wgpu_core.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2367,6 +2367,14 @@ impl crate::Context for ContextWgpuCore {
23672367
wgc::gfx_select!(device => self.0.device_get_internal_counters(*device))
23682368
}
23692369

2370+
fn device_generate_allocator_report(
2371+
&self,
2372+
device: &Self::DeviceId,
2373+
_device_data: &Self::DeviceData,
2374+
) -> Option<wgt::AllocatorReport> {
2375+
wgc::gfx_select!(device => self.0.device_generate_allocator_report(*device))
2376+
}
2377+
23702378
fn pipeline_cache_get_data(
23712379
&self,
23722380
cache: &Self::PipelineCacheId,

wgpu/src/context.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,12 @@ pub trait Context: Debug + WasmNotSendSync + Sized {
618618
_device_data: &Self::DeviceData,
619619
) -> wgt::InternalCounters;
620620

621+
fn device_generate_allocator_report(
622+
&self,
623+
device: &Self::DeviceId,
624+
_device_data: &Self::DeviceData,
625+
) -> Option<wgt::AllocatorReport>;
626+
621627
fn pipeline_cache_get_data(
622628
&self,
623629
cache: &Self::PipelineCacheId,
@@ -1617,6 +1623,12 @@ pub(crate) trait DynContext: Debug + WasmNotSendSync {
16171623
device_data: &crate::Data,
16181624
) -> wgt::InternalCounters;
16191625

1626+
fn generate_allocator_report(
1627+
&self,
1628+
device: &ObjectId,
1629+
device_data: &crate::Data,
1630+
) -> Option<wgt::AllocatorReport>;
1631+
16201632
fn pipeline_cache_get_data(
16211633
&self,
16221634
cache: &ObjectId,
@@ -3101,6 +3113,16 @@ where
31013113
Context::device_get_internal_counters(self, &device, device_data)
31023114
}
31033115

3116+
fn generate_allocator_report(
3117+
&self,
3118+
device: &ObjectId,
3119+
device_data: &crate::Data,
3120+
) -> Option<wgt::AllocatorReport> {
3121+
let device = <T::DeviceId>::from(*device);
3122+
let device_data = downcast_ref(device_data);
3123+
Context::device_generate_allocator_report(self, &device, device_data)
3124+
}
3125+
31043126
fn pipeline_cache_get_data(
31053127
&self,
31063128
cache: &ObjectId,

wgpu/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3238,6 +3238,15 @@ impl Device {
32383238
DynContext::device_get_internal_counters(&*self.context, &self.id, self.data.as_ref())
32393239
}
32403240

3241+
/// Generate an GPU memory allocation report if the underlying backend supports it.
3242+
///
3243+
/// Backends that do not support producing these reports return `None`. A backend may
3244+
/// Support it and still return `None` if it is not using performing sub-allocation,
3245+
/// for example as a workaround for driver issues.
3246+
pub fn generate_allocator_report(&self) -> Option<wgt::AllocatorReport> {
3247+
DynContext::generate_allocator_report(&*self.context, &self.id, self.data.as_ref())
3248+
}
3249+
32413250
/// Apply a callback to this `Device`'s underlying backend device.
32423251
///
32433252
/// If this `Device` is implemented by the backend API given by `A` (Vulkan,

0 commit comments

Comments
 (0)