#![allow(clippy::new_without_default)]
use super::Allocator;
use crate::allocator::{fmt_bytes, resolve_backtrace};
use crate::visualizer::ColorScheme;
use log::error;
const DEFAULT_BYTES_PER_UNIT: i32 = 1024;
struct AllocatorVisualizerBlockWindow {
memory_type_index: usize,
block_index: usize,
bytes_per_unit: i32,
show_backtraces: bool,
}
impl AllocatorVisualizerBlockWindow {
fn new(memory_type_index: usize, block_index: usize) -> Self {
Self {
memory_type_index,
block_index,
bytes_per_unit: DEFAULT_BYTES_PER_UNIT,
show_backtraces: false,
}
}
}
pub struct AllocatorVisualizer {
selected_blocks: Vec<AllocatorVisualizerBlockWindow>,
focus: Option<usize>,
color_scheme: ColorScheme,
allocation_breakdown_sorting: Option<(Option<imgui::TableSortDirection>, usize)>,
breakdown_filter: String,
}
impl AllocatorVisualizer {
pub fn new() -> Self {
Self {
selected_blocks: Vec::default(),
focus: None,
color_scheme: ColorScheme::default(),
allocation_breakdown_sorting: None,
breakdown_filter: String::new(),
}
}
pub fn set_color_scheme(&mut self, color_scheme: ColorScheme) {
self.color_scheme = color_scheme;
}
fn render_main_window(&mut self, ui: &imgui::Ui, opened: Option<&mut bool>, alloc: &Allocator) {
let mut window = ui.window("Allocator visualization");
if let Some(opened) = opened {
window = window.opened(opened);
}
window
.size([512.0, 512.0], imgui::Condition::FirstUseEver)
.build(|| {
use imgui::*;
ui.text(format!(
"buffer image granularity: {:?}",
alloc.buffer_image_granularity
));
let heap_count = alloc.memory_heaps.len();
if CollapsingHeader::new(format!("Memory Heaps ({} heaps)", heap_count)).build(ui) {
for (i, heap) in alloc.memory_heaps.iter().enumerate() {
ui.indent();
if CollapsingHeader::new(format!("Heap: {}", i)).build(ui) {
ui.indent();
ui.text(format!("flags: {:?}", heap.flags));
ui.text(format!(
"size: {} MiB",
heap.size as f64 / (1024 * 1024) as f64
));
ui.unindent();
}
ui.unindent();
}
}
if CollapsingHeader::new(format!(
"Memory Types: ({} types)",
alloc.memory_types.len()
))
.flags(TreeNodeFlags::DEFAULT_OPEN)
.build(ui)
{
ui.indent();
for (mem_type_i, mem_type) in alloc.memory_types.iter().enumerate() {
if CollapsingHeader::new(format!(
"Type: {} ({} blocks)##Type{}",
mem_type_i,
mem_type.memory_blocks.len(),
mem_type_i,
))
.build(ui)
{
let mut total_block_size = 0;
let mut total_allocated = 0;
for block in mem_type.memory_blocks.iter().flatten() {
total_block_size += block.size;
total_allocated += block.sub_allocator.allocated();
}
ui.text(format!("properties: {:?}", mem_type.memory_properties));
ui.text(format!("heap index: {}", mem_type.heap_index));
ui.text(format!("total block size: {} KiB", total_block_size / 1024));
ui.text(format!("total allocated: {} KiB", total_allocated / 1024));
let active_block_count = mem_type
.memory_blocks
.iter()
.filter(|block| block.is_some())
.count();
ui.text(format!("block count: {}", active_block_count));
for (block_i, block) in mem_type.memory_blocks.iter().enumerate() {
if let Some(block) = block {
if ui.tree_node(format!("Block: {}", block_i)).is_some() {
use ash::vk::Handle;
ui.indent();
ui.text(format!("size: {} KiB", block.size / 1024));
ui.text(format!(
"allocated: {} KiB",
block.sub_allocator.allocated() / 1024
));
ui.text(format!(
"vk device memory: 0x{:x}",
block.device_memory.as_raw()
));
if let Some(mapped_ptr) = block.mapped_ptr {
ui.text(format!(
"mapped pointer: {:#p}",
mapped_ptr.0.as_ptr()
));
}
if block.dedicated_allocation {
ui.text("Dedicated Allocation");
}
block.sub_allocator.draw_base_info(ui);
if block.sub_allocator.supports_visualization()
&& ui.small_button("visualize")
{
match self.selected_blocks.iter().enumerate().find(
|(_, x)| {
x.memory_type_index == mem_type_i
&& x.block_index == block_i
},
) {
Some(x) => self.focus = Some(x.0),
None => self.selected_blocks.push(
AllocatorVisualizerBlockWindow::new(
mem_type_i, block_i,
),
),
}
}
ui.unindent();
}
}
}
}
}
ui.unindent();
}
});
}
fn render_memory_block_windows(&mut self, ui: &imgui::Ui, alloc: &Allocator) {
let focus_opt = self.focus;
let mut windows_to_close = Vec::default();
let color_scheme = &self.color_scheme;
for (window_i, window) in self.selected_blocks.iter_mut().enumerate() {
let focus = focus_opt.map_or(false, |focus_i| window_i == focus_i);
let mut is_open = true;
ui.window(format!(
"Block Visualizer##memtype({})block({})",
window.memory_type_index, window.block_index
))
.size([1920.0 * 0.5, 1080.0 * 0.5], imgui::Condition::FirstUseEver)
.title_bar(true)
.scroll_bar(true)
.scrollable(true)
.focused(focus)
.opened(&mut is_open)
.build(|| {
use imgui::*;
let memblock = &alloc.memory_types[window.memory_type_index].memory_blocks
[window.block_index]
.as_ref();
if let Some(memblock) = memblock {
ui.text(format!(
"Memory type {}, Memory block {}, Block size: {} KiB",
window.memory_type_index,
window.block_index,
memblock.size / 1024
));
if alloc.debug_settings.store_stack_traces {
ui.checkbox("Show backtraces", &mut window.show_backtraces);
}
const BYTES_PER_UNIT_MIN: i32 = 1;
const BYTES_PER_UNIT_MAX: i32 = 1024 * 1024;
Drag::new("Bytes per Pixel (zoom)")
.range(BYTES_PER_UNIT_MIN, BYTES_PER_UNIT_MAX)
.speed(10.0f32)
.build(ui, &mut window.bytes_per_unit);
window.bytes_per_unit = window
.bytes_per_unit
.clamp(BYTES_PER_UNIT_MIN, BYTES_PER_UNIT_MAX);
ui.child_window(format!(
"Visualization Sub-window##memtype({})block({})",
window.memory_type_index, window.block_index
))
.scrollable(true)
.scroll_bar(true)
.build(|| {
memblock.sub_allocator.draw_visualization(
color_scheme,
ui,
window.bytes_per_unit,
window.show_backtraces,
)
});
} else {
ui.text("Deallocated memory block");
}
});
if !is_open {
windows_to_close.push(window_i);
}
}
let mut windows_removed = 0usize;
let mut i = 0usize;
if !windows_to_close.is_empty() && !self.selected_blocks.is_empty() {
loop {
if windows_to_close.iter().any(|j| i == (*j - windows_removed)) {
self.selected_blocks.remove(i);
windows_removed += 1;
} else {
i += 1;
}
if i == self.selected_blocks.len() {
break;
}
}
}
self.focus = None;
}
pub fn render(&mut self, allocator: &Allocator, ui: &imgui::Ui, opened: Option<&mut bool>) {
if opened != Some(&mut false) {
self.render_main_window(ui, opened, allocator);
self.render_memory_block_windows(ui, allocator);
}
}
pub fn render_breakdown(
&mut self,
allocator: &Allocator,
ui: &imgui::Ui,
opened: Option<&mut bool>,
) {
let mut allocation_report = vec![];
let mut total_size_in_bytes = 0;
if let Some(true) = opened {
let lowercase_needle = &self.breakdown_filter.to_lowercase();
for memory_type in &allocator.memory_types {
for block in memory_type.memory_blocks.iter().flatten() {
for report in block.sub_allocator.report_allocations() {
if self.breakdown_filter.is_empty()
|| report.name.to_lowercase().contains(lowercase_needle)
{
allocation_report.push(report);
}
}
}
}
total_size_in_bytes = allocation_report.iter().map(|report| report.size).sum();
}
let mut window = ui
.window(format!(
"Allocation Breakdown ({})###allocation_breakdown_window",
fmt_bytes(total_size_in_bytes)
))
.position([20.0f32, 80.0f32], imgui::Condition::FirstUseEver)
.size([460.0f32, 420.0f32], imgui::Condition::FirstUseEver);
if let Some(opened) = opened {
window = window.opened(opened);
}
window.build(|| {
ui.input_text("Filter", &mut self.breakdown_filter).build();
if let Some(_k) = ui.begin_table_header_with_flags(
"alloc_breakdown_table",
[
imgui::TableColumnSetup {
flags: imgui::TableColumnFlags::WIDTH_FIXED,
init_width_or_weight: 50.0,
..imgui::TableColumnSetup::new("Idx")
},
imgui::TableColumnSetup::new("Name"),
imgui::TableColumnSetup {
flags: imgui::TableColumnFlags::WIDTH_FIXED,
init_width_or_weight: 150.0,
..imgui::TableColumnSetup::new("Size")
},
],
imgui::TableFlags::SORTABLE | imgui::TableFlags::RESIZABLE,
) {
let mut allocation_report =
allocation_report.iter().enumerate().collect::<Vec<_>>();
if let Some(mut sort_data) = ui.table_sort_specs_mut() {
if sort_data.should_sort() {
let specs = sort_data.specs();
if let Some(ref spec) = specs.iter().next() {
self.allocation_breakdown_sorting =
Some((spec.sort_direction(), spec.column_idx()));
}
sort_data.set_sorted();
}
}
if let Some((Some(dir), column_idx)) = self.allocation_breakdown_sorting {
match dir {
imgui::TableSortDirection::Ascending => match column_idx {
0 => allocation_report.sort_by_key(|(idx, _)| *idx),
1 => allocation_report.sort_by_key(|(_, alloc)| &alloc.name),
2 => allocation_report.sort_by_key(|(_, alloc)| alloc.size),
_ => error!("Sorting invalid column index {}", column_idx),
},
imgui::TableSortDirection::Descending => match column_idx {
0 => allocation_report.sort_by_key(|(idx, _)| std::cmp::Reverse(*idx)),
1 => allocation_report
.sort_by_key(|(_, alloc)| std::cmp::Reverse(&alloc.name)),
2 => allocation_report
.sort_by_key(|(_, alloc)| std::cmp::Reverse(alloc.size)),
_ => error!("Sorting invalid column index {}", column_idx),
},
}
}
for (idx, alloc) in &allocation_report {
ui.table_next_column();
ui.text(idx.to_string());
ui.table_next_column();
ui.text(&alloc.name);
if ui.is_item_hovered() && alloc.backtrace.is_some() {
ui.tooltip(|| {
ui.text(resolve_backtrace(&alloc.backtrace));
});
}
ui.table_next_column();
ui.text(fmt_bytes(alloc.size));
}
}
});
}
}