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