diff --git a/source/blender/gpu/metal/mtl_memory.hh b/source/blender/gpu/metal/mtl_memory.hh index e7da1a6d4fd..0142f5d248a 100644 --- a/source/blender/gpu/metal/mtl_memory.hh +++ b/source/blender/gpu/metal/mtl_memory.hh @@ -3,6 +3,7 @@ #pragma once #include +#include #include #include #include @@ -231,17 +232,20 @@ class MTLCircularBuffer { struct MTLBufferHandle { gpu::MTLBuffer *buffer; uint64_t buffer_size; + time_t insert_time; inline MTLBufferHandle(gpu::MTLBuffer *buf) { this->buffer = buf; this->buffer_size = this->buffer->get_size(); + this->insert_time = std::time(nullptr); } inline MTLBufferHandle(uint64_t compare_size) { this->buffer = nullptr; this->buffer_size = compare_size; + this->insert_time = 0; } }; @@ -354,7 +358,6 @@ class MTLBufferPool { /* Debug statistics. */ std::atomic per_frame_allocation_count_; - std::atomic allocations_in_pool_; std::atomic buffers_in_pool_; #endif @@ -399,6 +402,7 @@ class MTLBufferPool { /* MTLBuffer::free() can be called from separate threads, due to usage within animation * system/worker threads. */ std::atomic current_free_list_; + std::atomic allocations_in_pool_; public: void init(id device); diff --git a/source/blender/gpu/metal/mtl_memory.mm b/source/blender/gpu/metal/mtl_memory.mm index ca5abec959e..73b167a9ce3 100644 --- a/source/blender/gpu/metal/mtl_memory.mm +++ b/source/blender/gpu/metal/mtl_memory.mm @@ -11,6 +11,12 @@ using namespace blender; using namespace blender::gpu; +/* Memory size in bytes macros, used as pool flushing frequency thresholds. */ +#define MEMORY_SIZE_2GB 2147483648LL +#define MEMORY_SIZE_1GB 1073741824LL +#define MEMORY_SIZE_512MB 536870912LL +#define MEMORY_SIZE_256MB 268435456LL + namespace blender::gpu { /* -------------------------------------------------------------------- */ @@ -28,9 +34,10 @@ void MTLBufferPool::init(id mtl_device) /* Debug statistics. */ total_allocation_bytes_ = 0; per_frame_allocation_count_ = 0; - allocations_in_pool_ = 0; buffers_in_pool_ = 0; #endif + /* Track pool allocation size. */ + allocations_in_pool_ = 0; /* Free pools -- Create initial safe free pool */ BLI_assert(current_free_list_ == nullptr); @@ -159,11 +166,13 @@ gpu::MTLBuffer *MTLBufferPool::allocate_aligned(uint64_t size, #if MTL_DEBUG_MEMORY_STATISTICS == 1 /* Debug. */ - allocations_in_pool_ -= new_buffer->get_size(); buffers_in_pool_--; - BLI_assert(allocations_in_pool_ >= 0); #endif + /* Decrement size of pool. */ + BLI_assert(allocations_in_pool_ >= 0); + allocations_in_pool_ -= new_buffer->get_size(); + /* Ensure buffer memory is correctly backed. */ BLI_assert(new_buffer->get_metal_buffer()); } @@ -275,6 +284,59 @@ void MTLBufferPool::update_memory_pools() } } + /* Release memory allocations which have not been used in a while. + * This ensures memory pressure stays low for scenes with compounding complexity during + * 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; + MTLBufferPoolOrderedList::iterator pool_iterator = pool_allocations->begin(); + while (pool_iterator != pool_allocations->end()) { + + const MTLBufferHandle handle = *pool_iterator; + const time_t time_passed = time_now - handle.insert_time; + + /* Free allocations if a certain amount of time has passed. + * Deletion frequency depends on how much excess memory + * the application is using. */ + time_t deletion_time_threshold_s = 600; + /* Spare pool memory >= 2GB. */ + if (allocations_in_pool_ >= MEMORY_SIZE_2GB) { + deletion_time_threshold_s = 2; + } + else + /* Spare pool memory >= 1GB. */ + if (allocations_in_pool_ >= MEMORY_SIZE_1GB) + { + deletion_time_threshold_s = 4; + } + /* Spare pool memory >= 512MB.*/ + else if (allocations_in_pool_ >= MEMORY_SIZE_512MB) { + deletion_time_threshold_s = 15; + } + /* Spare pool memory >= 256MB. */ + else if (allocations_in_pool_ >= MEMORY_SIZE_256MB) { + deletion_time_threshold_s = 60; + } + + if (time_passed > deletion_time_threshold_s) { + + /* Delete allocation. */ + delete handle.buffer; + 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; + } + pool_iterator++; + } + } + #if MTL_DEBUG_MEMORY_STATISTICS == 1 printf("--- Allocation Stats ---\n"); printf(" Num buffers processed in pool (this frame): %u\n", num_buffers_added); @@ -383,10 +445,10 @@ void MTLBufferPool::insert_buffer_into_pool(MTLResourceOptions options, gpu::MTL std::multiset *pool = buffer_pools_.lookup(options); pool->insert(MTLBufferHandle(buffer)); + allocations_in_pool_ += buffer->get_size(); #if MTL_DEBUG_MEMORY_STATISTICS == 1 /* Debug statistics. */ - allocations_in_pool_ += buffer->get_size(); buffers_in_pool_++; #endif }