Skip to content

Commit 5b3341b

Browse files
🥽 Metal allocation visualizer (#247)
* 🥽 Metal allocation visualizer * Implement Debug for Allocator * fixup clippy
1 parent 031b39d commit 5b3341b

File tree

2 files changed

+203
-0
lines changed

2 files changed

+203
-0
lines changed

src/metal/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
use std::{backtrace::Backtrace, sync::Arc};
22

3+
#[cfg(feature = "visualizer")]
4+
mod visualizer;
5+
#[cfg(feature = "visualizer")]
6+
pub use visualizer::AllocatorVisualizer;
7+
38
use log::debug;
49
use metal::{MTLDevice as _, MTLHeap as _, MTLResource as _};
510
use objc2::{rc::Retained, runtime::ProtocolObject};
@@ -163,6 +168,12 @@ pub struct Allocator {
163168
allocation_sizes: AllocationSizes,
164169
}
165170

171+
impl std::fmt::Debug for Allocator {
172+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173+
self.generate_report().fmt(f)
174+
}
175+
}
176+
166177
#[derive(Debug)]
167178
pub struct AllocatorCreateDesc {
168179
pub device: Retained<ProtocolObject<dyn metal::MTLDevice>>,

src/metal/visualizer.rs

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
#![allow(clippy::new_without_default)]
2+
3+
use super::Allocator;
4+
use crate::visualizer::{
5+
render_allocation_reports_ui, AllocationReportVisualizeSettings, ColorScheme,
6+
MemoryChunksVisualizationSettings,
7+
};
8+
9+
struct AllocatorVisualizerBlockWindow {
10+
memory_type_index: usize,
11+
block_index: usize,
12+
settings: MemoryChunksVisualizationSettings,
13+
}
14+
impl AllocatorVisualizerBlockWindow {
15+
fn new(memory_type_index: usize, block_index: usize) -> Self {
16+
Self {
17+
memory_type_index,
18+
block_index,
19+
settings: Default::default(),
20+
}
21+
}
22+
}
23+
24+
pub struct AllocatorVisualizer {
25+
selected_blocks: Vec<AllocatorVisualizerBlockWindow>,
26+
color_scheme: ColorScheme,
27+
breakdown_settings: AllocationReportVisualizeSettings,
28+
}
29+
30+
impl AllocatorVisualizer {
31+
pub fn new() -> Self {
32+
Self {
33+
selected_blocks: Vec::default(),
34+
color_scheme: ColorScheme::default(),
35+
breakdown_settings: Default::default(),
36+
}
37+
}
38+
39+
pub fn set_color_scheme(&mut self, color_scheme: ColorScheme) {
40+
self.color_scheme = color_scheme;
41+
}
42+
43+
pub fn render_memory_block_ui(&mut self, ui: &mut egui::Ui, alloc: &Allocator) {
44+
ui.collapsing(
45+
format!("Memory Types: ({} types)", alloc.memory_types.len()),
46+
|ui| {
47+
for (mem_type_idx, mem_type) in alloc.memory_types.iter().enumerate() {
48+
ui.collapsing(
49+
format!(
50+
"Type: {} ({} blocks)",
51+
mem_type_idx,
52+
mem_type.memory_blocks.len(),
53+
),
54+
|ui| {
55+
let mut total_block_size = 0;
56+
let mut total_allocated = 0;
57+
58+
for block in mem_type.memory_blocks.iter().flatten() {
59+
total_block_size += block.size;
60+
total_allocated += block.sub_allocator.allocated();
61+
}
62+
63+
let active_block_count = mem_type
64+
.memory_blocks
65+
.iter()
66+
.filter(|block| block.is_some())
67+
.count();
68+
69+
ui.label(format!("properties: {:?}", mem_type.heap_properties));
70+
ui.label(format!("memory type index: {}", mem_type.memory_type_index));
71+
ui.label(format!("total block size: {} KiB", total_block_size / 1024));
72+
ui.label(format!("total allocated: {} KiB", total_allocated / 1024));
73+
ui.label(format!("block count: {}", active_block_count));
74+
75+
for (block_idx, block) in mem_type.memory_blocks.iter().enumerate() {
76+
let Some(block) = block else { continue };
77+
78+
ui.collapsing(format!("Block: {}", block_idx), |ui| {
79+
ui.label(format!("size: {} KiB", block.size / 1024));
80+
ui.label(format!(
81+
"allocated: {} KiB",
82+
block.sub_allocator.allocated() / 1024
83+
));
84+
ui.label(format!("Heap: {:?}", &block.heap));
85+
86+
block.sub_allocator.draw_base_info(ui);
87+
88+
if block.sub_allocator.supports_visualization()
89+
&& ui.button("visualize").clicked()
90+
&& !self.selected_blocks.iter().any(|x| {
91+
x.memory_type_index == mem_type_idx
92+
&& x.block_index == block_idx
93+
})
94+
{
95+
self.selected_blocks.push(
96+
AllocatorVisualizerBlockWindow::new(
97+
mem_type_idx,
98+
block_idx,
99+
),
100+
);
101+
}
102+
});
103+
}
104+
},
105+
);
106+
}
107+
},
108+
);
109+
}
110+
111+
pub fn render_memory_block_window(
112+
&mut self,
113+
ctx: &egui::Context,
114+
allocator: &Allocator,
115+
open: &mut bool,
116+
) {
117+
egui::Window::new("Allocator Memory Blocks")
118+
.open(open)
119+
.show(ctx, |ui| self.render_breakdown_ui(ui, allocator));
120+
}
121+
122+
pub fn render_memory_block_visualization_windows(
123+
&mut self,
124+
ctx: &egui::Context,
125+
allocator: &Allocator,
126+
) {
127+
// Draw each window.
128+
let color_scheme = &self.color_scheme;
129+
130+
self.selected_blocks.retain_mut(|window| {
131+
let mut open = true;
132+
133+
egui::Window::new(format!(
134+
"Block Visualizer {}:{}",
135+
window.memory_type_index, window.block_index
136+
))
137+
.default_size([1920.0 * 0.5, 1080.0 * 0.5])
138+
.open(&mut open)
139+
.show(ctx, |ui| {
140+
let memblock = &allocator.memory_types[window.memory_type_index].memory_blocks
141+
[window.block_index]
142+
.as_ref();
143+
if let Some(memblock) = memblock {
144+
ui.label(format!(
145+
"Memory type {}, Memory block {}, Block size: {} KiB",
146+
window.memory_type_index,
147+
window.block_index,
148+
memblock.size / 1024
149+
));
150+
151+
window
152+
.settings
153+
.ui(ui, allocator.debug_settings.store_stack_traces);
154+
155+
ui.separator();
156+
157+
memblock
158+
.sub_allocator
159+
.draw_visualization(color_scheme, ui, &window.settings);
160+
} else {
161+
ui.label("Deallocated memory block");
162+
}
163+
});
164+
165+
open
166+
});
167+
}
168+
169+
pub fn render_breakdown_ui(&mut self, ui: &mut egui::Ui, allocator: &Allocator) {
170+
render_allocation_reports_ui(
171+
ui,
172+
&mut self.breakdown_settings,
173+
allocator
174+
.memory_types
175+
.iter()
176+
.flat_map(|memory_type| memory_type.memory_blocks.iter())
177+
.flatten()
178+
.flat_map(|memory_block| memory_block.sub_allocator.report_allocations()),
179+
);
180+
}
181+
182+
pub fn render_breakdown_window(
183+
&mut self,
184+
ctx: &egui::Context,
185+
allocator: &Allocator,
186+
open: &mut bool,
187+
) {
188+
egui::Window::new("Allocator Breakdown")
189+
.open(open)
190+
.show(ctx, |ui| self.render_breakdown_ui(ui, allocator));
191+
}
192+
}

0 commit comments

Comments
 (0)