1 #pragma once 2 3 #include <vector> 4 #include <unordered_map> 5 6 #include "Common/Log.h" 7 #include "Common/GPU/Vulkan/VulkanContext.h" 8 9 // VulkanMemory 10 // 11 // Vulkan memory management utils. 12 13 // VulkanPushBuffer 14 // Simple incrementing allocator. 15 // Use these to push vertex, index and uniform data. Generally you'll have two of these 16 // and alternate on each frame. Make sure not to reset until the fence from the last time you used it 17 // has completed. 18 // 19 // TODO: Make it possible to suballocate pushbuffers from a large DeviceMemory block. 20 class VulkanPushBuffer { 21 struct BufInfo { 22 VkBuffer buffer; 23 VkDeviceMemory deviceMemory; 24 }; 25 26 public: 27 // NOTE: If you create a push buffer with only VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 28 // then you can't use any of the push functions as pointers will not be reachable from the CPU. 29 // You must in this case use Allocate() only, and pass the returned offset and the VkBuffer to Vulkan APIs. 30 VulkanPushBuffer(VulkanContext *vulkan, size_t size, VkBufferUsageFlags usage, VkMemoryPropertyFlags memoryPropertyMask = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); 31 ~VulkanPushBuffer(); 32 33 void Destroy(VulkanContext *vulkan); 34 Reset()35 void Reset() { offset_ = 0; } 36 37 // Needs context in case of defragment. Begin(VulkanContext * vulkan)38 void Begin(VulkanContext *vulkan) { 39 buf_ = 0; 40 offset_ = 0; 41 // Note: we must defrag because some buffers may be smaller than size_. 42 Defragment(vulkan); 43 if (memoryPropertyMask_ & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) 44 Map(); 45 } 46 BeginNoReset()47 void BeginNoReset() { 48 if (memoryPropertyMask_ & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) 49 Map(); 50 } 51 End()52 void End() { 53 if (memoryPropertyMask_ & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) 54 Unmap(); 55 } 56 57 void Map(); 58 59 void Unmap(); 60 61 // When using the returned memory, make sure to bind the returned vkbuf. 62 // This will later allow for handling overflow correctly. Allocate(size_t numBytes,VkBuffer * vkbuf)63 size_t Allocate(size_t numBytes, VkBuffer *vkbuf) { 64 size_t out = offset_; 65 offset_ += (numBytes + 3) & ~3; // Round up to 4 bytes. 66 67 if (offset_ >= size_) { 68 NextBuffer(numBytes); 69 out = offset_; 70 offset_ += (numBytes + 3) & ~3; 71 } 72 *vkbuf = buffers_[buf_].buffer; 73 return out; 74 } 75 76 // Returns the offset that should be used when binding this buffer to get this data. Push(const void * data,size_t size,VkBuffer * vkbuf)77 size_t Push(const void *data, size_t size, VkBuffer *vkbuf) { 78 _dbg_assert_(writePtr_); 79 size_t off = Allocate(size, vkbuf); 80 memcpy(writePtr_ + off, data, size); 81 return off; 82 } 83 PushAligned(const void * data,size_t size,int align,VkBuffer * vkbuf)84 uint32_t PushAligned(const void *data, size_t size, int align, VkBuffer *vkbuf) { 85 _dbg_assert_(writePtr_); 86 offset_ = (offset_ + align - 1) & ~(align - 1); 87 size_t off = Allocate(size, vkbuf); 88 memcpy(writePtr_ + off, data, size); 89 return (uint32_t)off; 90 } 91 GetOffset()92 size_t GetOffset() const { 93 return offset_; 94 } 95 96 // "Zero-copy" variant - you can write the data directly as you compute it. 97 // Recommended. Push(size_t size,uint32_t * bindOffset,VkBuffer * vkbuf)98 void *Push(size_t size, uint32_t *bindOffset, VkBuffer *vkbuf) { 99 _dbg_assert_(writePtr_); 100 size_t off = Allocate(size, vkbuf); 101 *bindOffset = (uint32_t)off; 102 return writePtr_ + off; 103 } PushAligned(size_t size,uint32_t * bindOffset,VkBuffer * vkbuf,int align)104 void *PushAligned(size_t size, uint32_t *bindOffset, VkBuffer *vkbuf, int align) { 105 _dbg_assert_(writePtr_); 106 offset_ = (offset_ + align - 1) & ~(align - 1); 107 size_t off = Allocate(size, vkbuf); 108 *bindOffset = (uint32_t)off; 109 return writePtr_ + off; 110 } 111 112 size_t GetTotalSize() const; 113 114 private: 115 bool AddBuffer(); 116 void NextBuffer(size_t minSize); 117 void Defragment(VulkanContext *vulkan); 118 119 VulkanContext *vulkan_; 120 VkMemoryPropertyFlags memoryPropertyMask_; 121 122 std::vector<BufInfo> buffers_; 123 size_t buf_ = 0; 124 size_t offset_ = 0; 125 size_t size_ = 0; 126 uint8_t *writePtr_ = nullptr; 127 VkBufferUsageFlags usage_; 128 }; 129 130 // VulkanDeviceAllocator 131 // 132 // Implements a slab based allocator that manages suballocations inside the slabs. 133 // Bitmaps are used to handle allocation state, with a 1KB grain. 134 class VulkanDeviceAllocator { 135 public: 136 // Slab sizes start at minSlabSize and double until maxSlabSize. 137 // Total slab count is unlimited, as long as there's free memory. 138 VulkanDeviceAllocator(VulkanContext *vulkan, size_t minSlabSize, size_t maxSlabSize); 139 ~VulkanDeviceAllocator(); 140 141 // Requires all memory be free beforehand (including all pending deletes.) 142 void Destroy(); 143 Begin()144 void Begin() { 145 Decimate(); 146 } 147 End()148 void End() { 149 } 150 151 // May return ALLOCATE_FAILED if the allocation fails. 152 // NOTE: Lifetime of the string tag points to must exceed that of the allocation. 153 size_t Allocate(const VkMemoryRequirements &reqs, VkDeviceMemory *deviceMemory, const char *tag); 154 155 // Crashes on a double or misfree. 156 void Free(VkDeviceMemory deviceMemory, size_t offset); 157 Touch(VkDeviceMemory deviceMemory,size_t offset)158 inline void Touch(VkDeviceMemory deviceMemory, size_t offset) { 159 if (TRACK_TOUCH) { 160 DoTouch(deviceMemory, offset); 161 } 162 } 163 164 static const size_t ALLOCATE_FAILED = -1; 165 // Set to true to report potential leaks / long held textures. 166 static const bool TRACK_TOUCH = false; 167 GetSlabCount()168 int GetSlabCount() const { return (int)slabs_.size(); } GetMinSlabSize()169 int GetMinSlabSize() const { return (int)minSlabSize_; } GetMaxSlabSize()170 int GetMaxSlabSize() const { return (int)maxSlabSize_; } 171 172 int ComputeUsagePercent() const; 173 std::vector<uint8_t> GetSlabUsage(int slab) const; 174 175 private: 176 static const size_t SLAB_GRAIN_SIZE = 1024; 177 static const uint8_t SLAB_GRAIN_SHIFT = 10; 178 static const uint32_t UNDEFINED_MEMORY_TYPE = -1; 179 180 struct UsageInfo { 181 double created; 182 double touched; 183 const char *tag; 184 }; 185 186 struct Slab { 187 VkDeviceMemory deviceMemory; 188 uint32_t memoryTypeIndex = UNDEFINED_MEMORY_TYPE; 189 std::vector<uint8_t> usage; 190 std::unordered_map<size_t, size_t> allocSizes; 191 std::unordered_map<size_t, UsageInfo> tags; 192 size_t nextFree; 193 size_t totalUsage; 194 SizeSlab195 size_t Size() { 196 return usage.size() * SLAB_GRAIN_SIZE; 197 } 198 }; 199 200 struct FreeInfo { FreeInfoFreeInfo201 explicit FreeInfo(VulkanDeviceAllocator *a, VkDeviceMemory d, size_t o) 202 : allocator(a), deviceMemory(d), offset(o) { 203 } 204 205 VulkanDeviceAllocator *allocator; 206 VkDeviceMemory deviceMemory; 207 size_t offset; 208 }; 209 DispatchFree(void * userdata)210 static void DispatchFree(void *userdata) { 211 auto freeInfo = static_cast<FreeInfo *>(userdata); 212 freeInfo->allocator->ExecuteFree(freeInfo); // this deletes freeInfo 213 } 214 215 bool AllocateSlab(VkDeviceSize minBytes, int memoryTypeIndex); 216 bool AllocateFromSlab(Slab &slab, size_t &start, size_t blocks, const char *tag); 217 void Decimate(); 218 void DoTouch(VkDeviceMemory deviceMemory, size_t offset); 219 void ExecuteFree(FreeInfo *userdata); 220 void ReportOldUsage(); 221 222 VulkanContext *const vulkan_; 223 std::vector<Slab> slabs_; 224 size_t lastSlab_ = 0; 225 size_t minSlabSize_; 226 const size_t maxSlabSize_; 227 bool destroyed_ = false; 228 }; 229