Vendor things
This commit is contained in:
parent
5deceec006
commit
977e3c17e5
19434 changed files with 10682014 additions and 0 deletions
132
third-party/vendor/gpu-allocator/src/allocator/dedicated_block_allocator/mod.rs
vendored
Normal file
132
third-party/vendor/gpu-allocator/src/allocator/dedicated_block_allocator/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
#![deny(unsafe_code, clippy::unwrap_used)]
|
||||
|
||||
#[cfg(feature = "visualizer")]
|
||||
pub(crate) mod visualizer;
|
||||
|
||||
use super::{resolve_backtrace, AllocationReport, AllocationType, SubAllocator, SubAllocatorBase};
|
||||
use crate::{AllocationError, Result};
|
||||
use log::{log, Level};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct DedicatedBlockAllocator {
|
||||
size: u64,
|
||||
allocated: u64,
|
||||
name: Option<String>,
|
||||
backtrace: Option<backtrace::Backtrace>,
|
||||
}
|
||||
|
||||
impl DedicatedBlockAllocator {
|
||||
pub(crate) fn new(size: u64) -> Self {
|
||||
Self {
|
||||
size,
|
||||
allocated: 0,
|
||||
name: None,
|
||||
backtrace: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAllocatorBase for DedicatedBlockAllocator {}
|
||||
impl SubAllocator for DedicatedBlockAllocator {
|
||||
fn allocate(
|
||||
&mut self,
|
||||
size: u64,
|
||||
_alignment: u64,
|
||||
_allocation_type: AllocationType,
|
||||
_granularity: u64,
|
||||
name: &str,
|
||||
backtrace: Option<backtrace::Backtrace>,
|
||||
) -> Result<(u64, std::num::NonZeroU64)> {
|
||||
if self.allocated != 0 {
|
||||
return Err(AllocationError::OutOfMemory);
|
||||
}
|
||||
|
||||
if self.size != size {
|
||||
return Err(AllocationError::Internal(
|
||||
"DedicatedBlockAllocator size must match allocation size.".into(),
|
||||
));
|
||||
}
|
||||
|
||||
self.allocated = size;
|
||||
self.name = Some(name.to_string());
|
||||
self.backtrace = backtrace;
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let dummy_id = std::num::NonZeroU64::new(1).unwrap();
|
||||
Ok((0, dummy_id))
|
||||
}
|
||||
|
||||
fn free(&mut self, chunk_id: Option<std::num::NonZeroU64>) -> Result<()> {
|
||||
if chunk_id != std::num::NonZeroU64::new(1) {
|
||||
Err(AllocationError::Internal("Chunk ID must be 1.".into()))
|
||||
} else {
|
||||
self.allocated = 0;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn rename_allocation(
|
||||
&mut self,
|
||||
chunk_id: Option<std::num::NonZeroU64>,
|
||||
name: &str,
|
||||
) -> Result<()> {
|
||||
if chunk_id != std::num::NonZeroU64::new(1) {
|
||||
Err(AllocationError::Internal("Chunk ID must be 1.".into()))
|
||||
} else {
|
||||
self.name = Some(name.into());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn report_memory_leaks(
|
||||
&self,
|
||||
log_level: Level,
|
||||
memory_type_index: usize,
|
||||
memory_block_index: usize,
|
||||
) {
|
||||
let empty = "".to_string();
|
||||
let name = self.name.as_ref().unwrap_or(&empty);
|
||||
let backtrace = resolve_backtrace(&self.backtrace);
|
||||
|
||||
log!(
|
||||
log_level,
|
||||
r#"leak detected: {{
|
||||
memory type: {}
|
||||
memory block: {}
|
||||
dedicated allocation: {{
|
||||
size: 0x{:x},
|
||||
name: {},
|
||||
backtrace: {}
|
||||
}}
|
||||
}}"#,
|
||||
memory_type_index,
|
||||
memory_block_index,
|
||||
self.size,
|
||||
name,
|
||||
backtrace
|
||||
)
|
||||
}
|
||||
|
||||
fn report_allocations(&self) -> Vec<AllocationReport> {
|
||||
vec![AllocationReport {
|
||||
name: self
|
||||
.name
|
||||
.clone()
|
||||
.unwrap_or_else(|| "<Unnamed Dedicated allocation>".to_owned()),
|
||||
size: self.size,
|
||||
backtrace: self.backtrace.clone(),
|
||||
}]
|
||||
}
|
||||
|
||||
fn size(&self) -> u64 {
|
||||
self.size
|
||||
}
|
||||
|
||||
fn allocated(&self) -> u64 {
|
||||
self.allocated
|
||||
}
|
||||
|
||||
fn supports_general_allocations(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
8
third-party/vendor/gpu-allocator/src/allocator/dedicated_block_allocator/visualizer.rs
vendored
Normal file
8
third-party/vendor/gpu-allocator/src/allocator/dedicated_block_allocator/visualizer.rs
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
use super::DedicatedBlockAllocator;
|
||||
use crate::visualizer::SubAllocatorVisualizer;
|
||||
|
||||
impl SubAllocatorVisualizer for DedicatedBlockAllocator {
|
||||
fn draw_base_info(&self, ui: &imgui::Ui) {
|
||||
ui.text("Dedicated Block");
|
||||
}
|
||||
}
|
||||
413
third-party/vendor/gpu-allocator/src/allocator/free_list_allocator/mod.rs
vendored
Normal file
413
third-party/vendor/gpu-allocator/src/allocator/free_list_allocator/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,413 @@
|
|||
#![deny(unsafe_code, clippy::unwrap_used)]
|
||||
|
||||
#[cfg(feature = "visualizer")]
|
||||
pub(crate) mod visualizer;
|
||||
|
||||
use super::{resolve_backtrace, AllocationReport, AllocationType, SubAllocator, SubAllocatorBase};
|
||||
use crate::{AllocationError, Result};
|
||||
|
||||
use log::{log, Level};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
const USE_BEST_FIT: bool = true;
|
||||
|
||||
fn align_down(val: u64, alignment: u64) -> u64 {
|
||||
val & !(alignment - 1u64)
|
||||
}
|
||||
|
||||
fn align_up(val: u64, alignment: u64) -> u64 {
|
||||
align_down(val + alignment - 1u64, alignment)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct MemoryChunk {
|
||||
pub(crate) chunk_id: std::num::NonZeroU64,
|
||||
pub(crate) size: u64,
|
||||
pub(crate) offset: u64,
|
||||
pub(crate) allocation_type: AllocationType,
|
||||
pub(crate) name: Option<String>,
|
||||
pub(crate) backtrace: Option<backtrace::Backtrace>, // Only used if STORE_STACK_TRACES is true
|
||||
next: Option<std::num::NonZeroU64>,
|
||||
prev: Option<std::num::NonZeroU64>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct FreeListAllocator {
|
||||
size: u64,
|
||||
allocated: u64,
|
||||
pub(crate) chunk_id_counter: u64,
|
||||
pub(crate) chunks: HashMap<std::num::NonZeroU64, MemoryChunk>,
|
||||
free_chunks: HashSet<std::num::NonZeroU64>,
|
||||
}
|
||||
|
||||
/// Test if two suballocations will overlap the same page.
|
||||
fn is_on_same_page(offset_a: u64, size_a: u64, offset_b: u64, page_size: u64) -> bool {
|
||||
let end_a = offset_a + size_a - 1;
|
||||
let end_page_a = align_down(end_a, page_size);
|
||||
let start_b = offset_b;
|
||||
let start_page_b = align_down(start_b, page_size);
|
||||
|
||||
end_page_a == start_page_b
|
||||
}
|
||||
|
||||
/// Test if two allocation types will be conflicting or not.
|
||||
fn has_granularity_conflict(type0: AllocationType, type1: AllocationType) -> bool {
|
||||
if type0 == AllocationType::Free || type1 == AllocationType::Free {
|
||||
return false;
|
||||
}
|
||||
|
||||
type0 != type1
|
||||
}
|
||||
|
||||
impl FreeListAllocator {
|
||||
pub(crate) fn new(size: u64) -> Self {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let initial_chunk_id = std::num::NonZeroU64::new(1).unwrap();
|
||||
|
||||
let mut chunks = HashMap::default();
|
||||
chunks.insert(
|
||||
initial_chunk_id,
|
||||
MemoryChunk {
|
||||
chunk_id: initial_chunk_id,
|
||||
size,
|
||||
offset: 0,
|
||||
allocation_type: AllocationType::Free,
|
||||
name: None,
|
||||
backtrace: None,
|
||||
prev: None,
|
||||
next: None,
|
||||
},
|
||||
);
|
||||
|
||||
let mut free_chunks = HashSet::default();
|
||||
free_chunks.insert(initial_chunk_id);
|
||||
|
||||
Self {
|
||||
size,
|
||||
allocated: 0,
|
||||
// 0 is not allowed as a chunk ID, 1 is used by the initial chunk, next chunk is going to be 2.
|
||||
// The system well take the counter as the ID, and the increment the counter.
|
||||
chunk_id_counter: 2,
|
||||
chunks,
|
||||
free_chunks,
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a new unique chunk ID
|
||||
fn get_new_chunk_id(&mut self) -> Result<std::num::NonZeroU64> {
|
||||
if self.chunk_id_counter == u64::MAX {
|
||||
// End of chunk id counter reached, no more allocations are possible.
|
||||
return Err(AllocationError::OutOfMemory);
|
||||
}
|
||||
|
||||
let id = self.chunk_id_counter;
|
||||
self.chunk_id_counter += 1;
|
||||
std::num::NonZeroU64::new(id).ok_or_else(|| {
|
||||
AllocationError::Internal("New chunk id was 0, which is not allowed.".into())
|
||||
})
|
||||
}
|
||||
/// Finds the specified `chunk_id` in the list of free chunks and removes if from the list
|
||||
fn remove_id_from_free_list(&mut self, chunk_id: std::num::NonZeroU64) {
|
||||
self.free_chunks.remove(&chunk_id);
|
||||
}
|
||||
/// Merges two adjacent chunks. Right chunk will be merged into the left chunk
|
||||
fn merge_free_chunks(
|
||||
&mut self,
|
||||
chunk_left: std::num::NonZeroU64,
|
||||
chunk_right: std::num::NonZeroU64,
|
||||
) -> Result<()> {
|
||||
// Gather data from right chunk and remove it
|
||||
let (right_size, right_next) = {
|
||||
let chunk = self.chunks.remove(&chunk_right).ok_or_else(|| {
|
||||
AllocationError::Internal("Chunk ID not present in chunk list.".into())
|
||||
})?;
|
||||
self.remove_id_from_free_list(chunk.chunk_id);
|
||||
|
||||
(chunk.size, chunk.next)
|
||||
};
|
||||
|
||||
// Merge into left chunk
|
||||
{
|
||||
let chunk = self.chunks.get_mut(&chunk_left).ok_or_else(|| {
|
||||
AllocationError::Internal("Chunk ID not present in chunk list.".into())
|
||||
})?;
|
||||
chunk.next = right_next;
|
||||
chunk.size += right_size;
|
||||
}
|
||||
|
||||
// Patch pointers
|
||||
if let Some(right_next) = right_next {
|
||||
let chunk = self.chunks.get_mut(&right_next).ok_or_else(|| {
|
||||
AllocationError::Internal("Chunk ID not present in chunk list.".into())
|
||||
})?;
|
||||
chunk.prev = Some(chunk_left);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAllocatorBase for FreeListAllocator {}
|
||||
impl SubAllocator for FreeListAllocator {
|
||||
fn allocate(
|
||||
&mut self,
|
||||
size: u64,
|
||||
alignment: u64,
|
||||
allocation_type: AllocationType,
|
||||
granularity: u64,
|
||||
name: &str,
|
||||
backtrace: Option<backtrace::Backtrace>,
|
||||
) -> Result<(u64, std::num::NonZeroU64)> {
|
||||
let free_size = self.size - self.allocated;
|
||||
if size > free_size {
|
||||
return Err(AllocationError::OutOfMemory);
|
||||
}
|
||||
|
||||
let mut best_fit_id: Option<std::num::NonZeroU64> = None;
|
||||
let mut best_offset = 0u64;
|
||||
let mut best_aligned_size = 0u64;
|
||||
let mut best_chunk_size = 0u64;
|
||||
|
||||
for current_chunk_id in self.free_chunks.iter() {
|
||||
let current_chunk = self.chunks.get(current_chunk_id).ok_or_else(|| {
|
||||
AllocationError::Internal(
|
||||
"Chunk ID in free list is not present in chunk list.".into(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if current_chunk.size < size {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut offset = align_up(current_chunk.offset, alignment);
|
||||
|
||||
if let Some(prev_idx) = current_chunk.prev {
|
||||
let previous = self.chunks.get(&prev_idx).ok_or_else(|| {
|
||||
AllocationError::Internal("Invalid previous chunk reference.".into())
|
||||
})?;
|
||||
if is_on_same_page(previous.offset, previous.size, offset, granularity)
|
||||
&& has_granularity_conflict(previous.allocation_type, allocation_type)
|
||||
{
|
||||
offset = align_up(offset, granularity);
|
||||
}
|
||||
}
|
||||
|
||||
let padding = offset - current_chunk.offset;
|
||||
let aligned_size = padding + size;
|
||||
|
||||
if aligned_size > current_chunk.size {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(next_idx) = current_chunk.next {
|
||||
let next = self.chunks.get(&next_idx).ok_or_else(|| {
|
||||
AllocationError::Internal("Invalid next chunk reference.".into())
|
||||
})?;
|
||||
if is_on_same_page(offset, size, next.offset, granularity)
|
||||
&& has_granularity_conflict(allocation_type, next.allocation_type)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if USE_BEST_FIT {
|
||||
if best_fit_id.is_none() || current_chunk.size < best_chunk_size {
|
||||
best_fit_id = Some(*current_chunk_id);
|
||||
best_aligned_size = aligned_size;
|
||||
best_offset = offset;
|
||||
|
||||
best_chunk_size = current_chunk.size;
|
||||
};
|
||||
} else {
|
||||
best_fit_id = Some(*current_chunk_id);
|
||||
best_aligned_size = aligned_size;
|
||||
best_offset = offset;
|
||||
|
||||
best_chunk_size = current_chunk.size;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let first_fit_id = best_fit_id.ok_or(AllocationError::OutOfMemory)?;
|
||||
|
||||
let chunk_id = if best_chunk_size > best_aligned_size {
|
||||
let new_chunk_id = self.get_new_chunk_id()?;
|
||||
|
||||
let new_chunk = {
|
||||
let free_chunk = self.chunks.get_mut(&first_fit_id).ok_or_else(|| {
|
||||
AllocationError::Internal("Chunk ID must be in chunk list.".into())
|
||||
})?;
|
||||
let new_chunk = MemoryChunk {
|
||||
chunk_id: new_chunk_id,
|
||||
size: best_aligned_size,
|
||||
offset: free_chunk.offset,
|
||||
allocation_type,
|
||||
name: Some(name.to_string()),
|
||||
backtrace,
|
||||
prev: free_chunk.prev,
|
||||
next: Some(first_fit_id),
|
||||
};
|
||||
|
||||
free_chunk.prev = Some(new_chunk.chunk_id);
|
||||
free_chunk.offset += best_aligned_size;
|
||||
free_chunk.size -= best_aligned_size;
|
||||
new_chunk
|
||||
};
|
||||
|
||||
if let Some(prev_id) = new_chunk.prev {
|
||||
let prev_chunk = self.chunks.get_mut(&prev_id).ok_or_else(|| {
|
||||
AllocationError::Internal("Invalid previous chunk reference.".into())
|
||||
})?;
|
||||
prev_chunk.next = Some(new_chunk.chunk_id);
|
||||
}
|
||||
|
||||
self.chunks.insert(new_chunk_id, new_chunk);
|
||||
|
||||
new_chunk_id
|
||||
} else {
|
||||
let chunk = self
|
||||
.chunks
|
||||
.get_mut(&first_fit_id)
|
||||
.ok_or_else(|| AllocationError::Internal("Invalid chunk reference.".into()))?;
|
||||
|
||||
chunk.allocation_type = allocation_type;
|
||||
chunk.name = Some(name.to_string());
|
||||
chunk.backtrace = backtrace;
|
||||
|
||||
self.remove_id_from_free_list(first_fit_id);
|
||||
|
||||
first_fit_id
|
||||
};
|
||||
|
||||
self.allocated += best_aligned_size;
|
||||
|
||||
Ok((best_offset, chunk_id))
|
||||
}
|
||||
|
||||
fn free(&mut self, chunk_id: Option<std::num::NonZeroU64>) -> Result<()> {
|
||||
let chunk_id = chunk_id
|
||||
.ok_or_else(|| AllocationError::Internal("Chunk ID must be a valid value.".into()))?;
|
||||
|
||||
let (next_id, prev_id) = {
|
||||
let chunk = self.chunks.get_mut(&chunk_id).ok_or_else(|| {
|
||||
AllocationError::Internal(
|
||||
"Attempting to free chunk that is not in chunk list.".into(),
|
||||
)
|
||||
})?;
|
||||
chunk.allocation_type = AllocationType::Free;
|
||||
chunk.name = None;
|
||||
chunk.backtrace = None;
|
||||
|
||||
self.allocated -= chunk.size;
|
||||
|
||||
self.free_chunks.insert(chunk.chunk_id);
|
||||
|
||||
(chunk.next, chunk.prev)
|
||||
};
|
||||
|
||||
if let Some(next_id) = next_id {
|
||||
if self.chunks[&next_id].allocation_type == AllocationType::Free {
|
||||
self.merge_free_chunks(chunk_id, next_id)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(prev_id) = prev_id {
|
||||
if self.chunks[&prev_id].allocation_type == AllocationType::Free {
|
||||
self.merge_free_chunks(prev_id, chunk_id)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rename_allocation(
|
||||
&mut self,
|
||||
chunk_id: Option<std::num::NonZeroU64>,
|
||||
name: &str,
|
||||
) -> Result<()> {
|
||||
let chunk_id = chunk_id
|
||||
.ok_or_else(|| AllocationError::Internal("Chunk ID must be a valid value.".into()))?;
|
||||
|
||||
let chunk = self.chunks.get_mut(&chunk_id).ok_or_else(|| {
|
||||
AllocationError::Internal(
|
||||
"Attempting to rename chunk that is not in chunk list.".into(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if chunk.allocation_type == AllocationType::Free {
|
||||
return Err(AllocationError::Internal(
|
||||
"Attempting to rename a freed allocation.".into(),
|
||||
));
|
||||
}
|
||||
|
||||
chunk.name = Some(name.into());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn report_memory_leaks(
|
||||
&self,
|
||||
log_level: Level,
|
||||
memory_type_index: usize,
|
||||
memory_block_index: usize,
|
||||
) {
|
||||
for (chunk_id, chunk) in self.chunks.iter() {
|
||||
if chunk.allocation_type == AllocationType::Free {
|
||||
continue;
|
||||
}
|
||||
let empty = "".to_string();
|
||||
let name = chunk.name.as_ref().unwrap_or(&empty);
|
||||
let backtrace = resolve_backtrace(&chunk.backtrace);
|
||||
|
||||
log!(
|
||||
log_level,
|
||||
r#"leak detected: {{
|
||||
memory type: {}
|
||||
memory block: {}
|
||||
chunk: {{
|
||||
chunk_id: {},
|
||||
size: 0x{:x},
|
||||
offset: 0x{:x},
|
||||
allocation_type: {:?},
|
||||
name: {},
|
||||
backtrace: {}
|
||||
}}
|
||||
}}"#,
|
||||
memory_type_index,
|
||||
memory_block_index,
|
||||
chunk_id,
|
||||
chunk.size,
|
||||
chunk.offset,
|
||||
chunk.allocation_type,
|
||||
name,
|
||||
backtrace
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn report_allocations(&self) -> Vec<AllocationReport> {
|
||||
self.chunks
|
||||
.iter()
|
||||
.filter(|(_key, chunk)| chunk.allocation_type != AllocationType::Free)
|
||||
.map(|(_key, chunk)| AllocationReport {
|
||||
name: chunk
|
||||
.name
|
||||
.clone()
|
||||
.unwrap_or_else(|| "<Unnamed FreeList allocation>".to_owned()),
|
||||
size: chunk.size,
|
||||
backtrace: chunk.backtrace.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn size(&self) -> u64 {
|
||||
self.size
|
||||
}
|
||||
|
||||
fn allocated(&self) -> u64 {
|
||||
self.allocated
|
||||
}
|
||||
|
||||
fn supports_general_allocations(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
126
third-party/vendor/gpu-allocator/src/allocator/free_list_allocator/visualizer.rs
vendored
Normal file
126
third-party/vendor/gpu-allocator/src/allocator/free_list_allocator/visualizer.rs
vendored
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
use super::{resolve_backtrace, AllocationType, FreeListAllocator};
|
||||
use crate::visualizer::{ColorScheme, SubAllocatorVisualizer};
|
||||
|
||||
impl SubAllocatorVisualizer for FreeListAllocator {
|
||||
fn supports_visualization(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn draw_base_info(&self, ui: &imgui::Ui) {
|
||||
ui.text("free list sub-allocator");
|
||||
ui.text(format!("chunk count: {}", self.chunks.len()));
|
||||
ui.text(format!("chunk id counter: {}", self.chunk_id_counter));
|
||||
}
|
||||
|
||||
fn draw_visualization(
|
||||
&self,
|
||||
color_scheme: &ColorScheme,
|
||||
ui: &imgui::Ui,
|
||||
bytes_per_unit: i32,
|
||||
show_backtraces: bool,
|
||||
) {
|
||||
let draw_list = ui.get_window_draw_list();
|
||||
let window_size = ui.window_size();
|
||||
let base_pos = ui.cursor_screen_pos();
|
||||
const LINE_HEIGHT: f32 = 10.0f32;
|
||||
const LINE_SPACING: f32 = 1.0f32;
|
||||
// Variables for keeping track of our own cursor.
|
||||
let mut line_x = 0.0f32;
|
||||
let mut line_y = 0.0f32;
|
||||
let line_width = window_size[0];
|
||||
struct LineMarker {
|
||||
x: f32,
|
||||
y: f32,
|
||||
}
|
||||
let mut line_markers = Vec::<LineMarker>::default();
|
||||
|
||||
let mut sorted_chunks = self.chunks.values().collect::<Vec<_>>();
|
||||
sorted_chunks.sort_by(|a, b| a.offset.cmp(&b.offset));
|
||||
|
||||
// Draw each chunk in the memory block.
|
||||
for chunk in sorted_chunks.iter() {
|
||||
// Select a color based on the memory type.
|
||||
let color = match chunk.allocation_type {
|
||||
AllocationType::Free => color_scheme.free_color,
|
||||
AllocationType::Linear => color_scheme.linear_color,
|
||||
AllocationType::NonLinear => color_scheme.non_linear_color,
|
||||
};
|
||||
// Draw one or multiple bars based on the size of the chunk.
|
||||
let mut bytes_to_draw = chunk.size as f32;
|
||||
loop {
|
||||
// Calculate how large the block should be. We take in account the size of the chunk,
|
||||
// and the amount of space that is left on the line.
|
||||
let units_to_draw = bytes_to_draw / bytes_per_unit as f32;
|
||||
let units_left_on_line = line_width - line_x;
|
||||
let units_to_draw = units_to_draw.min(units_left_on_line);
|
||||
// Determine bounds of chunk line
|
||||
let top_left = [base_pos[0] + line_x, base_pos[1] + line_y];
|
||||
let bottom_right = [
|
||||
base_pos[0] + line_x + units_to_draw,
|
||||
base_pos[1] + line_y + LINE_HEIGHT,
|
||||
];
|
||||
if ui.is_rect_visible(top_left, bottom_right) {
|
||||
// Draw chunk line.
|
||||
draw_list
|
||||
.add_rect(top_left, bottom_right, color)
|
||||
.filled(true)
|
||||
.build();
|
||||
|
||||
// Show chunk information in a tool tip when hovering over the chunk.
|
||||
if ui.is_mouse_hovering_rect(top_left, bottom_right) {
|
||||
ui.tooltip(|| {
|
||||
ui.text(format!("chunk_id: {}", chunk.chunk_id));
|
||||
ui.text(format!("size: 0x{:x}", chunk.size));
|
||||
ui.text(format!("offset: 0x{:x}", chunk.offset));
|
||||
ui.text(format!("allocation_type: {:?}", chunk.allocation_type));
|
||||
if let Some(name) = &chunk.name {
|
||||
ui.text(format!("name: {:?}", name));
|
||||
}
|
||||
if show_backtraces && chunk.backtrace.is_some() {
|
||||
ui.text(format!(
|
||||
"backtrace: {:}",
|
||||
resolve_backtrace(&chunk.backtrace)
|
||||
));
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
// Advance line counter.
|
||||
line_x += units_to_draw;
|
||||
// Go to next line if it reached the end.
|
||||
if line_x >= line_width {
|
||||
line_x = 0.0f32;
|
||||
line_y += LINE_HEIGHT + LINE_SPACING;
|
||||
}
|
||||
// Calculate how many bytes have been drawn, and subtract that from the number of bytes left to draw
|
||||
let bytes_drawn = units_to_draw * bytes_per_unit as f32;
|
||||
bytes_to_draw -= bytes_drawn;
|
||||
// Exit when there are no more bytes to draw.
|
||||
if bytes_to_draw < 1.0f32 {
|
||||
// Add a line marker to the end of the chunk.
|
||||
line_markers.push(LineMarker {
|
||||
x: bottom_right[0],
|
||||
y: top_left[1],
|
||||
});
|
||||
// Exit the loop.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Draw the line markers after drawing all the chunks, so that chunks don't overlap the line markers
|
||||
for line_marker in line_markers.iter() {
|
||||
let top_left = [line_marker.x, line_marker.y];
|
||||
let bottom_right = [line_marker.x, line_marker.y + LINE_HEIGHT];
|
||||
|
||||
if ui.is_rect_visible(top_left, bottom_right) {
|
||||
// Draw a line to mark the end of the chunk.
|
||||
draw_list
|
||||
.add_line(top_left, bottom_right, 0xffff_ffff)
|
||||
.thickness(1.0f32)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
// Let ImGui know how much we drew using the draw list.
|
||||
ui.set_cursor_pos([line_x, line_y + LINE_HEIGHT]);
|
||||
}
|
||||
}
|
||||
106
third-party/vendor/gpu-allocator/src/allocator/mod.rs
vendored
Normal file
106
third-party/vendor/gpu-allocator/src/allocator/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
use crate::result::*;
|
||||
|
||||
pub(crate) mod dedicated_block_allocator;
|
||||
pub(crate) use dedicated_block_allocator::DedicatedBlockAllocator;
|
||||
|
||||
pub(crate) mod free_list_allocator;
|
||||
pub(crate) use free_list_allocator::FreeListAllocator;
|
||||
|
||||
use log::*;
|
||||
|
||||
#[derive(PartialEq, Copy, Clone, Debug)]
|
||||
#[repr(u8)]
|
||||
pub(crate) enum AllocationType {
|
||||
Free,
|
||||
Linear,
|
||||
NonLinear,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct AllocationReport {
|
||||
pub(crate) name: String,
|
||||
pub(crate) size: u64,
|
||||
pub(crate) backtrace: Option<backtrace::Backtrace>,
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_backtrace(backtrace: &Option<backtrace::Backtrace>) -> String {
|
||||
backtrace.as_ref().map_or_else(
|
||||
|| "".to_owned(),
|
||||
|bt| {
|
||||
let mut bt = bt.clone();
|
||||
bt.resolve();
|
||||
format!("{:?}", bt)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "visualizer")]
|
||||
pub(crate) trait SubAllocatorBase: crate::visualizer::SubAllocatorVisualizer {}
|
||||
#[cfg(not(feature = "visualizer"))]
|
||||
pub(crate) trait SubAllocatorBase {}
|
||||
|
||||
pub(crate) trait SubAllocator: SubAllocatorBase + std::fmt::Debug + Sync + Send {
|
||||
fn allocate(
|
||||
&mut self,
|
||||
size: u64,
|
||||
alignment: u64,
|
||||
allocation_type: AllocationType,
|
||||
granularity: u64,
|
||||
name: &str,
|
||||
backtrace: Option<backtrace::Backtrace>,
|
||||
) -> Result<(u64, std::num::NonZeroU64)>;
|
||||
|
||||
fn free(&mut self, chunk_id: Option<std::num::NonZeroU64>) -> Result<()>;
|
||||
|
||||
fn rename_allocation(
|
||||
&mut self,
|
||||
chunk_id: Option<std::num::NonZeroU64>,
|
||||
name: &str,
|
||||
) -> Result<()>;
|
||||
|
||||
fn report_memory_leaks(
|
||||
&self,
|
||||
log_level: Level,
|
||||
memory_type_index: usize,
|
||||
memory_block_index: usize,
|
||||
);
|
||||
|
||||
fn report_allocations(&self) -> Vec<AllocationReport>;
|
||||
|
||||
#[must_use]
|
||||
fn supports_general_allocations(&self) -> bool;
|
||||
#[must_use]
|
||||
fn size(&self) -> u64;
|
||||
#[must_use]
|
||||
fn allocated(&self) -> u64;
|
||||
|
||||
/// Helper function: reports how much memory is available in this suballocator
|
||||
#[must_use]
|
||||
fn available_memory(&self) -> u64 {
|
||||
self.size() - self.allocated()
|
||||
}
|
||||
|
||||
/// Helper function: reports if the suballocator is empty (meaning, having no allocations).
|
||||
#[must_use]
|
||||
fn is_empty(&self) -> bool {
|
||||
self.allocated() == 0
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const VISUALIZER_TABLE_MAX_ENTRY_NAME_LEN: usize = 40;
|
||||
|
||||
pub(crate) fn fmt_bytes(mut amount: u64) -> String {
|
||||
const SUFFIX: [&str; 5] = ["B", "KB", "MB", "GB", "TB"];
|
||||
|
||||
let mut idx = 0;
|
||||
let mut print_amount = amount as f64;
|
||||
loop {
|
||||
if amount < 1024 {
|
||||
return format!("{:.2} {}", print_amount, SUFFIX[idx]);
|
||||
}
|
||||
|
||||
print_amount = amount as f64 / 1024.0;
|
||||
amount /= 1024;
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
970
third-party/vendor/gpu-allocator/src/d3d12/mod.rs
vendored
Normal file
970
third-party/vendor/gpu-allocator/src/d3d12/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,970 @@
|
|||
#![deny(clippy::unimplemented, clippy::unwrap_used, clippy::ok_expect)]
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use log::{debug, warn, Level};
|
||||
|
||||
use windows::Win32::{Foundation::E_OUTOFMEMORY, Graphics::Direct3D12::*};
|
||||
|
||||
#[cfg(feature = "public-winapi")]
|
||||
mod public_winapi {
|
||||
use super::*;
|
||||
pub use winapi::um::d3d12 as winapi_d3d12;
|
||||
|
||||
/// Trait similar to [`AsRef`]/[`AsMut`],
|
||||
pub trait ToWinapi<T> {
|
||||
fn as_winapi(&self) -> *const T;
|
||||
fn as_winapi_mut(&mut self) -> *mut T;
|
||||
}
|
||||
|
||||
/// [`windows`] types hold their pointer internally and provide drop semantics. As such this trait
|
||||
/// is usually implemented on the _pointer type_ (`*const`, `*mut`) of the [`winapi`] object so that
|
||||
/// a **borrow of** that pointer becomes a borrow of the [`windows`] type.
|
||||
pub trait ToWindows<T> {
|
||||
fn as_windows(&self) -> &T;
|
||||
}
|
||||
|
||||
impl ToWinapi<winapi_d3d12::ID3D12Resource> for ID3D12Resource {
|
||||
fn as_winapi(&self) -> *const winapi_d3d12::ID3D12Resource {
|
||||
unsafe { std::mem::transmute_copy(self) }
|
||||
}
|
||||
|
||||
fn as_winapi_mut(&mut self) -> *mut winapi_d3d12::ID3D12Resource {
|
||||
unsafe { std::mem::transmute_copy(self) }
|
||||
}
|
||||
}
|
||||
|
||||
impl ToWinapi<winapi_d3d12::ID3D12Device> for ID3D12Device {
|
||||
fn as_winapi(&self) -> *const winapi_d3d12::ID3D12Device {
|
||||
unsafe { std::mem::transmute_copy(self) }
|
||||
}
|
||||
|
||||
fn as_winapi_mut(&mut self) -> *mut winapi_d3d12::ID3D12Device {
|
||||
unsafe { std::mem::transmute_copy(self) }
|
||||
}
|
||||
}
|
||||
|
||||
impl ToWindows<ID3D12Device> for *const winapi_d3d12::ID3D12Device {
|
||||
fn as_windows(&self) -> &ID3D12Device {
|
||||
unsafe { std::mem::transmute(self) }
|
||||
}
|
||||
}
|
||||
|
||||
impl ToWindows<ID3D12Device> for *mut winapi_d3d12::ID3D12Device {
|
||||
fn as_windows(&self) -> &ID3D12Device {
|
||||
unsafe { std::mem::transmute(self) }
|
||||
}
|
||||
}
|
||||
|
||||
impl ToWindows<ID3D12Device> for &mut winapi_d3d12::ID3D12Device {
|
||||
fn as_windows(&self) -> &ID3D12Device {
|
||||
unsafe { std::mem::transmute(self) }
|
||||
}
|
||||
}
|
||||
|
||||
impl ToWinapi<winapi_d3d12::ID3D12Heap> for ID3D12Heap {
|
||||
fn as_winapi(&self) -> *const winapi_d3d12::ID3D12Heap {
|
||||
unsafe { std::mem::transmute_copy(self) }
|
||||
}
|
||||
|
||||
fn as_winapi_mut(&mut self) -> *mut winapi_d3d12::ID3D12Heap {
|
||||
unsafe { std::mem::transmute_copy(self) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "public-winapi")]
|
||||
pub use public_winapi::*;
|
||||
|
||||
#[cfg(feature = "visualizer")]
|
||||
mod visualizer;
|
||||
#[cfg(feature = "visualizer")]
|
||||
pub use visualizer::AllocatorVisualizer;
|
||||
|
||||
use super::allocator;
|
||||
use super::allocator::AllocationType;
|
||||
|
||||
use crate::{
|
||||
allocator::fmt_bytes, AllocationError, AllocatorDebugSettings, MemoryLocation, Result,
|
||||
};
|
||||
|
||||
/// [`ResourceCategory`] is used for supporting [`D3D12_RESOURCE_HEAP_TIER_1`].
|
||||
/// [`ResourceCategory`] will be ignored if device supports [`D3D12_RESOURCE_HEAP_TIER_2`].
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum ResourceCategory {
|
||||
Buffer,
|
||||
RtvDsvTexture,
|
||||
OtherTexture,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ResourceCreateDesc<'a> {
|
||||
pub name: &'a str,
|
||||
pub memory_location: MemoryLocation,
|
||||
pub resource_category: ResourceCategory,
|
||||
pub resource_desc: &'a D3D12_RESOURCE_DESC,
|
||||
pub clear_value: Option<&'a D3D12_CLEAR_VALUE>,
|
||||
pub initial_state: D3D12_RESOURCE_STATES,
|
||||
pub resource_type: &'a ResourceType<'a>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum HeapCategory {
|
||||
All,
|
||||
Buffer,
|
||||
RtvDsvTexture,
|
||||
OtherTexture,
|
||||
}
|
||||
|
||||
impl From<ResourceCategory> for HeapCategory {
|
||||
fn from(resource_category: ResourceCategory) -> Self {
|
||||
match resource_category {
|
||||
ResourceCategory::Buffer => Self::Buffer,
|
||||
ResourceCategory::RtvDsvTexture => Self::RtvDsvTexture,
|
||||
ResourceCategory::OtherTexture => Self::OtherTexture,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&D3D12_RESOURCE_DESC> for ResourceCategory {
|
||||
fn from(desc: &D3D12_RESOURCE_DESC) -> Self {
|
||||
if desc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER {
|
||||
Self::Buffer
|
||||
} else if (desc.Flags
|
||||
& (D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL))
|
||||
!= D3D12_RESOURCE_FLAG_NONE
|
||||
{
|
||||
Self::RtvDsvTexture
|
||||
} else {
|
||||
Self::OtherTexture
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "public-winapi")]
|
||||
impl From<&winapi_d3d12::D3D12_RESOURCE_DESC> for ResourceCategory {
|
||||
fn from(desc: &winapi_d3d12::D3D12_RESOURCE_DESC) -> Self {
|
||||
if desc.Dimension == winapi_d3d12::D3D12_RESOURCE_DIMENSION_BUFFER {
|
||||
Self::Buffer
|
||||
} else if (desc.Flags
|
||||
& (winapi_d3d12::D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET
|
||||
| winapi_d3d12::D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL))
|
||||
!= 0
|
||||
{
|
||||
Self::RtvDsvTexture
|
||||
} else {
|
||||
Self::OtherTexture
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AllocationCreateDesc<'a> {
|
||||
/// Name of the allocation, for tracking and debugging purposes
|
||||
pub name: &'a str,
|
||||
/// Location where the memory allocation should be stored
|
||||
pub location: MemoryLocation,
|
||||
|
||||
/// Size of allocation, should be queried using [`ID3D12Device::GetResourceAllocationInfo()`]
|
||||
pub size: u64,
|
||||
/// Alignment of allocation, should be queried using [`ID3D12Device::GetResourceAllocationInfo()`]
|
||||
pub alignment: u64,
|
||||
/// Resource category based on resource dimension and flags. Can be created from a [`D3D12_RESOURCE_DESC`]
|
||||
/// using the helper into function. The resource category is ignored when Resource Heap Tier 2 or higher
|
||||
/// is supported.
|
||||
pub resource_category: ResourceCategory,
|
||||
}
|
||||
|
||||
impl<'a> AllocationCreateDesc<'a> {
|
||||
/// Helper conversion function utilizing [`winapi`] types.
|
||||
///
|
||||
/// This function is also available for [`windows::Win32::Graphics::Direct3D12`]
|
||||
/// types as [`from_d3d12_resource_desc()`][Self::from_d3d12_resource_desc()].
|
||||
#[cfg(feature = "public-winapi")]
|
||||
pub fn from_winapi_d3d12_resource_desc(
|
||||
device: *const winapi_d3d12::ID3D12Device,
|
||||
desc: &winapi_d3d12::D3D12_RESOURCE_DESC,
|
||||
name: &'a str,
|
||||
location: MemoryLocation,
|
||||
) -> AllocationCreateDesc<'a> {
|
||||
let device = device.as_windows();
|
||||
// Raw structs are binary-compatible
|
||||
let desc = unsafe { std::mem::transmute(desc) };
|
||||
let allocation_info =
|
||||
unsafe { device.GetResourceAllocationInfo(0, std::slice::from_ref(desc)) };
|
||||
let resource_category: ResourceCategory = desc.into();
|
||||
|
||||
AllocationCreateDesc {
|
||||
name,
|
||||
location,
|
||||
size: allocation_info.SizeInBytes,
|
||||
alignment: allocation_info.Alignment,
|
||||
resource_category,
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper conversion function utilizing [`windows::Win32::Graphics::Direct3D12`] types.
|
||||
///
|
||||
/// This function is also available for `winapi` types as `from_winapi_d3d12_resource_desc()`
|
||||
/// when the `public-winapi` feature is enabled.
|
||||
pub fn from_d3d12_resource_desc(
|
||||
device: &ID3D12Device,
|
||||
desc: &D3D12_RESOURCE_DESC,
|
||||
name: &'a str,
|
||||
location: MemoryLocation,
|
||||
) -> AllocationCreateDesc<'a> {
|
||||
let allocation_info =
|
||||
unsafe { device.GetResourceAllocationInfo(0, std::slice::from_ref(desc)) };
|
||||
let resource_category: ResourceCategory = desc.into();
|
||||
|
||||
AllocationCreateDesc {
|
||||
name,
|
||||
location,
|
||||
size: allocation_info.SizeInBytes,
|
||||
alignment: allocation_info.Alignment,
|
||||
resource_category,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AllocatorCreateDesc {
|
||||
pub device: ID3D12Device,
|
||||
pub debug_settings: AllocatorDebugSettings,
|
||||
}
|
||||
|
||||
pub enum ResourceType<'a> {
|
||||
/// Allocation equivalent to Dx12's CommittedResource.
|
||||
Committed {
|
||||
heap_properties: &'a D3D12_HEAP_PROPERTIES,
|
||||
heap_flags: D3D12_HEAP_FLAGS,
|
||||
},
|
||||
/// Allocation equivalent to Dx12's PlacedResource.
|
||||
Placed,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Resource {
|
||||
name: String,
|
||||
pub allocation: Option<Allocation>,
|
||||
resource: Option<ID3D12Resource>,
|
||||
pub memory_location: MemoryLocation,
|
||||
memory_type_index: Option<usize>,
|
||||
pub size: u64,
|
||||
}
|
||||
|
||||
impl Resource {
|
||||
pub fn resource(&self) -> &ID3D12Resource {
|
||||
self.resource.as_ref().expect("Resource was already freed.")
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Resource {
|
||||
fn drop(&mut self) {
|
||||
if self.resource.is_some() {
|
||||
warn!("Dropping resource `{}` that was not freed. Call `Allocator::free_resource(resource)` instead.", self.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CommittedAllocationStatistics {
|
||||
pub num_allocations: usize,
|
||||
pub total_size: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Allocation {
|
||||
chunk_id: Option<std::num::NonZeroU64>,
|
||||
offset: u64,
|
||||
size: u64,
|
||||
memory_block_index: usize,
|
||||
memory_type_index: usize,
|
||||
heap: ID3D12Heap,
|
||||
|
||||
name: Option<Box<str>>,
|
||||
}
|
||||
|
||||
impl Allocation {
|
||||
pub fn chunk_id(&self) -> Option<std::num::NonZeroU64> {
|
||||
self.chunk_id
|
||||
}
|
||||
|
||||
/// Returns the [`ID3D12Heap`] object that is backing this allocation.
|
||||
/// This heap object can be shared with multiple other allocations and shouldn't be freed (or allocated from)
|
||||
/// without this library, because that will lead to undefined behavior.
|
||||
///
|
||||
/// # Safety
|
||||
/// The result of this function be safely passed into [`ID3D12Device::CreatePlacedResource()`].
|
||||
/// It is exposed for this reason. Keep in mind to also pass [`Self::offset()`] along to it.
|
||||
pub unsafe fn heap(&self) -> &ID3D12Heap {
|
||||
&self.heap
|
||||
}
|
||||
|
||||
/// Returns the offset of the allocation on the [`ID3D12Heap`].
|
||||
/// When creating a placed resources, this offset needs to be supplied as well.
|
||||
pub fn offset(&self) -> u64 {
|
||||
self.offset
|
||||
}
|
||||
|
||||
/// Returns the size of the allocation
|
||||
pub fn size(&self) -> u64 {
|
||||
self.size
|
||||
}
|
||||
|
||||
pub fn is_null(&self) -> bool {
|
||||
self.chunk_id.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MemoryBlock {
|
||||
heap: ID3D12Heap,
|
||||
size: u64,
|
||||
sub_allocator: Box<dyn allocator::SubAllocator>,
|
||||
}
|
||||
impl MemoryBlock {
|
||||
fn new(
|
||||
device: &ID3D12Device,
|
||||
size: u64,
|
||||
heap_properties: &D3D12_HEAP_PROPERTIES,
|
||||
heap_category: HeapCategory,
|
||||
dedicated: bool,
|
||||
) -> Result<Self> {
|
||||
let heap = {
|
||||
let mut desc = D3D12_HEAP_DESC {
|
||||
SizeInBytes: size,
|
||||
Properties: *heap_properties,
|
||||
Alignment: D3D12_DEFAULT_MSAA_RESOURCE_PLACEMENT_ALIGNMENT as u64,
|
||||
..Default::default()
|
||||
};
|
||||
desc.Flags = match heap_category {
|
||||
HeapCategory::All => D3D12_HEAP_FLAG_NONE,
|
||||
HeapCategory::Buffer => D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS,
|
||||
HeapCategory::RtvDsvTexture => D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES,
|
||||
HeapCategory::OtherTexture => D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES,
|
||||
};
|
||||
|
||||
let mut heap = None;
|
||||
let hr = unsafe { device.CreateHeap(&desc, &mut heap) };
|
||||
match hr {
|
||||
Err(e) if e.code() == E_OUTOFMEMORY => Err(AllocationError::OutOfMemory),
|
||||
Err(e) => Err(AllocationError::Internal(format!(
|
||||
"ID3D12Device::CreateHeap failed: {}",
|
||||
e
|
||||
))),
|
||||
Ok(()) => heap.ok_or_else(|| {
|
||||
AllocationError::Internal(
|
||||
"ID3D12Heap pointer is null, but should not be.".into(),
|
||||
)
|
||||
}),
|
||||
}?
|
||||
};
|
||||
|
||||
let sub_allocator: Box<dyn allocator::SubAllocator> = if dedicated {
|
||||
Box::new(allocator::DedicatedBlockAllocator::new(size))
|
||||
} else {
|
||||
Box::new(allocator::FreeListAllocator::new(size))
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
heap,
|
||||
size,
|
||||
sub_allocator,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MemoryType {
|
||||
memory_blocks: Vec<Option<MemoryBlock>>,
|
||||
committed_allocations: CommittedAllocationStatistics,
|
||||
memory_location: MemoryLocation,
|
||||
heap_category: HeapCategory,
|
||||
heap_properties: D3D12_HEAP_PROPERTIES,
|
||||
memory_type_index: usize,
|
||||
active_general_blocks: usize,
|
||||
}
|
||||
|
||||
const DEFAULT_DEVICE_MEMBLOCK_SIZE: u64 = 256 * 1024 * 1024;
|
||||
const DEFAULT_HOST_MEMBLOCK_SIZE: u64 = 64 * 1024 * 1024;
|
||||
impl MemoryType {
|
||||
fn allocate(
|
||||
&mut self,
|
||||
device: &ID3D12Device,
|
||||
desc: &AllocationCreateDesc<'_>,
|
||||
backtrace: Option<backtrace::Backtrace>,
|
||||
) -> Result<Allocation> {
|
||||
let allocation_type = AllocationType::Linear;
|
||||
|
||||
let memblock_size = if self.heap_properties.Type == D3D12_HEAP_TYPE_DEFAULT {
|
||||
DEFAULT_DEVICE_MEMBLOCK_SIZE
|
||||
} else {
|
||||
DEFAULT_HOST_MEMBLOCK_SIZE
|
||||
};
|
||||
|
||||
let size = desc.size;
|
||||
let alignment = desc.alignment;
|
||||
|
||||
// Create a dedicated block for large memory allocations
|
||||
if size > memblock_size {
|
||||
let mem_block = MemoryBlock::new(
|
||||
device,
|
||||
size,
|
||||
&self.heap_properties,
|
||||
self.heap_category,
|
||||
true,
|
||||
)?;
|
||||
|
||||
let block_index = self.memory_blocks.iter().position(|block| block.is_none());
|
||||
let block_index = match block_index {
|
||||
Some(i) => {
|
||||
self.memory_blocks[i].replace(mem_block);
|
||||
i
|
||||
}
|
||||
None => {
|
||||
self.memory_blocks.push(Some(mem_block));
|
||||
self.memory_blocks.len() - 1
|
||||
}
|
||||
};
|
||||
|
||||
let mem_block = self.memory_blocks[block_index]
|
||||
.as_mut()
|
||||
.ok_or_else(|| AllocationError::Internal("Memory block must be Some".into()))?;
|
||||
|
||||
let (offset, chunk_id) = mem_block.sub_allocator.allocate(
|
||||
size,
|
||||
alignment,
|
||||
allocation_type,
|
||||
1,
|
||||
desc.name,
|
||||
backtrace,
|
||||
)?;
|
||||
|
||||
return Ok(Allocation {
|
||||
chunk_id: Some(chunk_id),
|
||||
size,
|
||||
offset,
|
||||
memory_block_index: block_index,
|
||||
memory_type_index: self.memory_type_index,
|
||||
heap: mem_block.heap.clone(),
|
||||
name: Some(desc.name.into()),
|
||||
});
|
||||
}
|
||||
|
||||
let mut empty_block_index = None;
|
||||
for (mem_block_i, mem_block) in self.memory_blocks.iter_mut().enumerate().rev() {
|
||||
if let Some(mem_block) = mem_block {
|
||||
let allocation = mem_block.sub_allocator.allocate(
|
||||
size,
|
||||
alignment,
|
||||
allocation_type,
|
||||
1,
|
||||
desc.name,
|
||||
backtrace.clone(),
|
||||
);
|
||||
|
||||
match allocation {
|
||||
Ok((offset, chunk_id)) => {
|
||||
return Ok(Allocation {
|
||||
chunk_id: Some(chunk_id),
|
||||
offset,
|
||||
size,
|
||||
memory_block_index: mem_block_i,
|
||||
memory_type_index: self.memory_type_index,
|
||||
heap: mem_block.heap.clone(),
|
||||
name: Some(desc.name.into()),
|
||||
});
|
||||
}
|
||||
Err(AllocationError::OutOfMemory) => {} // Block is full, continue search.
|
||||
Err(err) => return Err(err), // Unhandled error, return.
|
||||
}
|
||||
} else if empty_block_index.is_none() {
|
||||
empty_block_index = Some(mem_block_i);
|
||||
}
|
||||
}
|
||||
|
||||
let new_memory_block = MemoryBlock::new(
|
||||
device,
|
||||
memblock_size,
|
||||
&self.heap_properties,
|
||||
self.heap_category,
|
||||
false,
|
||||
)?;
|
||||
|
||||
let new_block_index = if let Some(block_index) = empty_block_index {
|
||||
self.memory_blocks[block_index] = Some(new_memory_block);
|
||||
block_index
|
||||
} else {
|
||||
self.memory_blocks.push(Some(new_memory_block));
|
||||
self.memory_blocks.len() - 1
|
||||
};
|
||||
|
||||
self.active_general_blocks += 1;
|
||||
|
||||
let mem_block = self.memory_blocks[new_block_index]
|
||||
.as_mut()
|
||||
.ok_or_else(|| AllocationError::Internal("Memory block must be Some".into()))?;
|
||||
let allocation = mem_block.sub_allocator.allocate(
|
||||
size,
|
||||
alignment,
|
||||
allocation_type,
|
||||
1,
|
||||
desc.name,
|
||||
backtrace,
|
||||
);
|
||||
let (offset, chunk_id) = match allocation {
|
||||
Err(AllocationError::OutOfMemory) => Err(AllocationError::Internal(
|
||||
"Allocation that must succeed failed. This is a bug in the allocator.".into(),
|
||||
)),
|
||||
a => a,
|
||||
}?;
|
||||
|
||||
Ok(Allocation {
|
||||
chunk_id: Some(chunk_id),
|
||||
offset,
|
||||
size,
|
||||
memory_block_index: new_block_index,
|
||||
memory_type_index: self.memory_type_index,
|
||||
heap: mem_block.heap.clone(),
|
||||
name: Some(desc.name.into()),
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
fn free(&mut self, allocation: Allocation) -> Result<()> {
|
||||
let block_idx = allocation.memory_block_index;
|
||||
|
||||
let mem_block = self.memory_blocks[block_idx]
|
||||
.as_mut()
|
||||
.ok_or_else(|| AllocationError::Internal("Memory block must be Some.".into()))?;
|
||||
|
||||
mem_block.sub_allocator.free(allocation.chunk_id)?;
|
||||
|
||||
if mem_block.sub_allocator.is_empty() {
|
||||
if mem_block.sub_allocator.supports_general_allocations() {
|
||||
if self.active_general_blocks > 1 {
|
||||
let block = self.memory_blocks[block_idx].take();
|
||||
if block.is_none() {
|
||||
return Err(AllocationError::Internal(
|
||||
"Memory block must be Some.".into(),
|
||||
));
|
||||
}
|
||||
// Note that `block` will be destroyed on `drop` here
|
||||
|
||||
self.active_general_blocks -= 1;
|
||||
}
|
||||
} else {
|
||||
let block = self.memory_blocks[block_idx].take();
|
||||
if block.is_none() {
|
||||
return Err(AllocationError::Internal(
|
||||
"Memory block must be Some.".into(),
|
||||
));
|
||||
}
|
||||
// Note that `block` will be destroyed on `drop` here
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Allocator {
|
||||
device: ID3D12Device,
|
||||
debug_settings: AllocatorDebugSettings,
|
||||
memory_types: Vec<MemoryType>,
|
||||
}
|
||||
|
||||
impl Allocator {
|
||||
pub fn device(&self) -> &ID3D12Device {
|
||||
&self.device
|
||||
}
|
||||
|
||||
pub fn new(desc: &AllocatorCreateDesc) -> Result<Self> {
|
||||
// Perform AddRef on the device
|
||||
let device = desc.device.clone();
|
||||
|
||||
// Query device for feature level
|
||||
let mut options = Default::default();
|
||||
unsafe {
|
||||
device.CheckFeatureSupport(
|
||||
D3D12_FEATURE_D3D12_OPTIONS,
|
||||
<*mut D3D12_FEATURE_DATA_D3D12_OPTIONS>::cast(&mut options),
|
||||
std::mem::size_of_val(&options) as u32,
|
||||
)
|
||||
}
|
||||
.map_err(|e| {
|
||||
AllocationError::Internal(format!("ID3D12Device::CheckFeatureSupport failed: {}", e))
|
||||
})?;
|
||||
|
||||
let is_heap_tier1 = options.ResourceHeapTier == D3D12_RESOURCE_HEAP_TIER_1;
|
||||
|
||||
let heap_types = vec![
|
||||
(
|
||||
MemoryLocation::GpuOnly,
|
||||
D3D12_HEAP_PROPERTIES {
|
||||
Type: D3D12_HEAP_TYPE_DEFAULT,
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
MemoryLocation::CpuToGpu,
|
||||
D3D12_HEAP_PROPERTIES {
|
||||
Type: D3D12_HEAP_TYPE_CUSTOM,
|
||||
CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE,
|
||||
MemoryPoolPreference: D3D12_MEMORY_POOL_L0,
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
MemoryLocation::GpuToCpu,
|
||||
D3D12_HEAP_PROPERTIES {
|
||||
Type: D3D12_HEAP_TYPE_CUSTOM,
|
||||
CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_WRITE_BACK,
|
||||
MemoryPoolPreference: D3D12_MEMORY_POOL_L0,
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
let heap_types = if is_heap_tier1 {
|
||||
heap_types
|
||||
.iter()
|
||||
.flat_map(|(memory_location, heap_properties)| {
|
||||
[
|
||||
(HeapCategory::Buffer, *memory_location, *heap_properties),
|
||||
(
|
||||
HeapCategory::RtvDsvTexture,
|
||||
*memory_location,
|
||||
*heap_properties,
|
||||
),
|
||||
(
|
||||
HeapCategory::OtherTexture,
|
||||
*memory_location,
|
||||
*heap_properties,
|
||||
),
|
||||
]
|
||||
.to_vec()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
heap_types
|
||||
.iter()
|
||||
.map(|(memory_location, heap_properties)| {
|
||||
(HeapCategory::All, *memory_location, *heap_properties)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
let memory_types = heap_types
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(
|
||||
|(i, &(heap_category, memory_location, heap_properties))| MemoryType {
|
||||
memory_blocks: Vec::default(),
|
||||
memory_location,
|
||||
heap_category,
|
||||
heap_properties,
|
||||
memory_type_index: i,
|
||||
active_general_blocks: 0,
|
||||
committed_allocations: CommittedAllocationStatistics {
|
||||
num_allocations: 0,
|
||||
total_size: 0,
|
||||
},
|
||||
},
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(Self {
|
||||
memory_types,
|
||||
device,
|
||||
debug_settings: desc.debug_settings,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn allocate(&mut self, desc: &AllocationCreateDesc<'_>) -> Result<Allocation> {
|
||||
let size = desc.size;
|
||||
let alignment = desc.alignment;
|
||||
|
||||
let backtrace = if self.debug_settings.store_stack_traces {
|
||||
Some(backtrace::Backtrace::new_unresolved())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if self.debug_settings.log_allocations {
|
||||
debug!(
|
||||
"Allocating `{}` of {} bytes with an alignment of {}.",
|
||||
&desc.name, size, alignment
|
||||
);
|
||||
if self.debug_settings.log_stack_traces {
|
||||
let backtrace = backtrace::Backtrace::new();
|
||||
debug!("Allocation stack trace: {:?}", &backtrace);
|
||||
}
|
||||
}
|
||||
|
||||
if size == 0 || !alignment.is_power_of_two() {
|
||||
return Err(AllocationError::InvalidAllocationCreateDesc);
|
||||
}
|
||||
|
||||
// Find memory type
|
||||
let memory_type = self
|
||||
.memory_types
|
||||
.iter_mut()
|
||||
.find(|memory_type| {
|
||||
let is_location_compatible = desc.location == MemoryLocation::Unknown
|
||||
|| desc.location == memory_type.memory_location;
|
||||
|
||||
let is_category_compatible = memory_type.heap_category == HeapCategory::All
|
||||
|| memory_type.heap_category == desc.resource_category.into();
|
||||
|
||||
is_location_compatible && is_category_compatible
|
||||
})
|
||||
.ok_or(AllocationError::NoCompatibleMemoryTypeFound)?;
|
||||
|
||||
memory_type.allocate(&self.device, desc, backtrace)
|
||||
}
|
||||
|
||||
pub fn free(&mut self, allocation: Allocation) -> Result<()> {
|
||||
if self.debug_settings.log_frees {
|
||||
let name = allocation.name.as_deref().unwrap_or("<null>");
|
||||
debug!("Freeing `{}`.", name);
|
||||
if self.debug_settings.log_stack_traces {
|
||||
let backtrace = backtrace::Backtrace::new();
|
||||
debug!("Free stack trace: {:?}", backtrace);
|
||||
}
|
||||
}
|
||||
|
||||
if allocation.is_null() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.memory_types[allocation.memory_type_index].free(allocation)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn rename_allocation(&mut self, allocation: &mut Allocation, name: &str) -> Result<()> {
|
||||
allocation.name = Some(name.into());
|
||||
|
||||
if allocation.is_null() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mem_type = &mut self.memory_types[allocation.memory_type_index];
|
||||
let mem_block = mem_type.memory_blocks[allocation.memory_block_index]
|
||||
.as_mut()
|
||||
.ok_or_else(|| AllocationError::Internal("Memory block must be Some.".into()))?;
|
||||
|
||||
mem_block
|
||||
.sub_allocator
|
||||
.rename_allocation(allocation.chunk_id, name)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn report_memory_leaks(&self, log_level: Level) {
|
||||
for (mem_type_i, mem_type) in self.memory_types.iter().enumerate() {
|
||||
for (block_i, mem_block) in mem_type.memory_blocks.iter().enumerate() {
|
||||
if let Some(mem_block) = mem_block {
|
||||
mem_block
|
||||
.sub_allocator
|
||||
.report_memory_leaks(log_level, mem_type_i, block_i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a resource according to the provided parameters.
|
||||
/// Created resources should be freed at the end of their lifetime by calling [`Self::free_resource()`].
|
||||
pub fn create_resource(&mut self, desc: &ResourceCreateDesc<'_>) -> Result<Resource> {
|
||||
match desc.resource_type {
|
||||
ResourceType::Committed {
|
||||
heap_properties,
|
||||
heap_flags,
|
||||
} => {
|
||||
let mut result: Option<ID3D12Resource> = None;
|
||||
|
||||
let clear_value: Option<*const D3D12_CLEAR_VALUE> =
|
||||
desc.clear_value.map(|v| -> *const _ { v });
|
||||
|
||||
if let Err(err) = unsafe {
|
||||
self.device.CreateCommittedResource(
|
||||
*heap_properties,
|
||||
*heap_flags,
|
||||
desc.resource_desc,
|
||||
desc.initial_state,
|
||||
clear_value,
|
||||
&mut result,
|
||||
)
|
||||
} {
|
||||
Err(AllocationError::Internal(err.message().to_string()))
|
||||
} else {
|
||||
let resource =
|
||||
result.expect("Allocation succeeded but no resource was returned?");
|
||||
|
||||
let allocation_info = unsafe {
|
||||
self.device
|
||||
.GetResourceAllocationInfo(0, &[*desc.resource_desc])
|
||||
};
|
||||
|
||||
let memory_type = self
|
||||
.memory_types
|
||||
.iter_mut()
|
||||
.find(|memory_type| {
|
||||
let is_location_compatible = desc.memory_location
|
||||
== MemoryLocation::Unknown
|
||||
|| desc.memory_location == memory_type.memory_location;
|
||||
|
||||
let is_category_compatible = memory_type.heap_category
|
||||
== HeapCategory::All
|
||||
|| memory_type.heap_category == desc.resource_category.into();
|
||||
|
||||
is_location_compatible && is_category_compatible
|
||||
})
|
||||
.ok_or(AllocationError::NoCompatibleMemoryTypeFound)?;
|
||||
|
||||
memory_type.committed_allocations.num_allocations += 1;
|
||||
memory_type.committed_allocations.total_size += allocation_info.SizeInBytes;
|
||||
|
||||
Ok(Resource {
|
||||
name: desc.name.into(),
|
||||
allocation: None,
|
||||
resource: Some(resource),
|
||||
size: allocation_info.SizeInBytes,
|
||||
memory_location: desc.memory_location,
|
||||
memory_type_index: Some(memory_type.memory_type_index),
|
||||
})
|
||||
}
|
||||
}
|
||||
ResourceType::Placed => {
|
||||
let allocation_desc = {
|
||||
let allocation_info = unsafe {
|
||||
self.device
|
||||
.GetResourceAllocationInfo(0, &[*desc.resource_desc])
|
||||
};
|
||||
|
||||
AllocationCreateDesc {
|
||||
name: desc.name,
|
||||
location: desc.memory_location,
|
||||
size: allocation_info.SizeInBytes,
|
||||
alignment: allocation_info.Alignment,
|
||||
resource_category: desc.resource_category,
|
||||
}
|
||||
};
|
||||
|
||||
let allocation = self.allocate(&allocation_desc)?;
|
||||
|
||||
let mut result: Option<ID3D12Resource> = None;
|
||||
if let Err(err) = unsafe {
|
||||
self.device.CreatePlacedResource(
|
||||
allocation.heap(),
|
||||
allocation.offset(),
|
||||
desc.resource_desc,
|
||||
desc.initial_state,
|
||||
None,
|
||||
&mut result,
|
||||
)
|
||||
} {
|
||||
Err(AllocationError::Internal(err.message().to_string()))
|
||||
} else {
|
||||
let resource =
|
||||
result.expect("Allocation succeeded but no resource was returned?");
|
||||
let size = allocation.size();
|
||||
Ok(Resource {
|
||||
name: desc.name.into(),
|
||||
allocation: Some(allocation),
|
||||
resource: Some(resource),
|
||||
size,
|
||||
memory_location: desc.memory_location,
|
||||
memory_type_index: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Free a resource and its memory.
|
||||
pub fn free_resource(&mut self, mut resource: Resource) -> Result<()> {
|
||||
// Explicitly drop the resource (which is backed by a refcounted COM object)
|
||||
// before freeing allocated memory. Windows-rs performs a Release() on drop().
|
||||
let _ = resource
|
||||
.resource
|
||||
.take()
|
||||
.expect("Resource was already freed.");
|
||||
|
||||
if let Some(allocation) = resource.allocation.take() {
|
||||
self.free(allocation)
|
||||
} else {
|
||||
// Dx12 CommittedResources do not have an application managed allocation.
|
||||
// We only have to update the tracked allocation count and memory usage.
|
||||
if let Some(memory_type_index) = resource.memory_type_index {
|
||||
let memory_type = &mut self.memory_types[memory_type_index];
|
||||
|
||||
memory_type.committed_allocations.num_allocations -= 1;
|
||||
memory_type.committed_allocations.total_size -= resource.size;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Allocator {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut allocation_report = vec![];
|
||||
let mut total_reserved_size_in_bytes = 0;
|
||||
|
||||
for memory_type in &self.memory_types {
|
||||
for block in memory_type.memory_blocks.iter().flatten() {
|
||||
total_reserved_size_in_bytes += block.size;
|
||||
allocation_report.extend(block.sub_allocator.report_allocations())
|
||||
}
|
||||
}
|
||||
|
||||
let total_used_size_in_bytes = allocation_report.iter().map(|report| report.size).sum();
|
||||
|
||||
allocation_report.sort_by_key(|alloc| std::cmp::Reverse(alloc.size));
|
||||
|
||||
writeln!(
|
||||
f,
|
||||
"================================================================",
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
"ALLOCATION BREAKDOWN ({} / {})",
|
||||
fmt_bytes(total_used_size_in_bytes),
|
||||
fmt_bytes(total_reserved_size_in_bytes),
|
||||
)?;
|
||||
|
||||
let max_num_allocations_to_print = f.precision().map_or(usize::MAX, |n| n);
|
||||
for (idx, alloc) in allocation_report.iter().enumerate() {
|
||||
if idx >= max_num_allocations_to_print {
|
||||
break;
|
||||
}
|
||||
writeln!(
|
||||
f,
|
||||
"{:max_len$.max_len$}\t- {}",
|
||||
alloc.name,
|
||||
fmt_bytes(alloc.size),
|
||||
max_len = allocator::VISUALIZER_TABLE_MAX_ENTRY_NAME_LEN,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Allocator {
|
||||
fn drop(&mut self) {
|
||||
if self.debug_settings.log_leaks_on_shutdown {
|
||||
self.report_memory_leaks(Level::Warn);
|
||||
}
|
||||
|
||||
// Because Rust drop rules drop members in source-code order (that would be the
|
||||
// ID3D12Device before the ID3D12Heaps nested in these memory blocks), free
|
||||
// all remaining memory blocks manually first by dropping.
|
||||
for mem_type in self.memory_types.iter_mut() {
|
||||
mem_type.memory_blocks.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
400
third-party/vendor/gpu-allocator/src/d3d12/visualizer.rs
vendored
Normal file
400
third-party/vendor/gpu-allocator/src/d3d12/visualizer.rs
vendored
Normal file
|
|
@ -0,0 +1,400 @@
|
|||
#![allow(clippy::new_without_default)]
|
||||
|
||||
use super::Allocator;
|
||||
use crate::visualizer::ColorScheme;
|
||||
|
||||
use log::error;
|
||||
use windows::Win32::Graphics::Direct3D12::*;
|
||||
|
||||
// Default value for block visualizer granularity.
|
||||
#[allow(dead_code)]
|
||||
const DEFAULT_BYTES_PER_UNIT: i32 = 1024;
|
||||
|
||||
#[allow(dead_code)]
|
||||
struct AllocatorVisualizerBlockWindow {
|
||||
memory_type_index: usize,
|
||||
block_index: usize,
|
||||
bytes_per_unit: i32,
|
||||
show_backtraces: bool,
|
||||
}
|
||||
|
||||
impl AllocatorVisualizerBlockWindow {
|
||||
#[allow(dead_code)]
|
||||
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 {
|
||||
#[allow(dead_code)]
|
||||
selected_blocks: Vec<AllocatorVisualizerBlockWindow>,
|
||||
#[allow(dead_code)]
|
||||
focus: Option<usize>,
|
||||
color_scheme: ColorScheme,
|
||||
allocation_breakdown_sorting: Option<(Option<imgui::TableSortDirection>, usize)>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn format_heap_type(heap_type: D3D12_HEAP_TYPE) -> &'static str {
|
||||
let names = [
|
||||
"D3D12_HEAP_TYPE_DEFAULT_INVALID",
|
||||
"D3D12_HEAP_TYPE_DEFAULT",
|
||||
"D3D12_HEAP_TYPE_UPLOAD",
|
||||
"D3D12_HEAP_TYPE_READBACK",
|
||||
"D3D12_HEAP_TYPE_CUSTOM",
|
||||
];
|
||||
|
||||
names[heap_type.0 as usize]
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn format_cpu_page_property(prop: D3D12_CPU_PAGE_PROPERTY) -> &'static str {
|
||||
let names = [
|
||||
"D3D12_CPU_PAGE_PROPERTY_UNKNOWN",
|
||||
"D3D12_CPU_PAGE_PROPERTY_NOT_AVAILABLE",
|
||||
"D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE",
|
||||
"D3D12_CPU_PAGE_PROPERTY_WRITE_BACK",
|
||||
];
|
||||
|
||||
names[prop.0 as usize]
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn format_memory_pool(pool: D3D12_MEMORY_POOL) -> &'static str {
|
||||
let names = [
|
||||
"D3D12_MEMORY_POOL_UNKNOWN",
|
||||
"D3D12_MEMORY_POOL_L0",
|
||||
"D3D12_MEMORY_POOL_L1",
|
||||
];
|
||||
|
||||
names[pool.0 as usize]
|
||||
}
|
||||
|
||||
impl AllocatorVisualizer {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
selected_blocks: Vec::default(),
|
||||
focus: None,
|
||||
color_scheme: ColorScheme::default(),
|
||||
allocation_breakdown_sorting: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_color_scheme(&mut self, color_scheme: ColorScheme) {
|
||||
self.color_scheme = color_scheme;
|
||||
}
|
||||
|
||||
pub 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::*;
|
||||
|
||||
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: {}", 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.sub_allocator.size();
|
||||
total_allocated += block.sub_allocator.allocated();
|
||||
}
|
||||
ui.text(format!("heap category: {:?}", mem_type.heap_category));
|
||||
ui.text(format!(
|
||||
"Heap Type: {} ({})",
|
||||
format_heap_type(mem_type.heap_properties.Type),
|
||||
mem_type.heap_properties.Type.0
|
||||
));
|
||||
ui.text(format!(
|
||||
"CpuPageProperty: {} ({})",
|
||||
format_cpu_page_property(mem_type.heap_properties.CPUPageProperty),
|
||||
mem_type.heap_properties.CPUPageProperty.0
|
||||
));
|
||||
ui.text(format!(
|
||||
"MemoryPoolPreference: {} ({})",
|
||||
format_memory_pool(mem_type.heap_properties.MemoryPoolPreference),
|
||||
mem_type.heap_properties.MemoryPoolPreference.0
|
||||
));
|
||||
ui.text(format!("total block size: {} KiB", total_block_size / 1024));
|
||||
ui.text(format!("total allocated: {} KiB", total_allocated / 1024));
|
||||
ui.text(format!(
|
||||
"committed resource allocations: {}",
|
||||
mem_type.committed_allocations.num_allocations
|
||||
));
|
||||
ui.text(format!(
|
||||
"total committed resource allocations: {} KiB",
|
||||
mem_type.committed_allocations.total_size
|
||||
));
|
||||
|
||||
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() {
|
||||
ui.indent();
|
||||
ui.text(format!(
|
||||
"size: {} KiB",
|
||||
block.sub_allocator.size() / 1024
|
||||
));
|
||||
ui.text(format!(
|
||||
"allocated: {} KiB",
|
||||
block.sub_allocator.allocated() / 1024
|
||||
));
|
||||
ui.text(format!("D3D12 heap: {:?}", block.heap));
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn render_memory_block_windows(&mut self, ui: &imgui::Ui, alloc: &Allocator) {
|
||||
// Copy here to workaround the borrow checker.
|
||||
let focus_opt = self.focus;
|
||||
// Keep track of a list of windows that are signaled by imgui to be closed.
|
||||
let mut windows_to_close = Vec::default();
|
||||
// Draw each window.
|
||||
let color_scheme = &self.color_scheme;
|
||||
for (window_i, window) in self.selected_blocks.iter_mut().enumerate() {
|
||||
// Determine if this window needs focus.
|
||||
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.sub_allocator.size() / 1024
|
||||
));
|
||||
|
||||
if alloc.debug_settings.store_stack_traces {
|
||||
ui.checkbox("Show backtraces", &mut window.show_backtraces);
|
||||
}
|
||||
// Slider for changing the 'zoom' level of the visualizer.
|
||||
#[allow(dead_code)]
|
||||
const BYTES_PER_UNIT_MIN: i32 = 1;
|
||||
#[allow(dead_code)]
|
||||
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);
|
||||
|
||||
// Imgui can actually modify this number to be out of bounds, so we will clamp manually.
|
||||
window.bytes_per_unit = window
|
||||
.bytes_per_unit
|
||||
.clamp(BYTES_PER_UNIT_MIN, BYTES_PER_UNIT_MAX);
|
||||
|
||||
// Draw the visualization in a child window.
|
||||
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 imgui signalled to close the window, add it to the list of windows to close.
|
||||
if !is_open {
|
||||
windows_to_close.push(window_i);
|
||||
}
|
||||
}
|
||||
//
|
||||
// Clean-up
|
||||
//
|
||||
// Close windows.
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Reset focus.
|
||||
self.focus = None;
|
||||
}
|
||||
|
||||
/// Renders imgui widgets.
|
||||
///
|
||||
/// The [`Option<&mut bool>`] can be used control and track changes to the opened/closed status of the widget.
|
||||
/// Pass [`None`] if no control and readback information is required. This will always render the widget.
|
||||
/// When passing `Some(&mut bool)`:
|
||||
/// - If [`false`], the widget won't be drawn.
|
||||
/// - If [`true`], the widget will be drawn and an (X) closing button will be added to the widget bar.
|
||||
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>,
|
||||
) {
|
||||
ui.window("Allocation Breakdown")
|
||||
.position([20.0f32, 80.0f32], imgui::Condition::FirstUseEver)
|
||||
.size([460.0f32, 420.0f32], imgui::Condition::FirstUseEver)
|
||||
.opened(opened.unwrap_or(&mut false))
|
||||
.build(|| {
|
||||
let mut allocation_report = vec![];
|
||||
|
||||
for memory_type in &allocator.memory_types {
|
||||
for block in memory_type.memory_blocks.iter().flatten() {
|
||||
allocation_report
|
||||
.extend_from_slice(&block.sub_allocator.report_allocations())
|
||||
}
|
||||
}
|
||||
|
||||
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(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);
|
||||
|
||||
ui.table_next_column();
|
||||
ui.text(format!("{:.3?}", alloc.size));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
212
third-party/vendor/gpu-allocator/src/lib.rs
vendored
Normal file
212
third-party/vendor/gpu-allocator/src/lib.rs
vendored
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
//! This crate provides a fully written in Rust memory allocator for Vulkan and DirectX 12.
|
||||
//!
|
||||
//! ## [Windows-rs] and [winapi]
|
||||
//!
|
||||
//! `gpu-allocator` recently migrated from [winapi] to [windows-rs] but still provides convenient helpers to convert to and from [winapi] types, enabled when compiling with the `public-winapi` crate feature.
|
||||
//!
|
||||
//! [Windows-rs]: https://github.com/microsoft/windows-rs
|
||||
//! [winapi]: https://github.com/retep998/winapi-rs
|
||||
//!
|
||||
//! ## Setting up the Vulkan memory allocator
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # #[cfg(feature = "vulkan")]
|
||||
//! # fn main() {
|
||||
//! use gpu_allocator::vulkan::*;
|
||||
//! # use ash::vk;
|
||||
//! # let device = todo!();
|
||||
//! # let instance = todo!();
|
||||
//! # let physical_device = todo!();
|
||||
//!
|
||||
//! let mut allocator = Allocator::new(&AllocatorCreateDesc {
|
||||
//! instance,
|
||||
//! device,
|
||||
//! physical_device,
|
||||
//! debug_settings: Default::default(),
|
||||
//! buffer_device_address: true, // Ideally, check the BufferDeviceAddressFeatures struct.
|
||||
//! });
|
||||
//! # }
|
||||
//! # #[cfg(not(feature = "vulkan"))]
|
||||
//! # fn main() {}
|
||||
//! ```
|
||||
//!
|
||||
//! ## Simple Vulkan allocation example
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # #[cfg(feature = "vulkan")]
|
||||
//! # fn main() {
|
||||
//! use gpu_allocator::vulkan::*;
|
||||
//! use gpu_allocator::MemoryLocation;
|
||||
//! # use ash::vk;
|
||||
//! # let device = todo!();
|
||||
//! # let instance = todo!();
|
||||
//! # let physical_device = todo!();
|
||||
//!
|
||||
//! # let mut allocator = Allocator::new(&AllocatorCreateDesc {
|
||||
//! # instance,
|
||||
//! # device,
|
||||
//! # physical_device,
|
||||
//! # debug_settings: Default::default(),
|
||||
//! # buffer_device_address: true, // Ideally, check the BufferDeviceAddressFeatures struct.
|
||||
//! # }).unwrap();
|
||||
//!
|
||||
//! // Setup vulkan info
|
||||
//! let vk_info = vk::BufferCreateInfo::builder()
|
||||
//! .size(512)
|
||||
//! .usage(vk::BufferUsageFlags::STORAGE_BUFFER);
|
||||
//!
|
||||
//! let buffer = unsafe { device.create_buffer(&vk_info, None) }.unwrap();
|
||||
//! let requirements = unsafe { device.get_buffer_memory_requirements(buffer) };
|
||||
//!
|
||||
//! let allocation = allocator
|
||||
//! .allocate(&AllocationCreateDesc {
|
||||
//! name: "Example allocation",
|
||||
//! requirements,
|
||||
//! location: MemoryLocation::CpuToGpu,
|
||||
//! linear: true, // Buffers are always linear
|
||||
//! allocation_scheme: AllocationScheme::GpuAllocatorManaged,
|
||||
//! }).unwrap();
|
||||
//!
|
||||
//! // Bind memory to the buffer
|
||||
//! unsafe { device.bind_buffer_memory(buffer, allocation.memory(), allocation.offset()).unwrap() };
|
||||
//!
|
||||
//! // Cleanup
|
||||
//! allocator.free(allocation).unwrap();
|
||||
//! unsafe { device.destroy_buffer(buffer, None) };
|
||||
//! # }
|
||||
//! # #[cfg(not(feature = "vulkan"))]
|
||||
//! # fn main() {}
|
||||
//! ```
|
||||
//!
|
||||
//! ## Setting up the D3D12 memory allocator
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # #[cfg(feature = "d3d12")]
|
||||
//! # fn main() {
|
||||
//! use gpu_allocator::d3d12::*;
|
||||
//! # let device = todo!();
|
||||
//!
|
||||
//! let mut allocator = Allocator::new(&AllocatorCreateDesc {
|
||||
//! device,
|
||||
//! debug_settings: Default::default(),
|
||||
//! });
|
||||
//! # }
|
||||
//! # #[cfg(not(feature = "d3d12"))]
|
||||
//! # fn main() {}
|
||||
//! ```
|
||||
//!
|
||||
//! ## Simple d3d12 allocation example
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # #[cfg(feature = "d3d12")]
|
||||
//! # fn main() -> windows::core::Result<()> {
|
||||
//! use gpu_allocator::d3d12::*;
|
||||
//! use gpu_allocator::MemoryLocation;
|
||||
//! # use windows::Win32::Graphics::{Dxgi, Direct3D12};
|
||||
//! # let device = todo!();
|
||||
//!
|
||||
//! # let mut allocator = Allocator::new(&AllocatorCreateDesc {
|
||||
//! # device: device,
|
||||
//! # debug_settings: Default::default(),
|
||||
//! # }).unwrap();
|
||||
//!
|
||||
//! let buffer_desc = Direct3D12::D3D12_RESOURCE_DESC {
|
||||
//! Dimension: Direct3D12::D3D12_RESOURCE_DIMENSION_BUFFER,
|
||||
//! Alignment: 0,
|
||||
//! Width: 512,
|
||||
//! Height: 1,
|
||||
//! DepthOrArraySize: 1,
|
||||
//! MipLevels: 1,
|
||||
//! Format: Dxgi::Common::DXGI_FORMAT_UNKNOWN,
|
||||
//! SampleDesc: Dxgi::Common::DXGI_SAMPLE_DESC {
|
||||
//! Count: 1,
|
||||
//! Quality: 0,
|
||||
//! },
|
||||
//! Layout: Direct3D12::D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
|
||||
//! Flags: Direct3D12::D3D12_RESOURCE_FLAG_NONE,
|
||||
//! };
|
||||
//! let allocation_desc = AllocationCreateDesc::from_d3d12_resource_desc(
|
||||
//! &allocator.device(),
|
||||
//! &buffer_desc,
|
||||
//! "Example allocation",
|
||||
//! MemoryLocation::GpuOnly,
|
||||
//! );
|
||||
//! let allocation = allocator.allocate(&allocation_desc).unwrap();
|
||||
//! let mut resource: Option<Direct3D12::ID3D12Resource> = None;
|
||||
//! let hr = unsafe {
|
||||
//! device.CreatePlacedResource(
|
||||
//! allocation.heap(),
|
||||
//! allocation.offset(),
|
||||
//! &buffer_desc,
|
||||
//! Direct3D12::D3D12_RESOURCE_STATE_COMMON,
|
||||
//! None,
|
||||
//! &mut resource,
|
||||
//! )
|
||||
//! }?;
|
||||
//!
|
||||
//! // Cleanup
|
||||
//! drop(resource);
|
||||
//! allocator.free(allocation).unwrap();
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! # #[cfg(not(feature = "d3d12"))]
|
||||
//! # fn main() {}
|
||||
//! ```
|
||||
|
||||
mod result;
|
||||
pub use result::*;
|
||||
|
||||
pub(crate) mod allocator;
|
||||
|
||||
#[cfg(feature = "visualizer")]
|
||||
pub mod visualizer;
|
||||
|
||||
#[cfg(feature = "vulkan")]
|
||||
pub mod vulkan;
|
||||
|
||||
#[cfg(all(windows, feature = "d3d12"))]
|
||||
pub mod d3d12;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum MemoryLocation {
|
||||
/// The allocated resource is stored at an unknown memory location; let the driver decide what's the best location
|
||||
Unknown,
|
||||
/// Store the allocation in GPU only accessible memory - typically this is the faster GPU resource and this should be
|
||||
/// where most of the allocations live.
|
||||
GpuOnly,
|
||||
/// Memory useful for uploading data to the GPU and potentially for constant buffers
|
||||
CpuToGpu,
|
||||
/// Memory useful for CPU readback of data
|
||||
GpuToCpu,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct AllocatorDebugSettings {
|
||||
/// Logs out debugging information about the various heaps the current device has on startup
|
||||
pub log_memory_information: bool,
|
||||
/// Logs out all memory leaks on shutdown with log level Warn
|
||||
pub log_leaks_on_shutdown: bool,
|
||||
/// Stores a copy of the full backtrace for every allocation made, this makes it easier to debug leaks
|
||||
/// or other memory allocations, but storing stack traces has a RAM overhead so should be disabled
|
||||
/// in shipping applications.
|
||||
pub store_stack_traces: bool,
|
||||
/// Log out every allocation as it's being made with log level Debug, rather spammy so off by default
|
||||
pub log_allocations: bool,
|
||||
/// Log out every free that is being called with log level Debug, rather spammy so off by default
|
||||
pub log_frees: bool,
|
||||
/// Log out stack traces when either `log_allocations` or `log_frees` is enabled.
|
||||
pub log_stack_traces: bool,
|
||||
}
|
||||
|
||||
impl Default for AllocatorDebugSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
log_memory_information: false,
|
||||
log_leaks_on_shutdown: true,
|
||||
store_stack_traces: false,
|
||||
log_allocations: false,
|
||||
log_frees: false,
|
||||
log_stack_traces: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
19
third-party/vendor/gpu-allocator/src/result.rs
vendored
Normal file
19
third-party/vendor/gpu-allocator/src/result.rs
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum AllocationError {
|
||||
#[error("Out of memory")]
|
||||
OutOfMemory,
|
||||
#[error("Failed to map memory: {0}")]
|
||||
FailedToMap(String),
|
||||
#[error("No compatible memory type available")]
|
||||
NoCompatibleMemoryTypeFound,
|
||||
#[error("Invalid AllocationCreateDesc")]
|
||||
InvalidAllocationCreateDesc,
|
||||
#[error("Invalid AllocatorCreateDesc {0}")]
|
||||
InvalidAllocatorCreateDesc(String),
|
||||
#[error("Internal error: {0}")]
|
||||
Internal(String),
|
||||
}
|
||||
|
||||
pub type Result<V, E = AllocationError> = ::std::result::Result<V, E>;
|
||||
34
third-party/vendor/gpu-allocator/src/visualizer/mod.rs
vendored
Normal file
34
third-party/vendor/gpu-allocator/src/visualizer/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
use imgui::*;
|
||||
#[derive(Clone)]
|
||||
pub struct ColorScheme {
|
||||
pub free_color: ImColor32,
|
||||
pub linear_color: ImColor32,
|
||||
pub non_linear_color: ImColor32,
|
||||
}
|
||||
|
||||
impl Default for ColorScheme {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
free_color: 0xff9f_9f9f.into(), // gray
|
||||
linear_color: 0xfffa_ce5b.into(), // blue
|
||||
non_linear_color: 0xffb8_a9fa.into(), // pink
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait SubAllocatorVisualizer {
|
||||
fn supports_visualization(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn draw_base_info(&self, ui: &Ui) {
|
||||
ui.text("No sub allocator information available");
|
||||
}
|
||||
fn draw_visualization(
|
||||
&self,
|
||||
_color_scheme: &ColorScheme,
|
||||
_ui: &Ui,
|
||||
_bytes_per_unit: i32,
|
||||
_show_backtraces: bool,
|
||||
) {
|
||||
}
|
||||
}
|
||||
808
third-party/vendor/gpu-allocator/src/vulkan/mod.rs
vendored
Normal file
808
third-party/vendor/gpu-allocator/src/vulkan/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,808 @@
|
|||
#![deny(clippy::unimplemented, clippy::unwrap_used, clippy::ok_expect)]
|
||||
|
||||
#[cfg(feature = "visualizer")]
|
||||
mod visualizer;
|
||||
#[cfg(feature = "visualizer")]
|
||||
pub use visualizer::AllocatorVisualizer;
|
||||
|
||||
use super::allocator;
|
||||
use super::allocator::AllocationType;
|
||||
use ash::vk;
|
||||
use log::{debug, Level};
|
||||
use std::fmt;
|
||||
|
||||
use crate::{
|
||||
allocator::fmt_bytes, AllocationError, AllocatorDebugSettings, MemoryLocation, Result,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum AllocationScheme {
|
||||
/// Perform a dedicated, driver-managed allocation for the given buffer, allowing
|
||||
/// it to perform optimizations on this type of allocation.
|
||||
DedicatedBuffer(vk::Buffer),
|
||||
/// Perform a dedicated, driver-managed allocation for the given image, allowing
|
||||
/// it to perform optimizations on this type of allocation.
|
||||
DedicatedImage(vk::Image),
|
||||
/// The memory for this resource will be allocated and managed by gpu-allocator.
|
||||
GpuAllocatorManaged,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AllocationCreateDesc<'a> {
|
||||
/// Name of the allocation, for tracking and debugging purposes
|
||||
pub name: &'a str,
|
||||
/// Vulkan memory requirements for an allocation
|
||||
pub requirements: vk::MemoryRequirements,
|
||||
/// Location where the memory allocation should be stored
|
||||
pub location: MemoryLocation,
|
||||
/// If the resource is linear (buffer / linear texture) or a regular (tiled) texture.
|
||||
pub linear: bool,
|
||||
/// Determines how this allocation should be managed.
|
||||
pub allocation_scheme: AllocationScheme,
|
||||
}
|
||||
|
||||
/// Wrapper type to only mark a raw pointer [`Send`] + [`Sync`] without having to
|
||||
/// mark the entire [`Allocation`] as such, instead relying on the compiler to
|
||||
/// auto-implement this or fail if fields are added that violate this constraint
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) struct SendSyncPtr(std::ptr::NonNull<std::ffi::c_void>);
|
||||
// Sending is fine because mapped_ptr does not change based on the thread we are in
|
||||
unsafe impl Send for SendSyncPtr {}
|
||||
// Sync is also okay because Sending &Allocation is safe: a mutable reference
|
||||
// to the data in mapped_ptr is never exposed while `self` is immutably borrowed.
|
||||
// In order to break safety guarantees, the user needs to `unsafe`ly dereference
|
||||
// `mapped_ptr` themselves.
|
||||
unsafe impl Sync for SendSyncPtr {}
|
||||
|
||||
pub struct AllocatorCreateDesc {
|
||||
pub instance: ash::Instance,
|
||||
pub device: ash::Device,
|
||||
pub physical_device: ash::vk::PhysicalDevice,
|
||||
pub debug_settings: AllocatorDebugSettings,
|
||||
pub buffer_device_address: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Allocation {
|
||||
chunk_id: Option<std::num::NonZeroU64>,
|
||||
offset: u64,
|
||||
size: u64,
|
||||
memory_block_index: usize,
|
||||
memory_type_index: usize,
|
||||
device_memory: vk::DeviceMemory,
|
||||
mapped_ptr: Option<SendSyncPtr>,
|
||||
dedicated_allocation: bool,
|
||||
|
||||
name: Option<Box<str>>,
|
||||
}
|
||||
|
||||
impl Allocation {
|
||||
pub fn chunk_id(&self) -> Option<std::num::NonZeroU64> {
|
||||
self.chunk_id
|
||||
}
|
||||
|
||||
/// Returns the [`vk::DeviceMemory`] object that is backing this allocation.
|
||||
/// This memory object can be shared with multiple other allocations and shouldn't be freed (or allocated from)
|
||||
/// without this library, because that will lead to undefined behavior.
|
||||
///
|
||||
/// # Safety
|
||||
/// The result of this function can safely be used to pass into [`ash::Device::bind_buffer_memory()`],
|
||||
/// [`ash::Device::bind_image_memory()`] etc. It is exposed for this reason. Keep in mind to also
|
||||
/// pass [`Self::offset()`] along to those.
|
||||
pub unsafe fn memory(&self) -> vk::DeviceMemory {
|
||||
self.device_memory
|
||||
}
|
||||
|
||||
/// Returns [`true`] if this allocation is using a dedicated underlying allocation.
|
||||
pub fn is_dedicated(&self) -> bool {
|
||||
self.dedicated_allocation
|
||||
}
|
||||
|
||||
/// Returns the offset of the allocation on the [`vk::DeviceMemory`].
|
||||
/// When binding the memory to a buffer or image, this offset needs to be supplied as well.
|
||||
pub fn offset(&self) -> u64 {
|
||||
self.offset
|
||||
}
|
||||
|
||||
/// Returns the size of the allocation
|
||||
pub fn size(&self) -> u64 {
|
||||
self.size
|
||||
}
|
||||
|
||||
/// Returns a valid mapped pointer if the memory is host visible, otherwise it will return None.
|
||||
/// The pointer already points to the exact memory region of the suballocation, so no offset needs to be applied.
|
||||
pub fn mapped_ptr(&self) -> Option<std::ptr::NonNull<std::ffi::c_void>> {
|
||||
self.mapped_ptr.map(|SendSyncPtr(p)| p)
|
||||
}
|
||||
|
||||
/// Returns a valid mapped slice if the memory is host visible, otherwise it will return None.
|
||||
/// The slice already references the exact memory region of the allocation, so no offset needs to be applied.
|
||||
pub fn mapped_slice(&self) -> Option<&[u8]> {
|
||||
self.mapped_ptr().map(|ptr| unsafe {
|
||||
std::slice::from_raw_parts(ptr.cast().as_ptr(), self.size as usize)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a valid mapped mutable slice if the memory is host visible, otherwise it will return None.
|
||||
/// The slice already references the exact memory region of the allocation, so no offset needs to be applied.
|
||||
pub fn mapped_slice_mut(&mut self) -> Option<&mut [u8]> {
|
||||
self.mapped_ptr().map(|ptr| unsafe {
|
||||
std::slice::from_raw_parts_mut(ptr.cast().as_ptr(), self.size as usize)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_null(&self) -> bool {
|
||||
self.chunk_id.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Allocation {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
chunk_id: None,
|
||||
offset: 0,
|
||||
size: 0,
|
||||
memory_block_index: !0,
|
||||
memory_type_index: !0,
|
||||
device_memory: vk::DeviceMemory::null(),
|
||||
mapped_ptr: None,
|
||||
name: None,
|
||||
dedicated_allocation: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct MemoryBlock {
|
||||
pub(crate) device_memory: vk::DeviceMemory,
|
||||
pub(crate) size: u64,
|
||||
pub(crate) mapped_ptr: Option<SendSyncPtr>,
|
||||
pub(crate) sub_allocator: Box<dyn allocator::SubAllocator>,
|
||||
pub(crate) dedicated_allocation: bool,
|
||||
}
|
||||
|
||||
impl MemoryBlock {
|
||||
fn new(
|
||||
device: &ash::Device,
|
||||
size: u64,
|
||||
mem_type_index: usize,
|
||||
mapped: bool,
|
||||
buffer_device_address: bool,
|
||||
allocation_scheme: AllocationScheme,
|
||||
requires_personal_block: bool,
|
||||
) -> Result<Self> {
|
||||
let dedicated_allocation = allocation_scheme != AllocationScheme::GpuAllocatorManaged;
|
||||
let device_memory = {
|
||||
let alloc_info = vk::MemoryAllocateInfo::builder()
|
||||
.allocation_size(size)
|
||||
.memory_type_index(mem_type_index as u32);
|
||||
|
||||
let allocation_flags = vk::MemoryAllocateFlags::DEVICE_ADDRESS;
|
||||
let mut flags_info = vk::MemoryAllocateFlagsInfo::builder().flags(allocation_flags);
|
||||
// TODO(manon): Test this based on if the device has this feature enabled or not
|
||||
let alloc_info = if buffer_device_address {
|
||||
alloc_info.push_next(&mut flags_info)
|
||||
} else {
|
||||
alloc_info
|
||||
};
|
||||
|
||||
// Flag the memory as dedicated if required.
|
||||
let mut dedicated_memory_info = vk::MemoryDedicatedAllocateInfo::builder();
|
||||
let alloc_info = match allocation_scheme {
|
||||
AllocationScheme::DedicatedBuffer(buffer) => {
|
||||
dedicated_memory_info = dedicated_memory_info.buffer(buffer);
|
||||
alloc_info.push_next(&mut dedicated_memory_info)
|
||||
}
|
||||
AllocationScheme::DedicatedImage(image) => {
|
||||
dedicated_memory_info = dedicated_memory_info.image(image);
|
||||
alloc_info.push_next(&mut dedicated_memory_info)
|
||||
}
|
||||
AllocationScheme::GpuAllocatorManaged => alloc_info,
|
||||
};
|
||||
|
||||
unsafe { device.allocate_memory(&alloc_info, None) }.map_err(|e| match e {
|
||||
vk::Result::ERROR_OUT_OF_DEVICE_MEMORY => AllocationError::OutOfMemory,
|
||||
e => AllocationError::Internal(format!(
|
||||
"Unexpected error in vkAllocateMemory: {:?}",
|
||||
e
|
||||
)),
|
||||
})?
|
||||
};
|
||||
|
||||
let mapped_ptr = mapped
|
||||
.then(|| {
|
||||
unsafe {
|
||||
device.map_memory(
|
||||
device_memory,
|
||||
0,
|
||||
vk::WHOLE_SIZE,
|
||||
vk::MemoryMapFlags::empty(),
|
||||
)
|
||||
}
|
||||
.map_err(|e| {
|
||||
unsafe { device.free_memory(device_memory, None) };
|
||||
AllocationError::FailedToMap(e.to_string())
|
||||
})
|
||||
.and_then(|p| {
|
||||
std::ptr::NonNull::new(p).map(SendSyncPtr).ok_or_else(|| {
|
||||
AllocationError::FailedToMap("Returned mapped pointer is null".to_owned())
|
||||
})
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let sub_allocator: Box<dyn allocator::SubAllocator> = if allocation_scheme
|
||||
!= AllocationScheme::GpuAllocatorManaged
|
||||
|| requires_personal_block
|
||||
{
|
||||
Box::new(allocator::DedicatedBlockAllocator::new(size))
|
||||
} else {
|
||||
Box::new(allocator::FreeListAllocator::new(size))
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
device_memory,
|
||||
size,
|
||||
mapped_ptr,
|
||||
sub_allocator,
|
||||
dedicated_allocation,
|
||||
})
|
||||
}
|
||||
|
||||
fn destroy(self, device: &ash::Device) {
|
||||
if self.mapped_ptr.is_some() {
|
||||
unsafe { device.unmap_memory(self.device_memory) };
|
||||
}
|
||||
|
||||
unsafe { device.free_memory(self.device_memory, None) };
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct MemoryType {
|
||||
pub(crate) memory_blocks: Vec<Option<MemoryBlock>>,
|
||||
pub(crate) memory_properties: vk::MemoryPropertyFlags,
|
||||
pub(crate) memory_type_index: usize,
|
||||
pub(crate) heap_index: usize,
|
||||
pub(crate) mappable: bool,
|
||||
pub(crate) active_general_blocks: usize,
|
||||
pub(crate) buffer_device_address: bool,
|
||||
}
|
||||
|
||||
const DEFAULT_DEVICE_MEMBLOCK_SIZE: u64 = 256 * 1024 * 1024;
|
||||
const DEFAULT_HOST_MEMBLOCK_SIZE: u64 = 64 * 1024 * 1024;
|
||||
|
||||
impl MemoryType {
|
||||
fn allocate(
|
||||
&mut self,
|
||||
device: &ash::Device,
|
||||
desc: &AllocationCreateDesc<'_>,
|
||||
granularity: u64,
|
||||
backtrace: Option<backtrace::Backtrace>,
|
||||
) -> Result<Allocation> {
|
||||
let allocation_type = if desc.linear {
|
||||
AllocationType::Linear
|
||||
} else {
|
||||
AllocationType::NonLinear
|
||||
};
|
||||
|
||||
let memblock_size = if self
|
||||
.memory_properties
|
||||
.contains(vk::MemoryPropertyFlags::HOST_VISIBLE)
|
||||
{
|
||||
DEFAULT_HOST_MEMBLOCK_SIZE
|
||||
} else {
|
||||
DEFAULT_DEVICE_MEMBLOCK_SIZE
|
||||
};
|
||||
|
||||
let size = desc.requirements.size;
|
||||
let alignment = desc.requirements.alignment;
|
||||
|
||||
let dedicated_allocation = desc.allocation_scheme != AllocationScheme::GpuAllocatorManaged;
|
||||
let requires_personal_block = size > memblock_size;
|
||||
|
||||
// Create a dedicated block for large memory allocations or allocations that require dedicated memory allocations.
|
||||
if dedicated_allocation || requires_personal_block {
|
||||
let mem_block = MemoryBlock::new(
|
||||
device,
|
||||
size,
|
||||
self.memory_type_index,
|
||||
self.mappable,
|
||||
self.buffer_device_address,
|
||||
desc.allocation_scheme,
|
||||
requires_personal_block,
|
||||
)?;
|
||||
|
||||
let mut block_index = None;
|
||||
for (i, block) in self.memory_blocks.iter().enumerate() {
|
||||
if block.is_none() {
|
||||
block_index = Some(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let block_index = match block_index {
|
||||
Some(i) => {
|
||||
self.memory_blocks[i].replace(mem_block);
|
||||
i
|
||||
}
|
||||
None => {
|
||||
self.memory_blocks.push(Some(mem_block));
|
||||
self.memory_blocks.len() - 1
|
||||
}
|
||||
};
|
||||
|
||||
let mem_block = self.memory_blocks[block_index]
|
||||
.as_mut()
|
||||
.ok_or_else(|| AllocationError::Internal("Memory block must be Some".into()))?;
|
||||
|
||||
let (offset, chunk_id) = mem_block.sub_allocator.allocate(
|
||||
size,
|
||||
alignment,
|
||||
allocation_type,
|
||||
granularity,
|
||||
desc.name,
|
||||
backtrace,
|
||||
)?;
|
||||
|
||||
return Ok(Allocation {
|
||||
chunk_id: Some(chunk_id),
|
||||
offset,
|
||||
size,
|
||||
memory_block_index: block_index,
|
||||
memory_type_index: self.memory_type_index,
|
||||
device_memory: mem_block.device_memory,
|
||||
mapped_ptr: mem_block.mapped_ptr,
|
||||
name: Some(desc.name.into()),
|
||||
dedicated_allocation,
|
||||
});
|
||||
}
|
||||
|
||||
let mut empty_block_index = None;
|
||||
for (mem_block_i, mem_block) in self.memory_blocks.iter_mut().enumerate().rev() {
|
||||
if let Some(mem_block) = mem_block {
|
||||
let allocation = mem_block.sub_allocator.allocate(
|
||||
size,
|
||||
alignment,
|
||||
allocation_type,
|
||||
granularity,
|
||||
desc.name,
|
||||
backtrace.clone(),
|
||||
);
|
||||
|
||||
match allocation {
|
||||
Ok((offset, chunk_id)) => {
|
||||
let mapped_ptr = if let Some(SendSyncPtr(mapped_ptr)) = mem_block.mapped_ptr
|
||||
{
|
||||
let offset_ptr = unsafe { mapped_ptr.as_ptr().add(offset as usize) };
|
||||
std::ptr::NonNull::new(offset_ptr).map(SendSyncPtr)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
return Ok(Allocation {
|
||||
chunk_id: Some(chunk_id),
|
||||
offset,
|
||||
size,
|
||||
memory_block_index: mem_block_i,
|
||||
memory_type_index: self.memory_type_index,
|
||||
device_memory: mem_block.device_memory,
|
||||
mapped_ptr,
|
||||
dedicated_allocation: false,
|
||||
name: Some(desc.name.into()),
|
||||
});
|
||||
}
|
||||
Err(err) => match err {
|
||||
AllocationError::OutOfMemory => {} // Block is full, continue search.
|
||||
_ => return Err(err), // Unhandled error, return.
|
||||
},
|
||||
}
|
||||
} else if empty_block_index.is_none() {
|
||||
empty_block_index = Some(mem_block_i);
|
||||
}
|
||||
}
|
||||
|
||||
let new_memory_block = MemoryBlock::new(
|
||||
device,
|
||||
memblock_size,
|
||||
self.memory_type_index,
|
||||
self.mappable,
|
||||
self.buffer_device_address,
|
||||
desc.allocation_scheme,
|
||||
false,
|
||||
)?;
|
||||
|
||||
let new_block_index = if let Some(block_index) = empty_block_index {
|
||||
self.memory_blocks[block_index] = Some(new_memory_block);
|
||||
block_index
|
||||
} else {
|
||||
self.memory_blocks.push(Some(new_memory_block));
|
||||
self.memory_blocks.len() - 1
|
||||
};
|
||||
|
||||
self.active_general_blocks += 1;
|
||||
|
||||
let mem_block = self.memory_blocks[new_block_index]
|
||||
.as_mut()
|
||||
.ok_or_else(|| AllocationError::Internal("Memory block must be Some".into()))?;
|
||||
let allocation = mem_block.sub_allocator.allocate(
|
||||
size,
|
||||
alignment,
|
||||
allocation_type,
|
||||
granularity,
|
||||
desc.name,
|
||||
backtrace,
|
||||
);
|
||||
let (offset, chunk_id) = match allocation {
|
||||
Ok(value) => value,
|
||||
Err(err) => match err {
|
||||
AllocationError::OutOfMemory => {
|
||||
return Err(AllocationError::Internal(
|
||||
"Allocation that must succeed failed. This is a bug in the allocator."
|
||||
.into(),
|
||||
))
|
||||
}
|
||||
_ => return Err(err),
|
||||
},
|
||||
};
|
||||
|
||||
let mapped_ptr = if let Some(SendSyncPtr(mapped_ptr)) = mem_block.mapped_ptr {
|
||||
let offset_ptr = unsafe { mapped_ptr.as_ptr().add(offset as usize) };
|
||||
std::ptr::NonNull::new(offset_ptr).map(SendSyncPtr)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Allocation {
|
||||
chunk_id: Some(chunk_id),
|
||||
offset,
|
||||
size,
|
||||
memory_block_index: new_block_index,
|
||||
memory_type_index: self.memory_type_index,
|
||||
device_memory: mem_block.device_memory,
|
||||
mapped_ptr,
|
||||
name: Some(desc.name.into()),
|
||||
dedicated_allocation: false,
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
fn free(&mut self, allocation: Allocation, device: &ash::Device) -> Result<()> {
|
||||
let block_idx = allocation.memory_block_index;
|
||||
|
||||
let mem_block = self.memory_blocks[block_idx]
|
||||
.as_mut()
|
||||
.ok_or_else(|| AllocationError::Internal("Memory block must be Some.".into()))?;
|
||||
|
||||
mem_block.sub_allocator.free(allocation.chunk_id)?;
|
||||
|
||||
if mem_block.sub_allocator.is_empty() {
|
||||
if mem_block.sub_allocator.supports_general_allocations() {
|
||||
if self.active_general_blocks > 1 {
|
||||
let block = self.memory_blocks[block_idx].take();
|
||||
let block = block.ok_or_else(|| {
|
||||
AllocationError::Internal("Memory block must be Some.".into())
|
||||
})?;
|
||||
block.destroy(device);
|
||||
|
||||
self.active_general_blocks -= 1;
|
||||
}
|
||||
} else {
|
||||
let block = self.memory_blocks[block_idx].take();
|
||||
let block = block.ok_or_else(|| {
|
||||
AllocationError::Internal("Memory block must be Some.".into())
|
||||
})?;
|
||||
block.destroy(device);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Allocator {
|
||||
pub(crate) memory_types: Vec<MemoryType>,
|
||||
pub(crate) memory_heaps: Vec<vk::MemoryHeap>,
|
||||
device: ash::Device,
|
||||
pub(crate) buffer_image_granularity: u64,
|
||||
pub(crate) debug_settings: AllocatorDebugSettings,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Allocator {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut allocation_report = vec![];
|
||||
let mut total_reserved_size_in_bytes = 0;
|
||||
|
||||
for memory_type in &self.memory_types {
|
||||
for block in memory_type.memory_blocks.iter().flatten() {
|
||||
total_reserved_size_in_bytes += block.size;
|
||||
allocation_report.extend(block.sub_allocator.report_allocations())
|
||||
}
|
||||
}
|
||||
|
||||
let total_used_size_in_bytes = allocation_report.iter().map(|report| report.size).sum();
|
||||
|
||||
allocation_report.sort_by_key(|alloc| std::cmp::Reverse(alloc.size));
|
||||
|
||||
writeln!(
|
||||
f,
|
||||
"================================================================",
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
"ALLOCATION BREAKDOWN ({} / {})",
|
||||
fmt_bytes(total_used_size_in_bytes),
|
||||
fmt_bytes(total_reserved_size_in_bytes),
|
||||
)?;
|
||||
|
||||
let max_num_allocations_to_print = f.precision().map_or(usize::MAX, |n| n);
|
||||
for (idx, alloc) in allocation_report.iter().enumerate() {
|
||||
if idx >= max_num_allocations_to_print {
|
||||
break;
|
||||
}
|
||||
|
||||
writeln!(
|
||||
f,
|
||||
"{:max_len$.max_len$}\t- {}",
|
||||
alloc.name,
|
||||
fmt_bytes(alloc.size),
|
||||
max_len = allocator::VISUALIZER_TABLE_MAX_ENTRY_NAME_LEN,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Allocator {
|
||||
pub fn new(desc: &AllocatorCreateDesc) -> Result<Self> {
|
||||
if desc.physical_device == ash::vk::PhysicalDevice::null() {
|
||||
return Err(AllocationError::InvalidAllocatorCreateDesc(
|
||||
"AllocatorCreateDesc field `physical_device` is null.".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let mem_props = unsafe {
|
||||
desc.instance
|
||||
.get_physical_device_memory_properties(desc.physical_device)
|
||||
};
|
||||
|
||||
let memory_types = &mem_props.memory_types[..mem_props.memory_type_count as _];
|
||||
let memory_heaps = mem_props.memory_heaps[..mem_props.memory_heap_count as _].to_vec();
|
||||
|
||||
if desc.debug_settings.log_memory_information {
|
||||
debug!("memory type count: {}", mem_props.memory_type_count);
|
||||
debug!("memory heap count: {}", mem_props.memory_heap_count);
|
||||
|
||||
for (i, mem_type) in memory_types.iter().enumerate() {
|
||||
let flags = mem_type.property_flags;
|
||||
debug!(
|
||||
"memory type[{}]: prop flags: 0x{:x}, heap[{}]",
|
||||
i,
|
||||
flags.as_raw(),
|
||||
mem_type.heap_index,
|
||||
);
|
||||
}
|
||||
for (i, heap) in memory_heaps.iter().enumerate() {
|
||||
debug!(
|
||||
"heap[{}] flags: 0x{:x}, size: {} MiB",
|
||||
i,
|
||||
heap.flags.as_raw(),
|
||||
heap.size / (1024 * 1024)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let memory_types = memory_types
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, mem_type)| MemoryType {
|
||||
memory_blocks: Vec::default(),
|
||||
memory_properties: mem_type.property_flags,
|
||||
memory_type_index: i,
|
||||
heap_index: mem_type.heap_index as usize,
|
||||
mappable: mem_type
|
||||
.property_flags
|
||||
.contains(vk::MemoryPropertyFlags::HOST_VISIBLE),
|
||||
active_general_blocks: 0,
|
||||
buffer_device_address: desc.buffer_device_address,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let physical_device_properties = unsafe {
|
||||
desc.instance
|
||||
.get_physical_device_properties(desc.physical_device)
|
||||
};
|
||||
|
||||
let granularity = physical_device_properties.limits.buffer_image_granularity;
|
||||
|
||||
Ok(Self {
|
||||
memory_types,
|
||||
memory_heaps,
|
||||
device: desc.device.clone(),
|
||||
buffer_image_granularity: granularity,
|
||||
debug_settings: desc.debug_settings,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn allocate(&mut self, desc: &AllocationCreateDesc<'_>) -> Result<Allocation> {
|
||||
let size = desc.requirements.size;
|
||||
let alignment = desc.requirements.alignment;
|
||||
|
||||
let backtrace = if self.debug_settings.store_stack_traces {
|
||||
Some(backtrace::Backtrace::new_unresolved())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if self.debug_settings.log_allocations {
|
||||
debug!(
|
||||
"Allocating `{}` of {} bytes with an alignment of {}.",
|
||||
&desc.name, size, alignment
|
||||
);
|
||||
if self.debug_settings.log_stack_traces {
|
||||
let backtrace = backtrace::Backtrace::new();
|
||||
debug!("Allocation stack trace: {:?}", backtrace);
|
||||
}
|
||||
}
|
||||
|
||||
if size == 0 || !alignment.is_power_of_two() {
|
||||
return Err(AllocationError::InvalidAllocationCreateDesc);
|
||||
}
|
||||
|
||||
let mem_loc_preferred_bits = match desc.location {
|
||||
MemoryLocation::GpuOnly => vk::MemoryPropertyFlags::DEVICE_LOCAL,
|
||||
MemoryLocation::CpuToGpu => {
|
||||
vk::MemoryPropertyFlags::HOST_VISIBLE
|
||||
| vk::MemoryPropertyFlags::HOST_COHERENT
|
||||
| vk::MemoryPropertyFlags::DEVICE_LOCAL
|
||||
}
|
||||
MemoryLocation::GpuToCpu => {
|
||||
vk::MemoryPropertyFlags::HOST_VISIBLE
|
||||
| vk::MemoryPropertyFlags::HOST_COHERENT
|
||||
| vk::MemoryPropertyFlags::HOST_CACHED
|
||||
}
|
||||
MemoryLocation::Unknown => vk::MemoryPropertyFlags::empty(),
|
||||
};
|
||||
let mut memory_type_index_opt =
|
||||
self.find_memorytype_index(&desc.requirements, mem_loc_preferred_bits);
|
||||
|
||||
if memory_type_index_opt.is_none() {
|
||||
let mem_loc_required_bits = match desc.location {
|
||||
MemoryLocation::GpuOnly => vk::MemoryPropertyFlags::DEVICE_LOCAL,
|
||||
MemoryLocation::CpuToGpu | MemoryLocation::GpuToCpu => {
|
||||
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT
|
||||
}
|
||||
MemoryLocation::Unknown => vk::MemoryPropertyFlags::empty(),
|
||||
};
|
||||
|
||||
memory_type_index_opt =
|
||||
self.find_memorytype_index(&desc.requirements, mem_loc_required_bits);
|
||||
}
|
||||
|
||||
let memory_type_index = match memory_type_index_opt {
|
||||
Some(x) => x as usize,
|
||||
None => return Err(AllocationError::NoCompatibleMemoryTypeFound),
|
||||
};
|
||||
|
||||
//Do not try to create a block if the heap is smaller than the required size (avoids validation warnings).
|
||||
let memory_type = &mut self.memory_types[memory_type_index];
|
||||
let allocation = if size > self.memory_heaps[memory_type.heap_index].size {
|
||||
Err(AllocationError::OutOfMemory)
|
||||
} else {
|
||||
memory_type.allocate(
|
||||
&self.device,
|
||||
desc,
|
||||
self.buffer_image_granularity,
|
||||
backtrace.clone(),
|
||||
)
|
||||
};
|
||||
|
||||
if desc.location == MemoryLocation::CpuToGpu {
|
||||
if allocation.is_err() {
|
||||
let mem_loc_preferred_bits =
|
||||
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT;
|
||||
|
||||
let memory_type_index_opt =
|
||||
self.find_memorytype_index(&desc.requirements, mem_loc_preferred_bits);
|
||||
|
||||
let memory_type_index = match memory_type_index_opt {
|
||||
Some(x) => x as usize,
|
||||
None => return Err(AllocationError::NoCompatibleMemoryTypeFound),
|
||||
};
|
||||
|
||||
self.memory_types[memory_type_index].allocate(
|
||||
&self.device,
|
||||
desc,
|
||||
self.buffer_image_granularity,
|
||||
backtrace,
|
||||
)
|
||||
} else {
|
||||
allocation
|
||||
}
|
||||
} else {
|
||||
allocation
|
||||
}
|
||||
}
|
||||
|
||||
pub fn free(&mut self, allocation: Allocation) -> Result<()> {
|
||||
if self.debug_settings.log_frees {
|
||||
let name = allocation.name.as_deref().unwrap_or("<null>");
|
||||
debug!("Freeing `{}`.", name);
|
||||
if self.debug_settings.log_stack_traces {
|
||||
let backtrace = format!("{:?}", backtrace::Backtrace::new());
|
||||
debug!("Free stack trace: {}", backtrace);
|
||||
}
|
||||
}
|
||||
|
||||
if allocation.is_null() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.memory_types[allocation.memory_type_index].free(allocation, &self.device)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn rename_allocation(&mut self, allocation: &mut Allocation, name: &str) -> Result<()> {
|
||||
allocation.name = Some(name.into());
|
||||
|
||||
if allocation.is_null() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mem_type = &mut self.memory_types[allocation.memory_type_index];
|
||||
let mem_block = mem_type.memory_blocks[allocation.memory_block_index]
|
||||
.as_mut()
|
||||
.ok_or_else(|| AllocationError::Internal("Memory block must be Some.".into()))?;
|
||||
|
||||
mem_block
|
||||
.sub_allocator
|
||||
.rename_allocation(allocation.chunk_id, name)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn report_memory_leaks(&self, log_level: Level) {
|
||||
for (mem_type_i, mem_type) in self.memory_types.iter().enumerate() {
|
||||
for (block_i, mem_block) in mem_type.memory_blocks.iter().enumerate() {
|
||||
if let Some(mem_block) = mem_block {
|
||||
mem_block
|
||||
.sub_allocator
|
||||
.report_memory_leaks(log_level, mem_type_i, block_i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_memorytype_index(
|
||||
&self,
|
||||
memory_req: &vk::MemoryRequirements,
|
||||
flags: vk::MemoryPropertyFlags,
|
||||
) -> Option<u32> {
|
||||
self.memory_types
|
||||
.iter()
|
||||
.find(|memory_type| {
|
||||
(1 << memory_type.memory_type_index) & memory_req.memory_type_bits != 0
|
||||
&& memory_type.memory_properties.contains(flags)
|
||||
})
|
||||
.map(|memory_type| memory_type.memory_type_index as _)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Allocator {
|
||||
fn drop(&mut self) {
|
||||
if self.debug_settings.log_leaks_on_shutdown {
|
||||
self.report_memory_leaks(Level::Warn);
|
||||
}
|
||||
|
||||
// Free all remaining memory blocks
|
||||
for mem_type in self.memory_types.iter_mut() {
|
||||
for mem_block in mem_type.memory_blocks.iter_mut() {
|
||||
let block = mem_block.take();
|
||||
if let Some(block) = block {
|
||||
block.destroy(&self.device);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
394
third-party/vendor/gpu-allocator/src/vulkan/visualizer.rs
vendored
Normal file
394
third-party/vendor/gpu-allocator/src/vulkan/visualizer.rs
vendored
Normal file
|
|
@ -0,0 +1,394 @@
|
|||
#![allow(clippy::new_without_default)]
|
||||
|
||||
use super::Allocator;
|
||||
use crate::allocator::{fmt_bytes, resolve_backtrace};
|
||||
use crate::visualizer::ColorScheme;
|
||||
use log::error;
|
||||
|
||||
// Default value for block visualizer granularity.
|
||||
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) {
|
||||
// Copy here to workaround the borrow checker.
|
||||
let focus_opt = self.focus;
|
||||
// Keep track of a list of windows that are signaled by imgui to be closed.
|
||||
let mut windows_to_close = Vec::default();
|
||||
// Draw each window.
|
||||
let color_scheme = &self.color_scheme;
|
||||
for (window_i, window) in self.selected_blocks.iter_mut().enumerate() {
|
||||
// Determine if this window needs focus.
|
||||
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);
|
||||
}
|
||||
// Slider for changing the 'zoom' level of the visualizer.
|
||||
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);
|
||||
|
||||
// Imgui can actually modify this number to be out of bounds, so we will clamp manually.
|
||||
window.bytes_per_unit = window
|
||||
.bytes_per_unit
|
||||
.clamp(BYTES_PER_UNIT_MIN, BYTES_PER_UNIT_MAX);
|
||||
|
||||
// Draw the visualization in a child window.
|
||||
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 imgui signalled to close the window, add it to the list of windows to close.
|
||||
if !is_open {
|
||||
windows_to_close.push(window_i);
|
||||
}
|
||||
}
|
||||
//
|
||||
// Clean-up
|
||||
//
|
||||
// Close windows.
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Reset focus.
|
||||
self.focus = None;
|
||||
}
|
||||
|
||||
/// Renders imgui widgets.
|
||||
///
|
||||
/// The [`Option<&mut bool>`] can be used control and track changes to the opened/closed status of the widget.
|
||||
/// Pass [`None`] if no control and readback information is required. This will always render the widget.
|
||||
/// When passing `Some(&mut bool)`:
|
||||
/// - If [`false`], the widget won't be drawn.
|
||||
/// - If [`true`], the widget will be drawn and an (X) closing button will be added to the widget bar.
|
||||
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));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue