@@ -19,6 +19,9 @@ std::vector<VulkanBuffer::PendingStagingCleanup> VulkanBuffer::s_pendingCleanup;
1919std::vector<VulkanBuffer::DeferredBufferDelete> VulkanBuffer::s_deferredDeletions;
2020uint64_t VulkanBuffer::s_frameNumber = 0 ;
2121
22+ // Buffer recycling pool
23+ std::vector<VulkanBuffer::PooledBuffer> VulkanBuffer::s_bufferPool;
24+
2225VulkanBuffer::VulkanBuffer (VkDevice device, VkPhysicalDevice physicalDevice,
2326 uint32_t size, BufferType type,
2427 VkCommandPool commandPool, VkQueue graphicsQueue)
@@ -221,11 +224,14 @@ void VulkanBuffer::Cleanup() {
221224 m_stagingMemory = VK_NULL_HANDLE ;
222225 }
223226 // Defer GPU buffer destruction — may still be referenced by in-flight
224- // command buffers. Will be cleaned up after fence wait.
227+ // command buffers. Will be recycled or cleaned up after fence wait.
225228 if (m_buffer != VK_NULL_HANDLE ) {
226- s_deferredDeletions.push_back ({m_buffer, m_memory, m_device, s_frameNumber});
229+ s_deferredDeletions.push_back ({m_buffer, m_memory, m_device,
230+ m_allocSize, m_usage, m_memoryTypeIndex,
231+ s_frameNumber});
227232 m_buffer = VK_NULL_HANDLE ;
228233 m_memory = VK_NULL_HANDLE ;
234+ m_allocSize = 0 ;
229235 }
230236
231237 bIsInitialized = false ;
@@ -265,6 +271,16 @@ void VulkanBuffer::CreateBuffer(VkDeviceSize size,
265271 VkMemoryPropertyFlags properties,
266272 VkBuffer& buffer,
267273 VkDeviceMemory& memory) {
274+ // Try to recycle a pooled buffer to avoid expensive vkAllocateMemory
275+ if (TryRecycleBuffer (size, usage, properties, buffer, memory)) {
276+ // Track the recycled buffer's info (keep existing alloc metadata)
277+ if (&buffer == &m_buffer) {
278+ m_usage = usage;
279+ // m_allocSize and m_memoryTypeIndex stay from pool
280+ }
281+ return ;
282+ }
283+
268284 VkBufferCreateInfo bufferInfo{};
269285 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO ;
270286 bufferInfo.size = size;
@@ -280,11 +296,12 @@ void VulkanBuffer::CreateBuffer(VkDeviceSize size,
280296 VkMemoryRequirements memRequirements;
281297 vkGetBufferMemoryRequirements (m_device, buffer, &memRequirements);
282298
299+ uint32_t memTypeIdx = FindMemoryType (memRequirements.memoryTypeBits , properties);
300+
283301 VkMemoryAllocateInfo allocInfo{};
284302 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO ;
285303 allocInfo.allocationSize = memRequirements.size ;
286- allocInfo.memoryTypeIndex =
287- FindMemoryType (memRequirements.memoryTypeBits , properties);
304+ allocInfo.memoryTypeIndex = memTypeIdx;
288305
289306 if (vkAllocateMemory (m_device, &allocInfo, nullptr , &memory) !=
290307 VK_SUCCESS ) {
@@ -293,6 +310,13 @@ void VulkanBuffer::CreateBuffer(VkDeviceSize size,
293310 }
294311
295312 vkBindBufferMemory (m_device, buffer, memory, 0 );
313+
314+ // Track allocation metadata for recycling
315+ if (&buffer == &m_buffer) {
316+ m_allocSize = memRequirements.size ;
317+ m_usage = usage;
318+ m_memoryTypeIndex = memTypeIdx;
319+ }
296320}
297321
298322void VulkanBuffer::CopyBuffer (VkBuffer srcBuffer, VkBuffer dstBuffer,
@@ -463,26 +487,75 @@ uint32_t VulkanBuffer::FindMemoryType(uint32_t typeFilter,
463487}
464488
465489void VulkanBuffer::ProcessDeferredDeletions (uint32_t maxFramesInFlight) {
490+ // Rate-limit processing to avoid vkFreeMemory spikes
491+ constexpr int MAX_PER_FRAME = 8 ;
492+ int processed = 0 ;
466493 auto it = s_deferredDeletions.begin ();
467- while (it != s_deferredDeletions.end ()) {
494+ while (it != s_deferredDeletions.end () && processed < MAX_PER_FRAME ) {
468495 if (s_frameNumber - it->frameNumber >= maxFramesInFlight) {
469- vkDestroyBuffer (it->device , it->buffer , nullptr );
470- if (it->memory != VK_NULL_HANDLE )
471- vkFreeMemory (it->device , it->memory , nullptr );
496+ // Recycle into pool if there's room, otherwise destroy
497+ if (s_bufferPool.size () < MAX_POOL_SIZE && it->allocSize > 0 ) {
498+ s_bufferPool.push_back ({it->buffer , it->memory , it->device ,
499+ it->allocSize , it->usage , it->memoryTypeIndex });
500+ } else {
501+ vkDestroyBuffer (it->device , it->buffer , nullptr );
502+ if (it->memory != VK_NULL_HANDLE )
503+ vkFreeMemory (it->device , it->memory , nullptr );
504+ }
472505 it = s_deferredDeletions.erase (it);
506+ ++processed;
473507 } else {
474508 ++it;
475509 }
476510 }
477511}
478512
513+ bool VulkanBuffer::TryRecycleBuffer (VkDeviceSize size,
514+ VkBufferUsageFlags usage,
515+ VkMemoryPropertyFlags properties,
516+ VkBuffer& buffer,
517+ VkDeviceMemory& memory) {
518+ // Find a pooled buffer with matching usage and sufficient size.
519+ // Prefer smallest fitting buffer to minimize waste.
520+ int bestIdx = -1 ;
521+ VkDeviceSize bestSize = UINT64_MAX ;
522+ for (size_t i = 0 ; i < s_bufferPool.size (); ++i) {
523+ auto & p = s_bufferPool[i];
524+ if (p.device == m_device && p.usage == usage &&
525+ p.allocSize >= size && p.allocSize < bestSize) {
526+ // Don't recycle if way too large (>4x waste)
527+ if (p.allocSize <= size * 4 ) {
528+ bestIdx = static_cast <int >(i);
529+ bestSize = p.allocSize ;
530+ }
531+ }
532+ }
533+ if (bestIdx >= 0 ) {
534+ buffer = s_bufferPool[bestIdx].buffer ;
535+ memory = s_bufferPool[bestIdx].memory ;
536+ // Remove from pool (swap with last for O(1))
537+ s_bufferPool[bestIdx] = s_bufferPool.back ();
538+ s_bufferPool.pop_back ();
539+ return true ;
540+ }
541+ return false ;
542+ }
543+
479544void VulkanBuffer::FlushAllDeferredDeletions () {
480545 for (auto & entry : s_deferredDeletions) {
481546 vkDestroyBuffer (entry.device , entry.buffer , nullptr );
482547 if (entry.memory != VK_NULL_HANDLE )
483548 vkFreeMemory (entry.device , entry.memory , nullptr );
484549 }
485550 s_deferredDeletions.clear ();
551+
552+ // Also drain the recycling pool
553+ for (auto & entry : s_bufferPool) {
554+ vkDestroyBuffer (entry.device , entry.buffer , nullptr );
555+ if (entry.memory != VK_NULL_HANDLE )
556+ vkFreeMemory (entry.device , entry.memory , nullptr );
557+ }
558+ s_bufferPool.clear ();
486559}
487560
488561} // namespace RenderEngine
0 commit comments