From f0dc921ed5c03690c376f88d3e9f3d2b867af2b3 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 23 Mar 2016 00:40:41 -0700 Subject: [PATCH] Vulkan: Setup a pool memory manager for textures. --- Common/Vulkan/VulkanMemory.cpp | 168 +++++++++++++++++++++++++++++++++ Common/Vulkan/VulkanMemory.h | 46 +++++++++ 2 files changed, 214 insertions(+) diff --git a/Common/Vulkan/VulkanMemory.cpp b/Common/Vulkan/VulkanMemory.cpp index 06097bdaa6..61a842c270 100644 --- a/Common/Vulkan/VulkanMemory.cpp +++ b/Common/Vulkan/VulkanMemory.cpp @@ -100,3 +100,171 @@ void VulkanPushBuffer::Defragment(VulkanContext *vulkan) { bool res = AddBuffer(); assert(res); } + +VulkanDeviceAllocator::VulkanDeviceAllocator(VulkanContext *vulkan, size_t minSlabSize) + : vulkan_(vulkan), minSlabSize_(minSlabSize), memoryTypeIndex_(UNDEFINED_MEMORY_TYPE) { + assert((minSlabSize_ & (SLAB_GRAIN_SIZE - 1)) == 0); +} + +VulkanDeviceAllocator::~VulkanDeviceAllocator() { + assert(slabs_.empty()); +} + +void VulkanDeviceAllocator::Destroy() { + for (Slab &slab : slabs_) { + // Did anyone forget to free? + assert(slab.allocSizes.empty()); + + vulkan_->Delete().QueueDeleteDeviceMemory(slab.deviceMemory); + } + slabs_.clear(); +} + +size_t VulkanDeviceAllocator::Allocate(const VkMemoryRequirements &reqs, VkDeviceMemory *deviceMemory) { + uint32_t memoryTypeIndex; + bool pass = vulkan_->MemoryTypeFromProperties(reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &memoryTypeIndex); + assert(pass); + if (!pass) { + return ALLOCATE_FAILED; + } + if (memoryTypeIndex_ == UNDEFINED_MEMORY_TYPE) { + memoryTypeIndex_ = memoryTypeIndex; + } else if (memoryTypeIndex_ != memoryTypeIndex) { + assert(memoryTypeIndex_ == memoryTypeIndex); + return ALLOCATE_FAILED; + } + + size_t align = reqs.alignment <= SLAB_GRAIN_SIZE ? 1 : (reqs.alignment >> SLAB_GRAIN_SHIFT); + size_t blocks = (reqs.size + SLAB_GRAIN_SIZE - 1) >> SLAB_GRAIN_SHIFT; + + for (Slab &slab : slabs_) { + size_t start = slab.nextFree; + + while (start < slab.usage.size()) { + start = (start + align - 1) & ~(align - 1); + if (AllocateFromSlab(slab, start, blocks)) { + // Allocated? Great, let's return right away. + *deviceMemory = slab.deviceMemory; + return start << SLAB_GRAIN_SHIFT; + } + } + } + + // Okay, we couldn't fit it into any existing slabs. We need a new one. + if (!AllocateSlab(reqs.size)) { + return ALLOCATE_FAILED; + } + + // Guaranteed to be the last one, unless it failed to allocate. + Slab &slab = slabs_[slabs_.size() - 1]; + size_t start = 0; + if (AllocateFromSlab(slab, start, blocks)) { + *deviceMemory = slab.deviceMemory; + return start << SLAB_GRAIN_SHIFT; + } + + // Somehow... we're out of space. Darn. + return ALLOCATE_FAILED; +} + +bool VulkanDeviceAllocator::AllocateFromSlab(Slab &slab, size_t &start, size_t blocks) { + bool matched = true; + for (size_t i = 0; i < blocks; ++i) { + if (slab.usage[start + i]) { + // If we just ran into one, there's probably an allocation size. + auto it = slab.allocSizes.find(start + i); + if (it != slab.allocSizes.end()) { + start += i + it->second; + } else { + // We don't know how big it is, so just skip to the next one. + start += i + 1; + } + return false; + } + } + + // Okay, this run is good. Actually mark it. + for (size_t i = 0; i < blocks; ++i) { + slab.usage[start + i] = 1; + } + slab.nextFree = start + blocks; + if (slab.nextFree >= slab.usage.size()) { + slab.nextFree = 0; + } + + // Remember the size so we can free. + slab.allocSizes[start] = blocks; + return true; +} + +void VulkanDeviceAllocator::Free(VkDeviceMemory deviceMemory, size_t offset) { + size_t start = offset >> SLAB_GRAIN_SHIFT; + bool found = false; + for (Slab &slab : slabs_) { + if (slab.deviceMemory != deviceMemory) { + continue; + } + + auto it = slab.allocSizes.find(start); + // Let's hope we don't get any double-frees or misfrees. + assert(it == slab.allocSizes.end()); + if (it != slab.allocSizes.end()) { + size_t size = it->second; + for (size_t i = 0; i < size; ++i) { + slab.usage[start + i] = 0; + } + slab.allocSizes.erase(it); + } + found = true; + break; + } + + // Wrong deviceMemory even? Maybe it was already decimated, but that means a double-free. + assert(found); +} + +bool VulkanDeviceAllocator::AllocateSlab(size_t minBytes) { + VkMemoryAllocateInfo alloc = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; + alloc.allocationSize = minSlabSize_; + alloc.memoryTypeIndex = memoryTypeIndex_; + + while (alloc.allocationSize < minBytes) { + alloc.allocationSize <<= 1; + } + + VkDeviceMemory deviceMemory; + VkResult res = vkAllocateMemory(vulkan_->GetDevice(), &alloc, NULL, &deviceMemory); + if (res != VK_SUCCESS) { + // Ran out of memory. + return false; + } + + slabs_.resize(slabs_.size() + 1); + Slab &slab = slabs_[slabs_.size() - 1]; + slab.deviceMemory = deviceMemory; + slab.usage.resize(alloc.allocationSize >> SLAB_GRAIN_SHIFT); + + return true; +} + +void VulkanDeviceAllocator::Decimate() { + bool foundFree = false; + + for (size_t i = 0; i < slabs_.size(); ++i) { + if (!slabs_[i].allocSizes.empty()) { + continue; + } + + if (!foundFree) { + // Let's allow one free slab, so we have room. + foundFree = true; + continue; + } + + // Okay, let's free this one up. + vulkan_->Delete().QueueDeleteDeviceMemory(slabs_[i].deviceMemory); + slabs_.erase(slabs_.begin() + i); + // Let's check the next one, which is now in this same slot. + --i; + } +} diff --git a/Common/Vulkan/VulkanMemory.h b/Common/Vulkan/VulkanMemory.h index 2838e2b7f3..f951359dcc 100644 --- a/Common/Vulkan/VulkanMemory.h +++ b/Common/Vulkan/VulkanMemory.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include "Common/Vulkan/VulkanContext.h" // VulkanMemory @@ -126,3 +127,48 @@ private: uint32_t memoryTypeIndex_; uint8_t *writePtr_; }; + +class VulkanDeviceAllocator { +public: + VulkanDeviceAllocator(VulkanContext *vulkan, size_t minSlabSize); + ~VulkanDeviceAllocator(); + + void Destroy(); + + void Begin() { + Decimate(); + } + + void End() { + } + + size_t Allocate(const VkMemoryRequirements &reqs, VkDeviceMemory *deviceMemory); + void Free(VkDeviceMemory deviceMemory, size_t offset); + + static const size_t ALLOCATE_FAILED = -1; + +private: + static const size_t SLAB_GRAIN_SIZE = 1024; + static const uint8_t SLAB_GRAIN_SHIFT = 10; + static const uint32_t UNDEFINED_MEMORY_TYPE = -1; + + struct Slab { + VkDeviceMemory deviceMemory; + std::vector usage; + std::unordered_map allocSizes; + size_t nextFree; + + size_t Size() { + return usage.size() * SLAB_GRAIN_SIZE; + } + }; + + bool AllocateSlab(size_t minBytes); + bool AllocateFromSlab(Slab &slab, size_t &start, size_t blocks); + void Decimate(); + + VulkanContext *vulkan_; + std::vector slabs_; + size_t minSlabSize_; + uint32_t memoryTypeIndex_; +};