2#include <vulkan/vulkan.h>
3#define VMA_STATIC_VULKAN_FUNCTIONS 1
4#define VMA_DYNAMIC_VULKAN_FUNCTIONS 0
5#define VMA_IMPLEMENTATION
6#include <vma/vk_mem_alloc.h>
14namespace RenderEngine {
16VmaAllocator VulkanBuffer::s_allocator = VK_NULL_HANDLE;
19 VkPhysicalDevice physicalDevice,
21 if (s_allocator != VK_NULL_HANDLE)
return;
22 VmaAllocatorCreateInfo aci{};
23 aci.instance = instance;
24 aci.physicalDevice = physicalDevice;
26 aci.vulkanApiVersion = VK_API_VERSION_1_1;
27 if (vmaCreateAllocator(&aci, &s_allocator) != VK_SUCCESS) {
29 s_allocator = VK_NULL_HANDLE;
34 if (s_allocator == VK_NULL_HANDLE)
return;
35 vmaDestroyAllocator(s_allocator);
36 s_allocator = VK_NULL_HANDLE;
40bool VulkanBuffer::s_batchingEnabled =
false;
41bool VulkanBuffer::s_batchActive =
false;
42VkCommandBuffer VulkanBuffer::s_batchCommandBuffer = VK_NULL_HANDLE;
43VkDevice VulkanBuffer::s_batchDevice = VK_NULL_HANDLE;
44VkCommandPool VulkanBuffer::s_batchCommandPool = VK_NULL_HANDLE;
45VkQueue VulkanBuffer::s_batchQueue = VK_NULL_HANDLE;
46std::vector<VulkanBuffer::PendingStagingCleanup> VulkanBuffer::s_pendingCleanup;
49std::vector<VulkanBuffer::DeferredBufferDelete> VulkanBuffer::s_deferredDeletions;
50uint64_t VulkanBuffer::s_frameNumber = 0;
73 return (usage & (VK_BUFFER_USAGE_VERTEX_BUFFER_BIT |
74 VK_BUFFER_USAGE_INDEX_BUFFER_BIT)) != 0;
77static bool IsRecyclable(VkBufferUsageFlags usage, VkMemoryPropertyFlags props) {
78 return (props & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) &&
83std::vector<VulkanBuffer::PooledBuffer> VulkanBuffer::s_bufferPool;
84VkDeviceSize VulkanBuffer::s_poolBytes = 0;
87VkDeviceSize VulkanBuffer::s_totalAllocatedBytes = 0;
88VkPhysicalDevice VulkanBuffer::s_physicalDeviceGlobal = VK_NULL_HANDLE;
89VkDeviceSize VulkanBuffer::s_perTypeBytes[VK_MAX_MEMORY_TYPES] = {};
90bool VulkanBuffer::s_memTypeIsDeviceLocal[VK_MAX_MEMORY_TYPES] = {};
91uint32_t VulkanBuffer::s_memTypeCount = 0;
96 VkCommandPool commandPool, VkQueue graphicsQueue)
98 m_physicalDevice(physicalDevice),
99 m_commandPool(commandPool),
100 m_graphicsQueue(graphicsQueue) {
110 if (
Size == 0)
return false;
114 VkDeviceSize stagingAllocSize = 0;
115 uint32_t stagingMemTypeIdx = 0;
118 VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
119 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
120 VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
121 m_stagingBuffer, m_stagingMemory, &stagingAllocSize,
123 if (m_stagingBuffer == VK_NULL_HANDLE)
return false;
128 vmaMapMemory(s_allocator, m_stagingMemory, &mapped);
129 memcpy(mapped, data,
Size);
130 vmaUnmapMemory(s_allocator, m_stagingMemory);
136 VK_BUFFER_USAGE_TRANSFER_DST_BIT |
137 VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
138 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
141 if (m_buffer == VK_NULL_HANDLE) {
142 if (m_stagingBuffer != VK_NULL_HANDLE) {
143 vmaDestroyBuffer(s_allocator, m_stagingBuffer,
145 m_stagingBuffer = VK_NULL_HANDLE;
146 m_stagingMemory = VK_NULL_HANDLE;
147 s_totalAllocatedBytes -= stagingAllocSize;
148 if (stagingMemTypeIdx < s_memTypeCount)
149 s_perTypeBytes[stagingMemTypeIdx] -= stagingAllocSize;
155 CopyBuffer(m_stagingBuffer, m_buffer,
Size);
159 s_pendingCleanup.push_back({m_stagingBuffer, m_stagingMemory, stagingAllocSize, stagingMemTypeIdx});
161 vmaDestroyBuffer(s_allocator, m_stagingBuffer, m_stagingMemory);
162 s_totalAllocatedBytes -= stagingAllocSize;
163 if (stagingMemTypeIdx < s_memTypeCount)
164 s_perTypeBytes[stagingMemTypeIdx] -= stagingAllocSize;
166 m_stagingBuffer = VK_NULL_HANDLE;
167 m_stagingMemory = VK_NULL_HANDLE;
171 VkDeviceSize stagingAllocSize = 0;
172 uint32_t stagingMemTypeIdx = 0;
175 VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
176 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
177 VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
178 m_stagingBuffer, m_stagingMemory, &stagingAllocSize,
180 if (m_stagingBuffer == VK_NULL_HANDLE)
return false;
184 vmaMapMemory(s_allocator, m_stagingMemory, &mapped);
185 memcpy(mapped, data,
Size);
186 vmaUnmapMemory(s_allocator, m_stagingMemory);
191 VK_BUFFER_USAGE_TRANSFER_DST_BIT |
192 VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
193 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
196 if (m_buffer == VK_NULL_HANDLE) {
197 if (m_stagingBuffer != VK_NULL_HANDLE) {
198 vmaDestroyBuffer(s_allocator, m_stagingBuffer,
200 m_stagingBuffer = VK_NULL_HANDLE;
201 m_stagingMemory = VK_NULL_HANDLE;
202 s_totalAllocatedBytes -= stagingAllocSize;
203 if (stagingMemTypeIdx < s_memTypeCount)
204 s_perTypeBytes[stagingMemTypeIdx] -= stagingAllocSize;
210 CopyBuffer(m_stagingBuffer, m_buffer,
Size);
214 s_pendingCleanup.push_back({m_stagingBuffer, m_stagingMemory, stagingAllocSize, stagingMemTypeIdx});
216 vmaDestroyBuffer(s_allocator, m_stagingBuffer, m_stagingMemory);
217 s_totalAllocatedBytes -= stagingAllocSize;
218 if (stagingMemTypeIdx < s_memTypeCount)
219 s_perTypeBytes[stagingMemTypeIdx] -= stagingAllocSize;
221 m_stagingBuffer = VK_NULL_HANDLE;
222 m_stagingMemory = VK_NULL_HANDLE;
229 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
230 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
231 VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
234 if (m_buffer == VK_NULL_HANDLE)
return false;
237 vmaMapMemory(s_allocator, m_memory, &m_mappedData);
240 memcpy(m_mappedData, data,
Size);
248 VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
249 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
250 VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
255 vmaMapMemory(s_allocator, m_memory, &mapped);
256 memcpy(mapped, data,
Size);
257 vmaUnmapMemory(s_allocator, m_memory);
273 if (!data || size == 0)
return;
277 memcpy(m_mappedData, data, size);
280 VmaAllocation stagingMem;
281 VkDeviceSize stagingAllocSize = 0;
282 uint32_t stagingMemTypeIdx = 0;
285 VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
286 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
287 VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
288 staging, stagingMem, &stagingAllocSize, &stagingMemTypeIdx);
291 vmaMapMemory(s_allocator, stagingMem, &mapped);
292 memcpy(mapped, data, size);
293 vmaUnmapMemory(s_allocator, stagingMem);
295 CopyBuffer(staging, m_buffer, size);
298 s_pendingCleanup.push_back({staging, stagingMem, stagingAllocSize, stagingMemTypeIdx});
300 vmaDestroyBuffer(s_allocator, staging, stagingMem);
301 s_totalAllocatedBytes -= stagingAllocSize;
302 if (stagingMemTypeIdx < s_memTypeCount)
303 s_perTypeBytes[stagingMemTypeIdx] -= stagingAllocSize;
309 if (m_device == VK_NULL_HANDLE)
return;
314 if (m_pendingInBatch && s_batchActive) {
316 m_pendingInBatch =
false;
320 vmaUnmapMemory(s_allocator, m_memory);
321 m_mappedData =
nullptr;
325 if (m_stagingBuffer != VK_NULL_HANDLE) {
326 vmaDestroyBuffer(s_allocator, m_stagingBuffer, m_stagingMemory);
327 m_stagingBuffer = VK_NULL_HANDLE;
328 m_stagingMemory = VK_NULL_HANDLE;
332 if (m_buffer != VK_NULL_HANDLE) {
333 s_deferredDeletions.push_back({m_buffer, m_memory, m_device,
334 m_allocSize, m_usage, m_memoryTypeIndex,
335 s_frameNumber, m_bufferSize});
336 m_buffer = VK_NULL_HANDLE;
337 m_memory = VK_NULL_HANDLE;
352 VkResult result = vmaMapMemory(s_allocator, m_memory, &
Data);
353 if (result != VK_SUCCESS)
return false;
365 vmaUnmapMemory(s_allocator, m_memory);
371 return m_mappedData ? m_mappedData :
Data;
374void VulkanBuffer::CreateBuffer(VkDeviceSize size,
375 VkBufferUsageFlags usage,
376 VkMemoryPropertyFlags properties,
378 VmaAllocation& memory,
379 VkDeviceSize* outAllocSize,
380 uint32_t* outMemTypeIdx) {
383 const bool recyclable =
IsRecyclable(usage, properties);
384 const VkDeviceSize createSize = recyclable ?
BucketSize(size) : size;
387 TryRecycleBuffer(createSize, usage, properties, buffer, memory,
389 if (outMemTypeIdx && &buffer == &m_buffer) *outMemTypeIdx = m_memoryTypeIndex;
393 VkBufferCreateInfo bufferInfo{};
394 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
395 bufferInfo.size = createSize;
396 bufferInfo.usage = usage;
397 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
399 VmaAllocationCreateInfo allocCI{};
400 allocCI.usage = VMA_MEMORY_USAGE_AUTO;
401 allocCI.requiredFlags = properties;
402 if (properties & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)
403 allocCI.flags |= VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
405 VmaAllocationInfo info{};
406 VkResult r = vmaCreateBuffer(s_allocator, &bufferInfo, &allocCI,
407 &buffer, &memory, &info);
409 if (r != VK_SUCCESS && !s_bufferPool.empty()) {
412 SLEAK_WARN(
"Vulkan alloc failed, evicting {} pooled buffers ({:.1f} MB)",
414 static_cast<double>(s_poolBytes) / (1024.0 * 1024.0));
417 for (
auto& e : s_bufferPool) {
418 vmaDestroyBuffer(s_allocator, e.buffer, e.memory);
419 s_totalAllocatedBytes -= e.allocSize;
420 if (e.memoryTypeIndex < s_memTypeCount)
421 s_perTypeBytes[e.memoryTypeIndex] -= e.allocSize;
423 s_bufferPool.clear();
425 r = vmaCreateBuffer(s_allocator, &bufferInfo, &allocCI,
426 &buffer, &memory, &info);
429 if (r != VK_SUCCESS) {
432 SLEAK_WARN(
"Vulkan alloc still failing, reclaiming fence-safe deferred deletions");
436 r = vmaCreateBuffer(s_allocator, &bufferInfo, &allocCI,
437 &buffer, &memory, &info);
440 if (r != VK_SUCCESS) {
441 SLEAK_ERROR(
"Failed to allocate Vulkan buffer memory!");
442 buffer = VK_NULL_HANDLE;
443 memory = VK_NULL_HANDLE;
447 s_totalAllocatedBytes += info.size;
448 if (info.memoryType < s_memTypeCount)
449 s_perTypeBytes[info.memoryType] += info.size;
451 if (outAllocSize) *outAllocSize = info.size;
452 if (outMemTypeIdx) *outMemTypeIdx = info.memoryType;
454 if (&buffer == &m_buffer) {
455 m_allocSize = info.size;
457 m_memoryTypeIndex = info.memoryType;
458 m_bufferSize = createSize;
462void VulkanBuffer::CopyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer,
465 EnsureBatchStarted(m_device, m_commandPool, m_graphicsQueue);
467 VkBufferCopy copyRegion{};
468 copyRegion.size = size;
469 vkCmdCopyBuffer(s_batchCommandBuffer, srcBuffer, dstBuffer, 1, ©Region);
470 m_pendingInBatch =
true;
475 VkCommandBufferAllocateInfo allocInfo{};
476 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
477 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
478 allocInfo.commandPool = m_commandPool;
479 allocInfo.commandBufferCount = 1;
481 VkCommandBuffer commandBuffer;
482 vkAllocateCommandBuffers(m_device, &allocInfo, &commandBuffer);
484 VkCommandBufferBeginInfo beginInfo{};
485 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
486 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
488 vkBeginCommandBuffer(commandBuffer, &beginInfo);
490 VkBufferCopy copyRegion{};
491 copyRegion.size = size;
492 vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region);
494 vkEndCommandBuffer(commandBuffer);
496 VkFenceCreateInfo fenceInfo{};
497 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
499 vkCreateFence(m_device, &fenceInfo,
nullptr, ©Fence);
501 VkSubmitInfo submitInfo{};
502 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
503 submitInfo.commandBufferCount = 1;
504 submitInfo.pCommandBuffers = &commandBuffer;
506 vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, copyFence);
507 vkWaitForFences(m_device, 1, ©Fence, VK_TRUE, UINT64_MAX);
509 vkDestroyFence(m_device, copyFence,
nullptr);
510 vkFreeCommandBuffers(m_device, m_commandPool, 1, &commandBuffer);
513void VulkanBuffer::EnsureBatchStarted(VkDevice device, VkCommandPool pool,
515 if (s_batchActive || !s_batchingEnabled)
return;
517 s_batchDevice = device;
518 s_batchCommandPool = pool;
519 s_batchQueue = queue;
521 VkCommandBufferAllocateInfo allocInfo{};
522 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
523 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
524 allocInfo.commandPool = pool;
525 allocInfo.commandBufferCount = 1;
527 if (vkAllocateCommandBuffers(device, &allocInfo, &s_batchCommandBuffer) != VK_SUCCESS) {
528 SLEAK_ERROR(
"Failed to allocate batch transfer command buffer!");
532 VkCommandBufferBeginInfo beginInfo{};
533 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
534 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
536 if (vkBeginCommandBuffer(s_batchCommandBuffer, &beginInfo) != VK_SUCCESS) {
537 SLEAK_ERROR(
"Failed to begin batch transfer command buffer!");
538 vkFreeCommandBuffers(device, pool, 1, &s_batchCommandBuffer);
539 s_batchCommandBuffer = VK_NULL_HANDLE;
543 s_batchActive =
true;
547 if (!s_batchActive)
return;
549 vkEndCommandBuffer(s_batchCommandBuffer);
552 VkFenceCreateInfo fenceInfo{};
553 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
555 vkCreateFence(s_batchDevice, &fenceInfo,
nullptr, &batchFence);
557 VkSubmitInfo submitInfo{};
558 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
559 submitInfo.commandBufferCount = 1;
560 submitInfo.pCommandBuffers = &s_batchCommandBuffer;
562 vkQueueSubmit(s_batchQueue, 1, &submitInfo, batchFence);
563 vkWaitForFences(s_batchDevice, 1, &batchFence, VK_TRUE, UINT64_MAX);
566 vkDestroyFence(s_batchDevice, batchFence,
nullptr);
567 vkFreeCommandBuffers(s_batchDevice, s_batchCommandPool, 1, &s_batchCommandBuffer);
569 for (
auto& pending : s_pendingCleanup) {
570 vmaDestroyBuffer(s_allocator, pending.buffer, pending.memory);
571 s_totalAllocatedBytes -= pending.allocSize;
572 if (pending.memoryTypeIndex < s_memTypeCount)
573 s_perTypeBytes[pending.memoryTypeIndex] -= pending.allocSize;
575 s_pendingCleanup.clear();
577 s_batchCommandBuffer = VK_NULL_HANDLE;
578 s_batchActive =
false;
585 if (!s_batchActive)
return result;
587 vkEndCommandBuffer(s_batchCommandBuffer);
590 VkSubmitInfo submitInfo{};
591 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
592 submitInfo.commandBufferCount = 1;
593 submitInfo.pCommandBuffers = &s_batchCommandBuffer;
594 submitInfo.signalSemaphoreCount = 1;
595 submitInfo.pSignalSemaphores = &signalSemaphore;
597 vkQueueSubmit(s_batchQueue, 1, &submitInfo, VK_NULL_HANDLE);
604 result.
device = s_batchDevice;
606 s_pendingCleanup.clear();
607 s_batchCommandBuffer = VK_NULL_HANDLE;
608 s_batchActive =
false;
613uint32_t VulkanBuffer::FindMemoryType(uint32_t typeFilter,
614 VkMemoryPropertyFlags properties) {
615 VkPhysicalDeviceMemoryProperties memProperties;
616 vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &memProperties);
618 for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
619 if ((typeFilter & (1 << i)) &&
620 (memProperties.memoryTypes[i].propertyFlags & properties) ==
626 SLEAK_ERROR(
"Failed to find suitable memory type!");
641 for (
size_t i = 0; i < s_deferredDeletions.size(); ) {
642 auto& entry = s_deferredDeletions[i];
643 if (s_frameNumber - entry.frameNumber >= maxFramesInFlight) {
644 const bool deviceLocal =
645 entry.memoryTypeIndex < s_memTypeCount &&
646 s_memTypeIsDeviceLocal[entry.memoryTypeIndex];
647 const bool poolable =
649 entry.bufferSize > 0 &&
651 s_bufferPool.size() < MAX_POOL_SIZE &&
652 s_poolBytes + entry.allocSize <= MAX_POOL_BYTES;
656 s_bufferPool.push_back({entry.buffer, entry.memory, entry.device,
657 entry.allocSize, entry.usage,
658 entry.memoryTypeIndex, entry.bufferSize,
660 s_poolBytes += entry.allocSize;
663 vmaDestroyBuffer(s_allocator, entry.buffer, entry.memory);
664 s_totalAllocatedBytes -= entry.allocSize;
665 if (entry.memoryTypeIndex < s_memTypeCount)
666 s_perTypeBytes[entry.memoryTypeIndex] -= entry.allocSize;
668 entry = std::move(s_deferredDeletions.back());
669 s_deferredDeletions.pop_back();
675 EvictPoolOverBudget();
682 if (s_deferredDeletions.empty()) {
687 auto& e = s_bufferPool.back();
688 vmaDestroyBuffer(s_allocator, e.buffer, e.memory);
689 s_totalAllocatedBytes -= e.allocSize;
690 s_poolBytes -= e.allocSize;
691 if (e.memoryTypeIndex < s_memTypeCount)
692 s_perTypeBytes[e.memoryTypeIndex] -= e.allocSize;
693 s_bufferPool.pop_back();
702 if (s_allocator && (s_frameNumber % 600) == 0) {
703 VmaTotalStatistics vs{};
704 vmaCalculateStatistics(s_allocator, &vs);
706 "VMA: live {} MB / blocks {} MB | pool {} entries {} MB | deferred {}",
707 vs.total.statistics.allocationBytes / (1024 * 1024),
708 vs.total.statistics.blockBytes / (1024 * 1024),
709 s_bufferPool.size(), s_poolBytes / (1024 * 1024),
710 s_deferredDeletions.size());
715bool VulkanBuffer::TryRecycleBuffer(VkDeviceSize size,
716 VkBufferUsageFlags usage,
717 VkMemoryPropertyFlags properties,
719 VmaAllocation& memory,
720 VkDeviceSize* outAllocSize) {
721 if (!
IsRecyclable(usage, properties) || s_bufferPool.empty())
return false;
724 size_t best = s_bufferPool.size();
725 VkDeviceSize bestSize = 0;
726 for (
size_t i = 0; i < s_bufferPool.size(); ++i) {
727 const auto& e = s_bufferPool[i];
728 if (e.usage == usage && e.device == m_device && e.bufferSize >= size) {
729 if (best == s_bufferPool.size() || e.bufferSize < bestSize) {
731 bestSize = e.bufferSize;
735 if (best == s_bufferPool.size())
return false;
737 PooledBuffer e = s_bufferPool[best];
738 s_bufferPool[best] = s_bufferPool.back();
739 s_bufferPool.pop_back();
740 s_poolBytes -= e.allocSize;
745 if (outAllocSize) *outAllocSize = e.allocSize;
747 if (&buffer == &m_buffer) {
748 m_allocSize = e.allocSize;
750 m_memoryTypeIndex = e.memoryTypeIndex;
751 m_bufferSize = e.bufferSize;
756void VulkanBuffer::EvictPoolOverBudget() {
758 while ((s_poolBytes > MAX_POOL_BYTES || s_bufferPool.size() > MAX_POOL_SIZE) &&
759 !s_bufferPool.empty()) {
760 size_t oldestIdx = 0;
761 uint64_t oldestFrame = s_bufferPool[0].insertFrame;
762 for (
size_t i = 1; i < s_bufferPool.size(); ++i) {
763 if (s_bufferPool[i].insertFrame < oldestFrame) {
764 oldestFrame = s_bufferPool[i].insertFrame;
768 auto& e = s_bufferPool[oldestIdx];
769 vmaDestroyBuffer(s_allocator, e.buffer, e.memory);
770 s_totalAllocatedBytes -= e.allocSize;
771 s_poolBytes -= e.allocSize;
772 if (e.memoryTypeIndex < s_memTypeCount)
773 s_perTypeBytes[e.memoryTypeIndex] -= e.allocSize;
774 s_bufferPool[oldestIdx] = s_bufferPool.back();
775 s_bufferPool.pop_back();
780 for (
auto& entry : s_deferredDeletions) {
781 vmaDestroyBuffer(s_allocator, entry.buffer, entry.memory);
782 s_totalAllocatedBytes -= entry.allocSize;
783 if (entry.memoryTypeIndex < s_memTypeCount)
784 s_perTypeBytes[entry.memoryTypeIndex] -= entry.allocSize;
786 s_deferredDeletions.clear();
789 for (
auto& entry : s_bufferPool) {
790 vmaDestroyBuffer(s_allocator, entry.buffer, entry.memory);
791 s_totalAllocatedBytes -= entry.allocSize;
792 if (entry.memoryTypeIndex < s_memTypeCount)
793 s_perTypeBytes[entry.memoryTypeIndex] -= entry.allocSize;
795 s_bufferPool.clear();
800 return s_totalAllocatedBytes;
806 if (s_physicalDeviceGlobal == VK_NULL_HANDLE)
return 0;
808 VkPhysicalDeviceMemoryProperties memProps;
809 vkGetPhysicalDeviceMemoryProperties(s_physicalDeviceGlobal, &memProps);
811 VkDeviceSize totalDeviceLocal = 0;
812 for (uint32_t i = 0; i < memProps.memoryHeapCount; i++) {
813 if (memProps.memoryHeaps[i].flags &
814 VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) {
815 totalDeviceLocal += memProps.memoryHeaps[i].size;
818 return totalDeviceLocal;
822 s_physicalDeviceGlobal = device;
823 if (device == VK_NULL_HANDLE)
return;
824 VkPhysicalDeviceMemoryProperties memProps;
825 vkGetPhysicalDeviceMemoryProperties(device, &memProps);
826 s_memTypeCount = memProps.memoryTypeCount;
827 for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) {
828 uint32_t heapIdx = memProps.memoryTypes[i].heapIndex;
829 s_memTypeIsDeviceLocal[i] = (memProps.memoryHeaps[heapIdx].flags &
830 VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0;
831 s_perTypeBytes[i] = 0;
836 VkDeviceSize total = 0;
837 for (uint32_t i = 0; i < s_memTypeCount; ++i) {
838 if (s_memTypeIsDeviceLocal[i]) total += s_perTypeBytes[i];
843void VulkanBuffer::EvictPoolForMemType(uint32_t memTypeIdx) {
844 for (
size_t i = 0; i < s_bufferPool.size(); ) {
845 auto& e = s_bufferPool[i];
846 if (e.memoryTypeIndex == memTypeIdx) {
847 vmaDestroyBuffer(s_allocator, e.buffer, e.memory);
848 s_totalAllocatedBytes -= e.allocSize;
849 s_poolBytes -= e.allocSize;
850 if (memTypeIdx < s_memTypeCount)
851 s_perTypeBytes[memTypeIdx] -= e.allocSize;
852 s_bufferPool[i] = s_bufferPool.back();
853 s_bufferPool.pop_back();
static void SetPhysicalDevice(VkPhysicalDevice device)
void * GetData() override
static void ProcessDeferredDeletions(uint32_t maxFramesInFlight)
bool Map() override
Maps host-visible memory for direct CPU writes.
static VkDeviceSize GetTotalAllocatedBytes()
static void DestroyAllocator()
bool Initialize(void *data) override
Allocates the buffer (recycling a pooled one if a match exists) and uploads initial data,...
static void FlushAllDeferredDeletions()
static void InitAllocator(VkInstance instance, VkPhysicalDevice physicalDevice, VkDevice device)
VulkanBuffer(VkDevice device, VkPhysicalDevice physicalDevice, uint32_t size, BufferType type, VkCommandPool commandPool, VkQueue graphicsQueue)
static VkDeviceSize GetDeviceLocalAllocatedBytes()
static void DumpPerFrameAllocStats()
static AsyncFlushResult FlushPendingCopiesAsync(VkSemaphore signalSemaphore)
static void FlushPendingCopies()
static VkDeviceSize GetDeviceLocalHeapSize()
static uint32_t g_maxFramesInFlight
static constexpr size_t kIdleTrimPerFrame
static uint64_t g_lastReclaimWarnFrame
static constexpr uint32_t kIdleFramesBeforeTrim
BufferType
GPU buffer usage kind, drives backend binding flags and layout.
static constexpr size_t kMaxDeletesPerFrame
static bool UsageIsRecyclable(VkBufferUsageFlags usage)
True for vertex/index usages, the only kinds worth pooling for reuse.
static constexpr VkDeviceSize kPoolBucket
static uint32_t g_idleFrames
static uint64_t g_lastPoolWarnFrame
static VkDeviceSize BucketSize(VkDeviceSize s)
Rounds a size up to the nearest pool bucket so similar-sized buffers can share slots.
static bool IsRecyclable(VkBufferUsageFlags usage, VkMemoryPropertyFlags props)
True when a buffer is both device-local and a recyclable usage, the pool's eligibility gate.
Root namespace for everything the engine exposes.
Result of an async batched flush: the recorded command buffer plus staging resources to free later.
VkCommandPool commandPool
std::vector< PendingStagingCleanup > stagingBuffers
VkCommandBuffer commandBuffer