Fix #108792: Ensure Metal buffers correctly freed on exit #108940
@ -107,6 +107,11 @@ class MTLUniformBuf;
|
||||
/* MTLBuffer allocation wrapper. */
|
||||
class MTLBuffer {
|
||||
|
||||
public:
|
||||
/* NOTE: ListBase API is not used due to cutsom destructor operation required to release
|
||||
|
||||
* Metal objective C buffer resource. */
|
||||
gpu::MTLBuffer *next, *prev;
|
||||
|
||||
private:
|
||||
/* Metal resource. */
|
||||
id<MTLBuffer> metal_buffer_;
|
||||
@ -177,6 +182,8 @@ class MTLBuffer {
|
||||
|
||||
/* Safety check to ensure buffers are not used after free. */
|
||||
void debug_ensure_used();
|
||||
|
||||
MEM_CXX_CLASS_ALLOC_FUNCS("MTLBuffer");
|
||||
};
|
||||
|
||||
/* View into part of an MTLBuffer. */
|
||||
@ -333,6 +340,8 @@ class MTLSafeFreeList {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MEM_CXX_CLASS_ALLOC_FUNCS("MTLSafeFreeList");
|
||||
};
|
||||
|
||||
/* MTLBuffer pools. */
|
||||
@ -362,7 +371,7 @@ class MTLBufferPool {
|
||||
#endif
|
||||
|
||||
/* Metal resources. */
|
||||
bool ensure_initialised_ = false;
|
||||
bool initialized_ = false;
|
||||
id<MTLDevice> device_ = nil;
|
||||
|
||||
/* The buffer selection aims to pick a buffer which meets the minimum size requirements.
|
||||
@ -389,7 +398,10 @@ class MTLBufferPool {
|
||||
|
||||
std::mutex buffer_pool_lock_;
|
||||
blender::Map<MTLBufferResourceOptions, MTLBufferPoolOrderedList *> buffer_pools_;
|
||||
blender::Vector<gpu::MTLBuffer *> allocations_;
|
||||
|
||||
/* Linked list to track all existing allocations. Prioritizing fast insert/deletion. */
|
||||
gpu::MTLBuffer *allocations_list_base_;
|
||||
uint allocations_list_size_;
|
||||
|
||||
/* Maintain a queue of all MTLSafeFreeList's that have been released
|
||||
* by the GPU and are ready to have their buffers re-inserted into the
|
||||
@ -432,6 +444,11 @@ class MTLBufferPool {
|
||||
void ensure_buffer_pool(MTLResourceOptions options);
|
||||
void insert_buffer_into_pool(MTLResourceOptions options, gpu::MTLBuffer *buffer);
|
||||
void free();
|
||||
|
||||
/* Allocations list. */
|
||||
void allocations_list_insert(gpu::MTLBuffer *buffer);
|
||||
void allocations_list_delete(gpu::MTLBuffer *buffer);
|
||||
void allocations_list_delete_all();
|
||||
};
|
||||
|
||||
/* Scratch buffers are circular-buffers used for temporary data within the current frame.
|
||||
@ -492,6 +509,8 @@ class MTLScratchBufferManager {
|
||||
* This call will perform a partial flush of the buffer starting from
|
||||
* the last offset the data was flushed from, to the current offset. */
|
||||
void flush_active_scratch_buffer();
|
||||
|
||||
MEM_CXX_CLASS_ALLOC_FUNCS("MTLBufferPool");
|
||||
};
|
||||
|
||||
/** \} */
|
||||
|
@ -25,9 +25,9 @@ namespace blender::gpu {
|
||||
|
||||
void MTLBufferPool::init(id<MTLDevice> mtl_device)
|
||||
{
|
||||
if (!ensure_initialised_) {
|
||||
if (!initialized_) {
|
||||
BLI_assert(mtl_device);
|
||||
ensure_initialised_ = true;
|
||||
initialized_ = true;
|
||||
device_ = mtl_device;
|
||||
|
||||
#if MTL_DEBUG_MEMORY_STATISTICS == 1
|
||||
@ -39,6 +39,10 @@ void MTLBufferPool::init(id<MTLDevice> mtl_device)
|
||||
/* Track pool allocation size. */
|
||||
allocations_in_pool_ = 0;
|
||||
|
||||
/* Live allocations list. */
|
||||
allocations_list_base_ = nullptr;
|
||||
allocations_list_size_ = 0;
|
||||
|
||||
/* Free pools -- Create initial safe free pool */
|
||||
BLI_assert(current_free_list_ == nullptr);
|
||||
this->begin_new_safe_list();
|
||||
@ -53,17 +57,29 @@ MTLBufferPool::~MTLBufferPool()
|
||||
void MTLBufferPool::free()
|
||||
{
|
||||
buffer_pool_lock_.lock();
|
||||
for (auto buffer : allocations_) {
|
||||
BLI_assert(buffer);
|
||||
delete buffer;
|
||||
}
|
||||
allocations_.clear();
|
||||
|
||||
/* Delete all existing allocations. */
|
||||
allocations_list_delete_all();
|
||||
|
||||
/* Release safe free lists. */
|
||||
for (int safe_pool_free_index = 0; safe_pool_free_index < completed_safelist_queue_.size();
|
||||
safe_pool_free_index++)
|
||||
{
|
||||
delete completed_safelist_queue_[safe_pool_free_index];
|
||||
}
|
||||
completed_safelist_queue_.clear();
|
||||
if (current_free_list_ != nullptr) {
|
||||
delete current_free_list_;
|
||||
current_free_list_ = nullptr;
|
||||
}
|
||||
|
||||
/* Clear and release memory pools. */
|
||||
for (std::multiset<blender::gpu::MTLBufferHandle, blender::gpu::CompareMTLBuffer> *buffer_pool :
|
||||
buffer_pools_.values())
|
||||
{
|
||||
delete buffer_pool;
|
||||
}
|
||||
|
||||
buffer_pools_.clear();
|
||||
buffer_pool_lock_.unlock();
|
||||
}
|
||||
@ -155,10 +171,7 @@ gpu::MTLBuffer *MTLBufferPool::allocate_aligned(uint64_t size,
|
||||
new_buffer = new gpu::MTLBuffer(device_, size, options, alignment);
|
||||
|
||||
/* Track allocation in context. */
|
||||
allocations_.append(new_buffer);
|
||||
#if MTL_DEBUG_MEMORY_STATISTICS == 1
|
||||
total_allocation_bytes_ += aligned_alloc_size;
|
||||
#endif
|
||||
allocations_list_insert(new_buffer);
|
||||
}
|
||||
else {
|
||||
/* Re-use suitable buffer. */
|
||||
@ -289,6 +302,7 @@ void MTLBufferPool::update_memory_pools()
|
||||
* animation.
|
||||
* If memory is continually used, then we do not want to free this memory as it will be
|
||||
* re-allocated during a short time period. */
|
||||
|
||||
const time_t time_now = std::time(nullptr);
|
||||
for (auto buffer_pool_list : buffer_pools_.items()) {
|
||||
MTLBufferPoolOrderedList *pool_allocations = buffer_pool_list.value;
|
||||
@ -323,12 +337,13 @@ void MTLBufferPool::update_memory_pools()
|
||||
|
||||
if (time_passed > deletion_time_threshold_s) {
|
||||
|
||||
/* Delete allocation. */
|
||||
delete handle.buffer;
|
||||
/* Remove buffer from global allocations list and release resource. */
|
||||
allocations_list_delete(handle.buffer);
|
||||
|
||||
/* Remove buffer from pool and update pool statistics. */
|
||||
pool_iterator = pool_allocations->erase(pool_iterator);
|
||||
allocations_in_pool_ -= handle.buffer_size;
|
||||
#if MTL_DEBUG_MEMORY_STATISTICS == 1
|
||||
total_allocation_bytes_ -= handle.buffer_size;
|
||||
buffers_in_pool_--;
|
||||
#endif
|
||||
continue;
|
||||
@ -343,7 +358,7 @@ void MTLBufferPool::update_memory_pools()
|
||||
|
||||
uint framealloc = (uint)per_frame_allocation_count_;
|
||||
printf(" Allocations in frame: %u\n", framealloc);
|
||||
printf(" Total Buffers allocated: %u\n", (uint)allocations_.size());
|
||||
printf(" Total Buffers allocated: %u\n", allocations_list_size_);
|
||||
printf(" Total Memory allocated: %u MB\n", (uint)total_allocation_bytes_ / (1024 * 1024));
|
||||
|
||||
uint allocs = (uint)(allocations_in_pool_) / 1024 / 2024;
|
||||
@ -453,6 +468,80 @@ void MTLBufferPool::insert_buffer_into_pool(MTLResourceOptions options, gpu::MTL
|
||||
#endif
|
||||
}
|
||||
|
||||
void MTLBufferPool::allocations_list_insert(gpu::MTLBuffer *buffer)
|
||||
{
|
||||
/* NOTE: Function should only be called while buffer_pool_lock_ is acquired. */
|
||||
BLI_assert(initialized_);
|
||||
BLI_assert(buffer != nullptr);
|
||||
|
||||
/* Insert buffer at base of allocations list. */
|
||||
gpu::MTLBuffer *current_head = allocations_list_base_;
|
||||
buffer->next = current_head;
|
||||
buffer->prev = nullptr;
|
||||
|
||||
if (current_head != nullptr) {
|
||||
current_head->prev = buffer;
|
||||
}
|
||||
|
||||
allocations_list_base_ = buffer;
|
||||
allocations_list_size_++;
|
||||
|
||||
#if MTL_DEBUG_MEMORY_STATISTICS == 1
|
||||
total_allocation_bytes_ += buffer->get_size();
|
||||
#endif
|
||||
}
|
||||
|
||||
void MTLBufferPool::allocations_list_delete(gpu::MTLBuffer *buffer)
|
||||
{
|
||||
/* NOTE: Function should only be called while buffer_pool_lock_ is acquired. */
|
||||
/* Remove a buffer link in the allocations chain. */
|
||||
BLI_assert(initialized_);
|
||||
BLI_assert(buffer != nullptr);
|
||||
BLI_assert(allocations_list_size_ >= 1);
|
||||
|
||||
gpu::MTLBuffer *next = buffer->next;
|
||||
gpu::MTLBuffer *prev = buffer->prev;
|
||||
|
||||
if (prev != nullptr) {
|
||||
BLI_assert(prev->next == buffer);
|
||||
prev->next = next;
|
||||
}
|
||||
|
||||
if (next != nullptr) {
|
||||
BLI_assert(next->prev == buffer);
|
||||
next->prev = prev;
|
||||
}
|
||||
|
||||
if (allocations_list_base_ == buffer) {
|
||||
allocations_list_base_ = next;
|
||||
BLI_assert(prev == nullptr);
|
||||
}
|
||||
allocations_list_size_--;
|
||||
Sergey Sharybin
commented
`allocations_list_size_` is unsigned, so the check is always true. You can do something like
```
BLI_assert(allocations_list_size_ >= 1);
allocations_list_size_--;
```
|
||||
|
||||
#if MTL_DEBUG_MEMORY_STATISTICS == 1
|
||||
total_allocation_bytes_ -= buffer->get_size();
|
||||
#endif
|
||||
|
||||
/* Delete buffer. */
|
||||
delete buffer;
|
||||
}
|
||||
|
||||
void MTLBufferPool::allocations_list_delete_all()
|
||||
{
|
||||
gpu::MTLBuffer *current = allocations_list_base_;
|
||||
while (current != nullptr) {
|
||||
gpu::MTLBuffer *next = current->next;
|
||||
delete current;
|
||||
current = next;
|
||||
}
|
||||
allocations_list_size_ = 0;
|
||||
allocations_list_base_ = nullptr;
|
||||
|
||||
#if MTL_DEBUG_MEMORY_STATISTICS == 1
|
||||
total_allocation_bytes_ = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
MTLSafeFreeList::MTLSafeFreeList()
|
||||
{
|
||||
reference_count_ = 1;
|
||||
@ -565,6 +654,9 @@ MTLBuffer::MTLBuffer(id<MTLDevice> mtl_device,
|
||||
else {
|
||||
data_ = nullptr;
|
||||
}
|
||||
|
||||
/* Linked resources. */
|
||||
next = prev = nullptr;
|
||||
}
|
||||
|
||||
MTLBuffer::MTLBuffer(id<MTLBuffer> external_buffer)
|
||||
@ -584,6 +676,9 @@ MTLBuffer::MTLBuffer(id<MTLBuffer> external_buffer)
|
||||
this->set_usage_size(size_);
|
||||
data_ = [metal_buffer_ contents];
|
||||
in_use_ = true;
|
||||
|
||||
/* Linked resources. */
|
||||
next = prev = nullptr;
|
||||
}
|
||||
|
||||
gpu::MTLBuffer::~MTLBuffer()
|
||||
|
Loading…
Reference in New Issue
Block a user
use
ListBase
,ListBaseWrapperTemplate
and the functions inBLI_listbase.h
. Some casts might be needed, but it reduces code-duplication.Thanks, have modified the implementation. Admittedly not 100% sure if there are any catches with ListBase.
I have attempted to utilise both
BLI_freelinkN
for deletion andBLI_freelistN
for complete freeing of the list, but a little unsure how the builtinMEM_freeN
functions work alongside C++ new/delete, and with constructors.i.e. is it valid for gpu::MTLBuffer to be created using
new gpu::MTLBuffer(params...)
, and freed usingMEM_freeN(buffer)
?It appears to be releasing the resources correctly, as it looked like it was sufficient to use
MEM_freeN
based functions, if you haveMEM_CXX_CLASS_ALLOC_FUNCS
specified, however, it would be very helpful if someone could confirm whether the new implementation in the latest commit is correct.Cheers!