1 // Copyright (c) 2016- PPSSPP Project.
2
3 // This program is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, version 2.0 or later versions.
6
7 // This program is distributed in the hope that it will be useful,
8 // but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // GNU General Public License 2.0 for more details.
11
12 // A copy of the GPL 2.0 should have been included with the program.
13 // If not, see http://www.gnu.org/licenses/
14
15 // Official git repository and contact information can be found at
16 // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18 // Additionally, Common/Vulkan/* , including this file, are also licensed
19 // under the public domain.
20
21 #include "Common/Math/math_util.h"
22
23 #include "Common/Log.h"
24 #include "Common/TimeUtil.h"
25 #include "Common/GPU/Vulkan/VulkanMemory.h"
26
27 using namespace PPSSPP_VK;
28
VulkanPushBuffer(VulkanContext * vulkan,size_t size,VkBufferUsageFlags usage,VkMemoryPropertyFlags memoryPropertyMask)29 VulkanPushBuffer::VulkanPushBuffer(VulkanContext *vulkan, size_t size, VkBufferUsageFlags usage, VkMemoryPropertyFlags memoryPropertyMask)
30 : vulkan_(vulkan), memoryPropertyMask_(memoryPropertyMask), size_(size), usage_(usage) {
31 bool res = AddBuffer();
32 _assert_(res);
33 }
34
~VulkanPushBuffer()35 VulkanPushBuffer::~VulkanPushBuffer() {
36 _assert_(buffers_.empty());
37 }
38
AddBuffer()39 bool VulkanPushBuffer::AddBuffer() {
40 BufInfo info;
41 VkDevice device = vulkan_->GetDevice();
42
43 VkBufferCreateInfo b{ VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
44 b.size = size_;
45 b.flags = 0;
46 b.usage = usage_;
47 b.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
48 b.queueFamilyIndexCount = 0;
49 b.pQueueFamilyIndices = nullptr;
50
51 VkResult res = vkCreateBuffer(device, &b, nullptr, &info.buffer);
52 if (VK_SUCCESS != res) {
53 _assert_msg_(false, "vkCreateBuffer failed! result=%d", (int)res);
54 return false;
55 }
56
57 // Get the buffer memory requirements. None of this can be cached!
58 VkMemoryRequirements reqs;
59 vkGetBufferMemoryRequirements(device, info.buffer, &reqs);
60
61 // Okay, that's the buffer. Now let's allocate some memory for it.
62 VkMemoryAllocateInfo alloc{ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };
63 alloc.allocationSize = reqs.size;
64 vulkan_->MemoryTypeFromProperties(reqs.memoryTypeBits, memoryPropertyMask_, &alloc.memoryTypeIndex);
65
66 res = vkAllocateMemory(device, &alloc, nullptr, &info.deviceMemory);
67 if (VK_SUCCESS != res) {
68 _assert_msg_(false, "vkAllocateMemory failed! size=%d result=%d", (int)reqs.size, (int)res);
69 vkDestroyBuffer(device, info.buffer, nullptr);
70 return false;
71 }
72 res = vkBindBufferMemory(device, info.buffer, info.deviceMemory, 0);
73 if (VK_SUCCESS != res) {
74 ERROR_LOG(G3D, "vkBindBufferMemory failed! result=%d", (int)res);
75 vkFreeMemory(device, info.deviceMemory, nullptr);
76 vkDestroyBuffer(device, info.buffer, nullptr);
77 return false;
78 }
79
80 buffers_.push_back(info);
81 buf_ = buffers_.size() - 1;
82 return true;
83 }
84
Destroy(VulkanContext * vulkan)85 void VulkanPushBuffer::Destroy(VulkanContext *vulkan) {
86 for (BufInfo &info : buffers_) {
87 vulkan->Delete().QueueDeleteBuffer(info.buffer);
88 vulkan->Delete().QueueDeleteDeviceMemory(info.deviceMemory);
89 }
90 buffers_.clear();
91 }
92
NextBuffer(size_t minSize)93 void VulkanPushBuffer::NextBuffer(size_t minSize) {
94 // First, unmap the current memory.
95 if (memoryPropertyMask_ & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)
96 Unmap();
97
98 buf_++;
99 if (buf_ >= buffers_.size() || minSize > size_) {
100 // Before creating the buffer, adjust to the new size_ if necessary.
101 while (size_ < minSize) {
102 size_ <<= 1;
103 }
104
105 bool res = AddBuffer();
106 _assert_(res);
107 if (!res) {
108 // Let's try not to crash at least?
109 buf_ = 0;
110 }
111 }
112
113 // Now, move to the next buffer and map it.
114 offset_ = 0;
115 if (memoryPropertyMask_ & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)
116 Map();
117 }
118
Defragment(VulkanContext * vulkan)119 void VulkanPushBuffer::Defragment(VulkanContext *vulkan) {
120 if (buffers_.size() <= 1) {
121 return;
122 }
123
124 // Okay, we have more than one. Destroy them all and start over with a larger one.
125 size_t newSize = size_ * buffers_.size();
126 Destroy(vulkan);
127
128 size_ = newSize;
129 bool res = AddBuffer();
130 _assert_(res);
131 }
132
GetTotalSize() const133 size_t VulkanPushBuffer::GetTotalSize() const {
134 size_t sum = 0;
135 if (buffers_.size() > 1)
136 sum += size_ * (buffers_.size() - 1);
137 sum += offset_;
138 return sum;
139 }
140
Map()141 void VulkanPushBuffer::Map() {
142 _dbg_assert_(!writePtr_);
143 VkResult res = vkMapMemory(vulkan_->GetDevice(), buffers_[buf_].deviceMemory, 0, size_, 0, (void **)(&writePtr_));
144 _dbg_assert_(writePtr_);
145 _assert_(VK_SUCCESS == res);
146 }
147
Unmap()148 void VulkanPushBuffer::Unmap() {
149 _dbg_assert_msg_(writePtr_ != nullptr, "VulkanPushBuffer::Unmap: writePtr_ null here means we have a bug (map/unmap mismatch)");
150 if (!writePtr_)
151 return;
152
153 if ((memoryPropertyMask_ & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) == 0) {
154 VkMappedMemoryRange range{ VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE };
155 range.offset = 0;
156 range.size = offset_;
157 range.memory = buffers_[buf_].deviceMemory;
158 vkFlushMappedMemoryRanges(vulkan_->GetDevice(), 1, &range);
159 }
160
161 vkUnmapMemory(vulkan_->GetDevice(), buffers_[buf_].deviceMemory);
162 writePtr_ = nullptr;
163 }
164
VulkanDeviceAllocator(VulkanContext * vulkan,size_t minSlabSize,size_t maxSlabSize)165 VulkanDeviceAllocator::VulkanDeviceAllocator(VulkanContext *vulkan, size_t minSlabSize, size_t maxSlabSize)
166 : vulkan_(vulkan), minSlabSize_(minSlabSize), maxSlabSize_(maxSlabSize) {
167 _assert_((minSlabSize_ & (SLAB_GRAIN_SIZE - 1)) == 0);
168 }
169
~VulkanDeviceAllocator()170 VulkanDeviceAllocator::~VulkanDeviceAllocator() {
171 _assert_(destroyed_);
172 _assert_(slabs_.empty());
173 }
174
Destroy()175 void VulkanDeviceAllocator::Destroy() {
176 for (Slab &slab : slabs_) {
177 // Did anyone forget to free?
178 for (auto pair : slab.allocSizes) {
179 int slabUsage = slab.usage[pair.first];
180 // If it's not 2 (queued), there's a leak.
181 // If it's zero, it means allocSizes is somehow out of sync.
182 if (slabUsage == 1) {
183 ERROR_LOG(G3D, "VulkanDeviceAllocator detected memory leak of size %d", (int)pair.second);
184 } else {
185 _dbg_assert_msg_(slabUsage == 2, "Destroy: slabUsage has unexpected value %d", slabUsage);
186 }
187 }
188
189 _assert_(slab.deviceMemory);
190 vulkan_->Delete().QueueDeleteDeviceMemory(slab.deviceMemory);
191 }
192 slabs_.clear();
193 destroyed_ = true;
194 }
195
Allocate(const VkMemoryRequirements & reqs,VkDeviceMemory * deviceMemory,const char * tag)196 size_t VulkanDeviceAllocator::Allocate(const VkMemoryRequirements &reqs, VkDeviceMemory *deviceMemory, const char *tag) {
197 _assert_(!destroyed_);
198 uint32_t memoryTypeIndex;
199 bool pass = vulkan_->MemoryTypeFromProperties(reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &memoryTypeIndex);
200 if (!pass) {
201 ERROR_LOG(G3D, "Failed to pick an appropriate memory type (req: %08x)", reqs.memoryTypeBits);
202 return ALLOCATE_FAILED;
203 }
204
205 size_t size = reqs.size;
206
207 size_t align = reqs.alignment <= SLAB_GRAIN_SIZE ? 1 : (size_t)(reqs.alignment >> SLAB_GRAIN_SHIFT);
208 size_t blocks = (size_t)((size + SLAB_GRAIN_SIZE - 1) >> SLAB_GRAIN_SHIFT);
209
210 const size_t numSlabs = slabs_.size();
211 for (size_t i = 0; i < numSlabs; ++i) {
212 // We loop starting at the last successful allocation.
213 // This helps us "creep forward", and also spend less time allocating.
214 const size_t actualSlab = (lastSlab_ + i) % numSlabs;
215 Slab &slab = slabs_[actualSlab];
216 if (slab.memoryTypeIndex != memoryTypeIndex)
217 continue;
218 size_t start = slab.nextFree;
219
220 while (start < slab.usage.size()) {
221 start = (start + align - 1) & ~(align - 1);
222 if (AllocateFromSlab(slab, start, blocks, tag)) {
223 // Allocated? Great, let's return right away.
224 *deviceMemory = slab.deviceMemory;
225 lastSlab_ = actualSlab;
226 return start << SLAB_GRAIN_SHIFT;
227 }
228 }
229 }
230
231 // Okay, we couldn't fit it into any existing slabs. We need a new one.
232 if (!AllocateSlab(size, memoryTypeIndex)) {
233 return ALLOCATE_FAILED;
234 }
235
236 // Guaranteed to be the last one, unless it failed to allocate.
237 Slab &slab = slabs_[slabs_.size() - 1];
238 size_t start = 0;
239 if (AllocateFromSlab(slab, start, blocks, tag)) {
240 *deviceMemory = slab.deviceMemory;
241 lastSlab_ = slabs_.size() - 1;
242 return start << SLAB_GRAIN_SHIFT;
243 }
244
245 // Somehow... we're out of space. Darn.
246 return ALLOCATE_FAILED;
247 }
248
AllocateFromSlab(Slab & slab,size_t & start,size_t blocks,const char * tag)249 bool VulkanDeviceAllocator::AllocateFromSlab(Slab &slab, size_t &start, size_t blocks, const char *tag) {
250 _assert_(!destroyed_);
251
252 if (start + blocks > slab.usage.size()) {
253 start = slab.usage.size();
254 return false;
255 }
256
257 for (size_t i = 0; i < blocks; ++i) {
258 if (slab.usage[start + i]) {
259 // If we just ran into one, there's probably an allocation size.
260 auto it = slab.allocSizes.find(start + i);
261 if (it != slab.allocSizes.end()) {
262 start += i + it->second;
263 } else {
264 // We don't know how big it is, so just skip to the next one.
265 start += i + 1;
266 }
267 return false;
268 }
269 }
270
271 // Okay, this run is good. Actually mark it.
272 for (size_t i = 0; i < blocks; ++i) {
273 slab.usage[start + i] = 1;
274 }
275 slab.nextFree = start + blocks;
276 if (slab.nextFree >= slab.usage.size()) {
277 slab.nextFree = 0;
278 }
279
280 // Remember the size so we can free.
281 slab.allocSizes[start] = blocks;
282 slab.tags[start] = { time_now_d(), 0.0, tag };
283 slab.totalUsage += blocks;
284 return true;
285 }
286
ComputeUsagePercent() const287 int VulkanDeviceAllocator::ComputeUsagePercent() const {
288 int blockSum = 0;
289 int blocksUsed = 0;
290 for (size_t i = 0; i < slabs_.size(); i++) {
291 blockSum += (int)slabs_[i].usage.size();
292 for (size_t j = 0; j < slabs_[i].usage.size(); j++) {
293 blocksUsed += slabs_[i].usage[j] != 0 ? 1 : 0;
294 }
295 }
296 return blockSum == 0 ? 0 : 100 * blocksUsed / blockSum;
297 }
298
GetSlabUsage(int slabIndex) const299 std::vector<uint8_t> VulkanDeviceAllocator::GetSlabUsage(int slabIndex) const {
300 if (slabIndex < 0 || slabIndex >= (int)slabs_.size())
301 return std::vector<uint8_t>();
302 const Slab &slab = slabs_[slabIndex];
303 return slab.usage;
304 }
305
DoTouch(VkDeviceMemory deviceMemory,size_t offset)306 void VulkanDeviceAllocator::DoTouch(VkDeviceMemory deviceMemory, size_t offset) {
307 size_t start = offset >> SLAB_GRAIN_SHIFT;
308 bool found = false;
309 for (Slab &slab : slabs_) {
310 if (slab.deviceMemory != deviceMemory) {
311 continue;
312 }
313
314 auto it = slab.tags.find(start);
315 if (it != slab.tags.end()) {
316 it->second.touched = time_now_d();
317 found = true;
318 }
319 }
320
321 _assert_msg_(found, "Failed to find allocation to touch - use after free?");
322 }
323
Free(VkDeviceMemory deviceMemory,size_t offset)324 void VulkanDeviceAllocator::Free(VkDeviceMemory deviceMemory, size_t offset) {
325 _assert_(!destroyed_);
326
327 _assert_msg_(!slabs_.empty(), "No slabs - can't be anything to free! double-freed?");
328
329 // First, let's validate. This will allow stack traces to tell us when frees are bad.
330 size_t start = offset >> SLAB_GRAIN_SHIFT;
331 bool found = false;
332 for (Slab &slab : slabs_) {
333 if (slab.deviceMemory != deviceMemory) {
334 continue;
335 }
336
337 auto it = slab.allocSizes.find(start);
338 _assert_msg_(it != slab.allocSizes.end(), "Double free?");
339 // This means a double free, while queued to actually free.
340 _assert_msg_(slab.usage[start] == 1, "Double free when queued to free!");
341
342 // Mark it as "free in progress".
343 slab.usage[start] = 2;
344 found = true;
345 break;
346 }
347
348 // Wrong deviceMemory even? Maybe it was already decimated, but that means a double-free.
349 _assert_msg_(found, "Failed to find allocation to free! Double-freed?");
350
351 // Okay, now enqueue. It's valid.
352 FreeInfo *info = new FreeInfo(this, deviceMemory, offset);
353 // Dispatches a call to ExecuteFree on the next delete round.
354 vulkan_->Delete().QueueCallback(&DispatchFree, info);
355 }
356
ExecuteFree(FreeInfo * userdata)357 void VulkanDeviceAllocator::ExecuteFree(FreeInfo *userdata) {
358 if (destroyed_) {
359 // We already freed this, and it's been validated.
360 delete userdata;
361 return;
362 }
363
364 VkDeviceMemory deviceMemory = userdata->deviceMemory;
365 size_t offset = userdata->offset;
366
367 // Revalidate in case something else got freed and made things inconsistent.
368 size_t start = offset >> SLAB_GRAIN_SHIFT;
369 bool found = false;
370 for (Slab &slab : slabs_) {
371 if (slab.deviceMemory != deviceMemory) {
372 continue;
373 }
374
375 auto it = slab.allocSizes.find(start);
376 if (it != slab.allocSizes.end()) {
377 size_t size = it->second;
378 for (size_t i = 0; i < size; ++i) {
379 slab.usage[start + i] = 0;
380 }
381 slab.allocSizes.erase(it);
382 slab.totalUsage -= size;
383
384 // Allow reusing.
385 if (slab.nextFree > start) {
386 slab.nextFree = start;
387 }
388 } else {
389 // Ack, a double free?
390 _assert_msg_(false, "Double free? Block missing at offset %d", (int)userdata->offset);
391 }
392 auto itTag = slab.tags.find(start);
393 if (itTag != slab.tags.end()) {
394 slab.tags.erase(itTag);
395 }
396 found = true;
397 break;
398 }
399
400 // Wrong deviceMemory even? Maybe it was already decimated, but that means a double-free.
401 _assert_msg_(found, "ExecuteFree: Block not found (offset %d)", (int)offset);
402 delete userdata;
403 }
404
AllocateSlab(VkDeviceSize minBytes,int memoryTypeIndex)405 bool VulkanDeviceAllocator::AllocateSlab(VkDeviceSize minBytes, int memoryTypeIndex) {
406 _assert_(!destroyed_);
407 if (!slabs_.empty() && minSlabSize_ < maxSlabSize_) {
408 // We're allocating an additional slab, so rachet up its size.
409 // TODO: Maybe should not do this when we are allocating a new slab due to memoryTypeIndex not matching?
410 minSlabSize_ <<= 1;
411 }
412
413 VkMemoryAllocateInfo alloc{ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };
414 alloc.allocationSize = minSlabSize_;
415 alloc.memoryTypeIndex = memoryTypeIndex;
416
417 while (alloc.allocationSize < minBytes) {
418 alloc.allocationSize <<= 1;
419 }
420
421 VkDeviceMemory deviceMemory;
422 VkResult res = vkAllocateMemory(vulkan_->GetDevice(), &alloc, NULL, &deviceMemory);
423 if (res != VK_SUCCESS) {
424 // If it's something else, we used it wrong?
425 _assert_(res == VK_ERROR_OUT_OF_HOST_MEMORY || res == VK_ERROR_OUT_OF_DEVICE_MEMORY || res == VK_ERROR_TOO_MANY_OBJECTS);
426 // Okay, so we ran out of memory.
427 return false;
428 }
429
430 slabs_.resize(slabs_.size() + 1);
431 Slab &slab = slabs_[slabs_.size() - 1];
432 slab.memoryTypeIndex = memoryTypeIndex;
433 slab.deviceMemory = deviceMemory;
434 slab.usage.resize((size_t)(alloc.allocationSize >> SLAB_GRAIN_SHIFT));
435
436 return true;
437 }
438
ReportOldUsage()439 void VulkanDeviceAllocator::ReportOldUsage() {
440 double now = time_now_d();
441 static const double OLD_AGE = 10.0;
442 for (size_t i = 0; i < slabs_.size(); ++i) {
443 const auto &slab = slabs_[i];
444
445 bool hasOldAllocs = false;
446 for (auto &it : slab.tags) {
447 const auto info = it.second;
448 double touchedAge = now - info.touched;
449 if (touchedAge >= OLD_AGE) {
450 hasOldAllocs = true;
451 break;
452 }
453 }
454
455 if (hasOldAllocs) {
456 NOTICE_LOG(G3D, "Slab %d usage:", (int)i);
457 for (auto &it : slab.tags) {
458 const auto info = it.second;
459
460 double createAge = now - info.created;
461 double touchedAge = now - info.touched;
462 NOTICE_LOG(G3D, " * %s (created %fs ago, used %fs ago)", info.tag, createAge, touchedAge);
463 }
464 }
465 }
466 }
467
Decimate()468 void VulkanDeviceAllocator::Decimate() {
469 _assert_(!destroyed_);
470 bool foundFree = false;
471
472 if (TRACK_TOUCH) {
473 ReportOldUsage();
474 }
475
476 for (size_t i = 0; i < slabs_.size(); ++i) {
477 // Go backwards. This way, we keep the largest free slab.
478 // We do this here (instead of the for) since size_t is unsigned.
479 size_t index = slabs_.size() - i - 1;
480 auto &slab = slabs_[index];
481
482 if (!slab.allocSizes.empty()) {
483 size_t usagePercent = 100 * slab.totalUsage / slab.usage.size();
484 size_t freeNextPercent = 100 * slab.nextFree / slab.usage.size();
485
486 // This may mean we're going to leave an allocation hanging. Reset nextFree instead.
487 if (freeNextPercent >= 100 - usagePercent) {
488 size_t newFree = 0;
489 while (newFree < slab.usage.size()) {
490 auto it = slab.allocSizes.find(newFree);
491 if (it == slab.allocSizes.end()) {
492 break;
493 }
494
495 newFree += it->second;
496 }
497
498 slab.nextFree = newFree;
499 }
500 continue;
501 }
502
503 if (!foundFree) {
504 // Let's allow one free slab, so we have room.
505 foundFree = true;
506 continue;
507 }
508
509 // Okay, let's free this one up.
510 vulkan_->Delete().QueueDeleteDeviceMemory(slab.deviceMemory);
511 slabs_.erase(slabs_.begin() + index);
512
513 // Let's check the next one, which is now in this same slot.
514 --i;
515 }
516 }
517