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