From f0dc921ed5c03690c376f88d3e9f3d2b867af2b3 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 23 Mar 2016 00:40:41 -0700 Subject: [PATCH 01/11] 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_; +}; From 7d5a8aa4700630fb18463959f1b486138fd3a990 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 23 Mar 2016 01:05:20 -0700 Subject: [PATCH 02/11] Vulkan: Protect better against memory alloc errors. --- Common/Vulkan/VulkanMemory.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Common/Vulkan/VulkanMemory.cpp b/Common/Vulkan/VulkanMemory.cpp index 61a842c270..a79e27fbcc 100644 --- a/Common/Vulkan/VulkanMemory.cpp +++ b/Common/Vulkan/VulkanMemory.cpp @@ -235,7 +235,9 @@ bool VulkanDeviceAllocator::AllocateSlab(size_t minBytes) { VkDeviceMemory deviceMemory; VkResult res = vkAllocateMemory(vulkan_->GetDevice(), &alloc, NULL, &deviceMemory); if (res != VK_SUCCESS) { - // Ran out of memory. + // If it's something else, we used it wrong? + assert(res == VK_ERROR_OUT_OF_HOST_MEMORY || res == VK_ERROR_OUT_OF_DEVICE_MEMORY || res == VK_ERROR_TOO_MANY_OBJECTS); + // Okay, so we ran out of memory. return false; } From 973a9f6124cafa4e721373f97848b3f63b71a9f5 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 24 Mar 2016 23:27:14 -0700 Subject: [PATCH 03/11] Vulkan: Delay frees for device memory slabs. --- Common/Vulkan/VulkanContext.h | 18 ++++++++++++ Common/Vulkan/VulkanMemory.cpp | 50 ++++++++++++++++++++++++++++++++-- Common/Vulkan/VulkanMemory.h | 16 +++++++++++ 3 files changed, 81 insertions(+), 3 deletions(-) diff --git a/Common/Vulkan/VulkanContext.h b/Common/Vulkan/VulkanContext.h index 7e01e0290e..bbe30ced57 100644 --- a/Common/Vulkan/VulkanContext.h +++ b/Common/Vulkan/VulkanContext.h @@ -56,6 +56,16 @@ struct VulkanPhysicalDeviceInfo { // This is a bit repetitive... class VulkanDeleteList { + struct Callback { + explicit Callback(void (*f)(void *userdata1, void *userdata2), void *u1, void *u2) + : func(f), userdata1(u1), userdata2(u2) { + } + + void (*func)(void *userdata1, void *userdata2); + void *userdata1; + void *userdata2; + }; + public: void QueueDeleteDescriptorPool(VkDescriptorPool pool) { descPools_.push_back(pool); } void QueueDeleteShaderModule(VkShaderModule module) { modules_.push_back(module); } @@ -68,6 +78,7 @@ public: void QueueDeletePipelineCache(VkPipelineCache pipelineCache) { pipelineCaches_.push_back(pipelineCache); } void QueueDeleteRenderPass(VkRenderPass renderPass) { renderPasses_.push_back(renderPass); } void QueueDeleteFramebuffer(VkFramebuffer framebuffer) { framebuffers_.push_back(framebuffer); } + void QueueCallback(void (*func)(void *userdata1, void *userdata2), void *userdata1, void *userdata2) { callbacks_.push_back(Callback(func, userdata1, userdata2)); } void Take(VulkanDeleteList &del) { assert(descPools_.size() == 0); @@ -81,6 +92,7 @@ public: assert(pipelineCaches_.size() == 0); assert(renderPasses_.size() == 0); assert(framebuffers_.size() == 0); + assert(callbacks_.size() == 0); descPools_ = std::move(del.descPools_); modules_ = std::move(del.modules_); buffers_ = std::move(del.buffers_); @@ -92,6 +104,7 @@ public: pipelineCaches_ = std::move(del.pipelineCaches_); renderPasses_ = std::move(del.renderPasses_); framebuffers_ = std::move(del.framebuffers_); + callbacks_ = std::move(del.callbacks_); } void PerformDeletes(VkDevice device) { @@ -139,6 +152,10 @@ public: vkDestroyFramebuffer(device, framebuffer, nullptr); } framebuffers_.clear(); + for (auto &callback : callbacks_) { + callback.func(callback.userdata1, callback.userdata2); + } + callbacks_.clear(); } private: @@ -153,6 +170,7 @@ private: std::vector pipelineCaches_; std::vector renderPasses_; std::vector framebuffers_; + std::vector callbacks_; }; // VulkanContext sets up the basics necessary for rendering to a window, including framebuffers etc. diff --git a/Common/Vulkan/VulkanMemory.cpp b/Common/Vulkan/VulkanMemory.cpp index a79e27fbcc..9eea7cd13f 100644 --- a/Common/Vulkan/VulkanMemory.cpp +++ b/Common/Vulkan/VulkanMemory.cpp @@ -198,6 +198,45 @@ bool VulkanDeviceAllocator::AllocateFromSlab(Slab &slab, size_t &start, size_t b } void VulkanDeviceAllocator::Free(VkDeviceMemory deviceMemory, size_t offset) { + // First, let's validate. This will allow stack traces to tell us when frees are bad. + 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); + if (it == slab.allocSizes.end()) { + // Ack, a double free? + Crash(); + } + if (slab.usage[start] != 1) { + // This means a double free, while queued to actually free. + Crash(); + } + + // Mark it as "free in progress". + slab.usage[start] = 2; + found = true; + break; + } + + // Wrong deviceMemory even? Maybe it was already decimated, but that means a double-free. + if (!found) { + Crash(); + } + + // Okay, now enqueue. It's valid. + FreeInfo *info = new FreeInfo(deviceMemory, offset); + vulkan_->Delete().QueueCallback(&DispatchFree, this, info); +} + +void VulkanDeviceAllocator::ExecuteFree(FreeInfo *userdata) { + VkDeviceMemory deviceMemory = userdata->deviceMemory; + size_t offset = userdata->offset; + + // Revalidate in case something else got freed and made things inconsistent. size_t start = offset >> SLAB_GRAIN_SHIFT; bool found = false; for (Slab &slab : slabs_) { @@ -206,21 +245,26 @@ void VulkanDeviceAllocator::Free(VkDeviceMemory deviceMemory, size_t offset) { } 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); + } else { + // Ack, a double free? + Crash(); } found = true; break; } // Wrong deviceMemory even? Maybe it was already decimated, but that means a double-free. - assert(found); + if (!found) { + Crash(); + } + + delete userdata; } bool VulkanDeviceAllocator::AllocateSlab(size_t minBytes) { diff --git a/Common/Vulkan/VulkanMemory.h b/Common/Vulkan/VulkanMemory.h index f951359dcc..9956414fa9 100644 --- a/Common/Vulkan/VulkanMemory.h +++ b/Common/Vulkan/VulkanMemory.h @@ -163,9 +163,25 @@ private: } }; + struct FreeInfo { + explicit FreeInfo(VkDeviceMemory d, size_t o) + : deviceMemory(d), offset(o) { + } + + VkDeviceMemory deviceMemory; + size_t offset; + }; + + static void DispatchFree(void *thiz, void *userdata) { + auto allocator = static_cast(thiz); + auto freeInfo = static_cast(userdata); + allocator->ExecuteFree(freeInfo); + } + bool AllocateSlab(size_t minBytes); bool AllocateFromSlab(Slab &slab, size_t &start, size_t blocks); void Decimate(); + void ExecuteFree(FreeInfo *userdata); VulkanContext *vulkan_; std::vector slabs_; From bd7c4315328c7eb54e7b52e294aba9543c5ad1c4 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 24 Mar 2016 23:30:01 -0700 Subject: [PATCH 04/11] Vulkan: Increase new size for new slabs. --- Common/Vulkan/VulkanMemory.cpp | 20 +++++++++++++++----- Common/Vulkan/VulkanMemory.h | 5 +++-- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/Common/Vulkan/VulkanMemory.cpp b/Common/Vulkan/VulkanMemory.cpp index 9eea7cd13f..cf7bbb2868 100644 --- a/Common/Vulkan/VulkanMemory.cpp +++ b/Common/Vulkan/VulkanMemory.cpp @@ -101,8 +101,8 @@ void VulkanPushBuffer::Defragment(VulkanContext *vulkan) { assert(res); } -VulkanDeviceAllocator::VulkanDeviceAllocator(VulkanContext *vulkan, size_t minSlabSize) - : vulkan_(vulkan), minSlabSize_(minSlabSize), memoryTypeIndex_(UNDEFINED_MEMORY_TYPE) { +VulkanDeviceAllocator::VulkanDeviceAllocator(VulkanContext *vulkan, size_t minSlabSize, size_t maxSlabSize) + : vulkan_(vulkan), minSlabSize_(minSlabSize), maxSlabSize_(maxSlabSize), memoryTypeIndex_(UNDEFINED_MEMORY_TYPE) { assert((minSlabSize_ & (SLAB_GRAIN_SIZE - 1)) == 0); } @@ -268,6 +268,11 @@ void VulkanDeviceAllocator::ExecuteFree(FreeInfo *userdata) { } bool VulkanDeviceAllocator::AllocateSlab(size_t minBytes) { + if (!slabs_.empty() && minSlabSize_ < maxSlabSize_) { + // We're allocating an additional slab, so rachet up its size. + minSlabSize_ <<= 1; + } + VkMemoryAllocateInfo alloc = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; alloc.allocationSize = minSlabSize_; alloc.memoryTypeIndex = memoryTypeIndex_; @@ -297,7 +302,11 @@ void VulkanDeviceAllocator::Decimate() { bool foundFree = false; for (size_t i = 0; i < slabs_.size(); ++i) { - if (!slabs_[i].allocSizes.empty()) { + // Go backwards. This way, we keep the largest free slab. + // We do this here (instead of the for) since size_t is unsigned. + size_t index = slabs_.size() - i - 1; + + if (!slabs_[index].allocSizes.empty()) { continue; } @@ -308,8 +317,9 @@ void VulkanDeviceAllocator::Decimate() { } // Okay, let's free this one up. - vulkan_->Delete().QueueDeleteDeviceMemory(slabs_[i].deviceMemory); - slabs_.erase(slabs_.begin() + i); + vulkan_->Delete().QueueDeleteDeviceMemory(slabs_[index].deviceMemory); + slabs_.erase(slabs_.begin() + index); + // 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 9956414fa9..ea0d504c61 100644 --- a/Common/Vulkan/VulkanMemory.h +++ b/Common/Vulkan/VulkanMemory.h @@ -130,7 +130,7 @@ private: class VulkanDeviceAllocator { public: - VulkanDeviceAllocator(VulkanContext *vulkan, size_t minSlabSize); + VulkanDeviceAllocator(VulkanContext *vulkan, size_t minSlabSize, size_t maxSlabSize); ~VulkanDeviceAllocator(); void Destroy(); @@ -183,8 +183,9 @@ private: void Decimate(); void ExecuteFree(FreeInfo *userdata); - VulkanContext *vulkan_; + VulkanContext *const vulkan_; std::vector slabs_; size_t minSlabSize_; + const size_t maxSlabSize_; uint32_t memoryTypeIndex_; }; From 9921fd2ea70bc0986bd33b07aa73176428cb88de Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 25 Mar 2016 23:48:39 -0700 Subject: [PATCH 05/11] Vulkan: Don't bother with extra userdata, comments. Was a failed attempt to avoid new/delete. --- Common/Vulkan/VulkanContext.h | 13 ++++++------- Common/Vulkan/VulkanMemory.cpp | 4 ++-- Common/Vulkan/VulkanMemory.h | 20 +++++++++++++++----- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/Common/Vulkan/VulkanContext.h b/Common/Vulkan/VulkanContext.h index bbe30ced57..ab87a7f5ac 100644 --- a/Common/Vulkan/VulkanContext.h +++ b/Common/Vulkan/VulkanContext.h @@ -57,13 +57,12 @@ struct VulkanPhysicalDeviceInfo { // This is a bit repetitive... class VulkanDeleteList { struct Callback { - explicit Callback(void (*f)(void *userdata1, void *userdata2), void *u1, void *u2) - : func(f), userdata1(u1), userdata2(u2) { + explicit Callback(void (*f)(void *userdata), void *u) + : func(f), userdata(u) { } - void (*func)(void *userdata1, void *userdata2); - void *userdata1; - void *userdata2; + void (*func)(void *userdata); + void *userdata; }; public: @@ -78,7 +77,7 @@ public: void QueueDeletePipelineCache(VkPipelineCache pipelineCache) { pipelineCaches_.push_back(pipelineCache); } void QueueDeleteRenderPass(VkRenderPass renderPass) { renderPasses_.push_back(renderPass); } void QueueDeleteFramebuffer(VkFramebuffer framebuffer) { framebuffers_.push_back(framebuffer); } - void QueueCallback(void (*func)(void *userdata1, void *userdata2), void *userdata1, void *userdata2) { callbacks_.push_back(Callback(func, userdata1, userdata2)); } + void QueueCallback(void (*func)(void *userdata), void *userdata) { callbacks_.push_back(Callback(func, userdata)); } void Take(VulkanDeleteList &del) { assert(descPools_.size() == 0); @@ -153,7 +152,7 @@ public: } framebuffers_.clear(); for (auto &callback : callbacks_) { - callback.func(callback.userdata1, callback.userdata2); + callback.func(callback.userdata); } callbacks_.clear(); } diff --git a/Common/Vulkan/VulkanMemory.cpp b/Common/Vulkan/VulkanMemory.cpp index cf7bbb2868..93541c5249 100644 --- a/Common/Vulkan/VulkanMemory.cpp +++ b/Common/Vulkan/VulkanMemory.cpp @@ -228,8 +228,8 @@ void VulkanDeviceAllocator::Free(VkDeviceMemory deviceMemory, size_t offset) { } // Okay, now enqueue. It's valid. - FreeInfo *info = new FreeInfo(deviceMemory, offset); - vulkan_->Delete().QueueCallback(&DispatchFree, this, info); + FreeInfo *info = new FreeInfo(this, deviceMemory, offset); + vulkan_->Delete().QueueCallback(&DispatchFree, info); } void VulkanDeviceAllocator::ExecuteFree(FreeInfo *userdata) { diff --git a/Common/Vulkan/VulkanMemory.h b/Common/Vulkan/VulkanMemory.h index ea0d504c61..186cc8cddd 100644 --- a/Common/Vulkan/VulkanMemory.h +++ b/Common/Vulkan/VulkanMemory.h @@ -128,11 +128,18 @@ private: uint8_t *writePtr_; }; +// VulkanDeviceAllocator +// +// Implements a slab based allocator that manages suballocations inside the slabs. +// Bitmaps are used to handle allocation state, with a 1KB grain. class VulkanDeviceAllocator { public: + // Slab sizes start at minSlabSize and double until maxSlabSize. + // Total slab count is unlimited, as long as there's free memory. VulkanDeviceAllocator(VulkanContext *vulkan, size_t minSlabSize, size_t maxSlabSize); ~VulkanDeviceAllocator(); + // Requires all memory be free beforehand (including all pending deletes.) void Destroy(); void Begin() { @@ -142,7 +149,10 @@ public: void End() { } + // May return ALLOCATE_FAILED if the allocation fails. size_t Allocate(const VkMemoryRequirements &reqs, VkDeviceMemory *deviceMemory); + + // Crashes on a double or misfree. void Free(VkDeviceMemory deviceMemory, size_t offset); static const size_t ALLOCATE_FAILED = -1; @@ -164,18 +174,18 @@ private: }; struct FreeInfo { - explicit FreeInfo(VkDeviceMemory d, size_t o) - : deviceMemory(d), offset(o) { + explicit FreeInfo(VulkanDeviceAllocator *a, VkDeviceMemory d, size_t o) + : allocator(a), deviceMemory(d), offset(o) { } + VulkanDeviceAllocator *allocator; VkDeviceMemory deviceMemory; size_t offset; }; - static void DispatchFree(void *thiz, void *userdata) { - auto allocator = static_cast(thiz); + static void DispatchFree(void *userdata) { auto freeInfo = static_cast(userdata); - allocator->ExecuteFree(freeInfo); + freeInfo->allocator->ExecuteFree(freeInfo); } bool AllocateSlab(size_t minBytes); From 87d65e696c6400b03628e9c719a5499cb16f6672 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 26 Mar 2016 00:51:55 -0700 Subject: [PATCH 06/11] Vulkan: Correct alloc near slab end. --- Common/Vulkan/VulkanMemory.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Common/Vulkan/VulkanMemory.cpp b/Common/Vulkan/VulkanMemory.cpp index 93541c5249..2424cf3cc2 100644 --- a/Common/Vulkan/VulkanMemory.cpp +++ b/Common/Vulkan/VulkanMemory.cpp @@ -169,6 +169,12 @@ size_t VulkanDeviceAllocator::Allocate(const VkMemoryRequirements &reqs, VkDevic bool VulkanDeviceAllocator::AllocateFromSlab(Slab &slab, size_t &start, size_t blocks) { bool matched = true; + + if (start + blocks > slab.usage.size()) { + start = slab.usage.size(); + return false; + } + 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. From 62b64321211bbf9d036435ff71b37a4fb5b2b714 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 26 Mar 2016 18:16:39 -0700 Subject: [PATCH 07/11] Vulkan: Allocate forwards in device slabs. --- Common/Vulkan/VulkanMemory.cpp | 11 +++++++++-- Common/Vulkan/VulkanMemory.h | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Common/Vulkan/VulkanMemory.cpp b/Common/Vulkan/VulkanMemory.cpp index 2424cf3cc2..a8c3b48f1d 100644 --- a/Common/Vulkan/VulkanMemory.cpp +++ b/Common/Vulkan/VulkanMemory.cpp @@ -102,7 +102,7 @@ void VulkanPushBuffer::Defragment(VulkanContext *vulkan) { } VulkanDeviceAllocator::VulkanDeviceAllocator(VulkanContext *vulkan, size_t minSlabSize, size_t maxSlabSize) - : vulkan_(vulkan), minSlabSize_(minSlabSize), maxSlabSize_(maxSlabSize), memoryTypeIndex_(UNDEFINED_MEMORY_TYPE) { + : vulkan_(vulkan), lastSlab_(0), minSlabSize_(minSlabSize), maxSlabSize_(maxSlabSize), memoryTypeIndex_(UNDEFINED_MEMORY_TYPE) { assert((minSlabSize_ & (SLAB_GRAIN_SIZE - 1)) == 0); } @@ -137,7 +137,12 @@ size_t VulkanDeviceAllocator::Allocate(const VkMemoryRequirements &reqs, VkDevic 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_) { + const size_t numSlabs = slabs_.size(); + for (size_t i = 0; i < numSlabs; ++i) { + // We loop starting at the last successful allocation. + // This helps us "creep forward", and also spend less time allocating. + const size_t actualSlab = (lastSlab_ + i) % numSlabs; + Slab &slab = slabs_[actualSlab]; size_t start = slab.nextFree; while (start < slab.usage.size()) { @@ -145,6 +150,7 @@ size_t VulkanDeviceAllocator::Allocate(const VkMemoryRequirements &reqs, VkDevic if (AllocateFromSlab(slab, start, blocks)) { // Allocated? Great, let's return right away. *deviceMemory = slab.deviceMemory; + lastSlab_ = actualSlab; return start << SLAB_GRAIN_SHIFT; } } @@ -160,6 +166,7 @@ size_t VulkanDeviceAllocator::Allocate(const VkMemoryRequirements &reqs, VkDevic size_t start = 0; if (AllocateFromSlab(slab, start, blocks)) { *deviceMemory = slab.deviceMemory; + lastSlab_ = slabs_.size() - 1; return start << SLAB_GRAIN_SHIFT; } diff --git a/Common/Vulkan/VulkanMemory.h b/Common/Vulkan/VulkanMemory.h index 186cc8cddd..28cd31325f 100644 --- a/Common/Vulkan/VulkanMemory.h +++ b/Common/Vulkan/VulkanMemory.h @@ -195,6 +195,7 @@ private: VulkanContext *const vulkan_; std::vector slabs_; + size_t lastSlab_; size_t minSlabSize_; const size_t maxSlabSize_; uint32_t memoryTypeIndex_; From 27a5697a96563f5a21d199441533e3c3d7c9b7c8 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 26 Mar 2016 18:22:21 -0700 Subject: [PATCH 08/11] Vulkan: Use the slab allocator for textures. --- Common/Vulkan/VulkanImage.cpp | 35 +++++++++++++++++++++---------- Common/Vulkan/VulkanImage.h | 9 ++++++-- GPU/Vulkan/TextureCacheVulkan.cpp | 14 ++++++++++--- GPU/Vulkan/TextureCacheVulkan.h | 2 ++ 4 files changed, 44 insertions(+), 16 deletions(-) diff --git a/Common/Vulkan/VulkanImage.cpp b/Common/Vulkan/VulkanImage.cpp index 86484b0547..7c403cf9f3 100644 --- a/Common/Vulkan/VulkanImage.cpp +++ b/Common/Vulkan/VulkanImage.cpp @@ -1,4 +1,5 @@ #include "Common/Vulkan/VulkanImage.h" +#include "Common/Vulkan/VulkanMemory.h" VkResult VulkanTexture::Create(int w, int h, VkFormat format) { tex_width = w; @@ -234,9 +235,12 @@ void VulkanTexture::Wipe() { vulkan_->Delete().QueueDeleteImageView(view); view = VK_NULL_HANDLE; } - if (mem) { + if (mem && !allocator_) { vulkan_->Delete().QueueDeleteDeviceMemory(mem); mem = VK_NULL_HANDLE; + } else if (mem) { + allocator_->Free(mem, offset_); + mem = VK_NULL_HANDLE; } } @@ -267,18 +271,25 @@ void VulkanTexture::CreateDirect(int w, int h, int numMips, VkFormat format, VkI assert(res == VK_SUCCESS); vkGetImageMemoryRequirements(vulkan_->GetDevice(), image, &mem_reqs); - VkMemoryAllocateInfo mem_alloc = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; - mem_alloc.memoryTypeIndex = 0; - mem_alloc.allocationSize = mem_reqs.size; - // Find memory type - don't specify any mapping requirements - bool pass = vulkan_->MemoryTypeFromProperties(mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &mem_alloc.memoryTypeIndex); - assert(pass); + if (allocator_) { + offset_ = allocator_->Allocate(mem_reqs, &mem); + } else { + VkMemoryAllocateInfo mem_alloc = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; + mem_alloc.memoryTypeIndex = 0; + mem_alloc.allocationSize = mem_reqs.size; - res = vkAllocateMemory(vulkan_->GetDevice(), &mem_alloc, NULL, &mem); - assert(res == VK_SUCCESS); + // Find memory type - don't specify any mapping requirements + bool pass = vulkan_->MemoryTypeFromProperties(mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &mem_alloc.memoryTypeIndex); + assert(pass); - res = vkBindImageMemory(vulkan_->GetDevice(), image, mem, 0); + res = vkAllocateMemory(vulkan_->GetDevice(), &mem_alloc, NULL, &mem); + assert(res == VK_SUCCESS); + + offset_ = 0; + } + + res = vkBindImageMemory(vulkan_->GetDevice(), image, mem, offset_); assert(res == VK_SUCCESS); // Since we're going to blit to the target, set its layout to TRANSFER_DST @@ -347,11 +358,13 @@ void VulkanTexture::Destroy() { mappableImage = VK_NULL_HANDLE; } } - if (mem) { + if (mem && !allocator_) { vulkan_->Delete().QueueDeleteDeviceMemory(mem); if (mappableMemory == mem) { mappableMemory = VK_NULL_HANDLE; } + } else if (mem) { + allocator_->Free(mem, offset_); } view = VK_NULL_HANDLE; diff --git a/Common/Vulkan/VulkanImage.h b/Common/Vulkan/VulkanImage.h index 3b0a3ece81..9b8af3179f 100644 --- a/Common/Vulkan/VulkanImage.h +++ b/Common/Vulkan/VulkanImage.h @@ -2,14 +2,17 @@ #include "Common/Vulkan/VulkanContext.h" +class VulkanDeviceAllocator; + // Wrapper around what you need to use a texture. // Not very optimal - if you have many small textures you should use other strategies. class VulkanTexture { public: - VulkanTexture(VulkanContext *vulkan) + VulkanTexture(VulkanContext *vulkan, VulkanDeviceAllocator *allocator = nullptr) : vulkan_(vulkan), image(VK_NULL_HANDLE), mem(VK_NULL_HANDLE), view(VK_NULL_HANDLE), tex_width(0), tex_height(0), numMips_(1), format_(VK_FORMAT_UNDEFINED), - mappableImage(VK_NULL_HANDLE), mappableMemory(VK_NULL_HANDLE), needStaging(false) { + mappableImage(VK_NULL_HANDLE), mappableMemory(VK_NULL_HANDLE), needStaging(false), + allocator_(allocator), offset_(0) { memset(&mem_reqs, 0, sizeof(mem_reqs)); } ~VulkanTexture() { @@ -47,4 +50,6 @@ private: VkDeviceMemory mappableMemory; VkMemoryRequirements mem_reqs; bool needStaging; + VulkanDeviceAllocator *allocator_; + size_t offset_; }; diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index e2b4fa0fe2..66f608c3ce 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -66,8 +66,11 @@ #define TEXCACHE_MAX_TEXELS_SCALED (256*256) // Per frame -#define TEXCACHE_MIN_PRESSURE 16 * 1024 * 1024 // Total in GL -#define TEXCACHE_SECOND_MIN_PRESSURE 4 * 1024 * 1024 +#define TEXCACHE_MIN_PRESSURE (16 * 1024 * 1024) // Total in GL +#define TEXCACHE_SECOND_MIN_PRESSURE (4 * 1024 * 1024) + +#define TEXCACHE_MIN_SLAB_SIZE (4 * 1024 * 1024) +#define TEXCACHE_MAX_SLAB_SIZE (32 * 1024 * 1024) // Note: some drivers prefer B4G4R4A4_UNORM_PACK16 over R4G4B4A4_UNORM_PACK16. #define VULKAN_4444_FORMAT VK_FORMAT_B4G4R4A4_UNORM_PACK16 @@ -137,6 +140,7 @@ TextureCacheVulkan::TextureCacheVulkan(VulkanContext *vulkan) timesInvalidatedAllThisFrame_ = 0; lastBoundTexture = nullptr; decimationCounter_ = TEXCACHE_DECIMATION_INTERVAL; + allocator_ = new VulkanDeviceAllocator(vulkan_, TEXCACHE_MIN_SLAB_SIZE, TEXCACHE_MAX_SLAB_SIZE); SetupTextureDecoder(); @@ -559,6 +563,8 @@ void TextureCacheVulkan::SetFramebufferSamplingParams(u16 bufferWidth, u16 buffe } void TextureCacheVulkan::StartFrame() { + // TODO: Better place? + allocator_->End(); lastBoundTexture = nullptr; timesInvalidatedAllThisFrame_ = 0; @@ -572,6 +578,8 @@ void TextureCacheVulkan::StartFrame() { } else { Decimate(); } + + allocator_->Begin(); } static inline u32 MiniHash(const u32 *ptr) { @@ -1318,7 +1326,7 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) { } } else { entry->vkTex = new CachedTextureVulkan(); - entry->vkTex->texture_ = new VulkanTexture(vulkan_); + entry->vkTex->texture_ = new VulkanTexture(vulkan_, allocator_); VulkanTexture *image = entry->vkTex->texture_; const VkComponentMapping *mapping; diff --git a/GPU/Vulkan/TextureCacheVulkan.h b/GPU/Vulkan/TextureCacheVulkan.h index 590ae4edbc..f47164d77a 100644 --- a/GPU/Vulkan/TextureCacheVulkan.h +++ b/GPU/Vulkan/TextureCacheVulkan.h @@ -35,6 +35,7 @@ class DrawEngineVulkan; class VulkanContext; class VulkanTexture; class VulkanPushBuffer; +class VulkanDeviceAllocator; struct SamplerCacheKey { SamplerCacheKey() : fullKey(0) {} @@ -140,6 +141,7 @@ private: void SetFramebufferSamplingParams(u16 bufferWidth, u16 bufferHeight, SamplerCacheKey &key); VulkanContext *vulkan_; + VulkanDeviceAllocator *allocator_; TexCache secondCache; u32 secondCacheSizeEstimate_; From 16570f10bdc0f565cdc4ebacca5c48ed4a8046b7 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 26 Mar 2016 18:41:53 -0700 Subject: [PATCH 09/11] Vulkan: Handle texture allocation failure. Users hit out of memory even using desktop GL devices, and it will definitely be possible on mobile and desktop Vulkan. --- Common/Vulkan/VulkanImage.cpp | 40 +++++++++------ Common/Vulkan/VulkanImage.h | 2 +- GPU/Vulkan/TextureCacheVulkan.cpp | 85 +++++++++++++++---------------- 3 files changed, 68 insertions(+), 59 deletions(-) diff --git a/Common/Vulkan/VulkanImage.cpp b/Common/Vulkan/VulkanImage.cpp index 7c403cf9f3..8f885015bb 100644 --- a/Common/Vulkan/VulkanImage.cpp +++ b/Common/Vulkan/VulkanImage.cpp @@ -206,10 +206,8 @@ void VulkanTexture::Unlock() { mappableMemory = VK_NULL_HANDLE; } - VkImageViewCreateInfo view_info = {}; - view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - view_info.pNext = NULL; - view_info.image = VK_NULL_HANDLE; + VkImageViewCreateInfo view_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; + view_info.image = image; view_info.viewType = VK_IMAGE_VIEW_TYPE_2D; view_info.format = format_; view_info.components.r = VK_COMPONENT_SWIZZLE_R; @@ -221,7 +219,6 @@ void VulkanTexture::Unlock() { view_info.subresourceRange.levelCount = 1; view_info.subresourceRange.baseArrayLayer = 0; view_info.subresourceRange.layerCount = 1; - view_info.image = image; VkResult res = vkCreateImageView(vulkan_->GetDevice(), &view_info, NULL, &view); assert(res == VK_SUCCESS); } @@ -244,7 +241,7 @@ void VulkanTexture::Wipe() { } } -void VulkanTexture::CreateDirect(int w, int h, int numMips, VkFormat format, VkImageUsageFlags usage, const VkComponentMapping *mapping) { +bool VulkanTexture::CreateDirect(int w, int h, int numMips, VkFormat format, VkImageUsageFlags usage, const VkComponentMapping *mapping) { Wipe(); VkCommandBuffer cmd = vulkan_->GetInitCommandBuffer(); @@ -268,12 +265,18 @@ void VulkanTexture::CreateDirect(int w, int h, int numMips, VkFormat format, VkI image_create_info.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; VkResult res = vkCreateImage(vulkan_->GetDevice(), &image_create_info, NULL, &image); - assert(res == VK_SUCCESS); + if (res != VK_SUCCESS) { + assert(res == VK_ERROR_OUT_OF_HOST_MEMORY || res == VK_ERROR_OUT_OF_DEVICE_MEMORY || res == VK_ERROR_TOO_MANY_OBJECTS); + return false; + } vkGetImageMemoryRequirements(vulkan_->GetDevice(), image, &mem_reqs); if (allocator_) { offset_ = allocator_->Allocate(mem_reqs, &mem); + if (offset_ == VulkanDeviceAllocator::ALLOCATE_FAILED) { + return false; + } } else { VkMemoryAllocateInfo mem_alloc = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; mem_alloc.memoryTypeIndex = 0; @@ -284,13 +287,19 @@ void VulkanTexture::CreateDirect(int w, int h, int numMips, VkFormat format, VkI assert(pass); res = vkAllocateMemory(vulkan_->GetDevice(), &mem_alloc, NULL, &mem); - assert(res == VK_SUCCESS); + if (res != VK_SUCCESS) { + assert(res == VK_ERROR_OUT_OF_HOST_MEMORY || res == VK_ERROR_OUT_OF_DEVICE_MEMORY || res == VK_ERROR_TOO_MANY_OBJECTS); + return false; + } offset_ = 0; } res = vkBindImageMemory(vulkan_->GetDevice(), image, mem, offset_); - assert(res == VK_SUCCESS); + if (res != VK_SUCCESS) { + assert(res == VK_ERROR_OUT_OF_HOST_MEMORY || res == VK_ERROR_OUT_OF_DEVICE_MEMORY || res == VK_ERROR_TOO_MANY_OBJECTS); + return false; + } // Since we're going to blit to the target, set its layout to TRANSFER_DST TransitionImageLayout(cmd, image, @@ -299,10 +308,8 @@ void VulkanTexture::CreateDirect(int w, int h, int numMips, VkFormat format, VkI VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); // Create the view while we're at it. - VkImageViewCreateInfo view_info = {}; - view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - view_info.pNext = NULL; - view_info.image = VK_NULL_HANDLE; + VkImageViewCreateInfo view_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; + view_info.image = image; view_info.viewType = VK_IMAGE_VIEW_TYPE_2D; view_info.format = format_; if (mapping) { @@ -318,9 +325,12 @@ void VulkanTexture::CreateDirect(int w, int h, int numMips, VkFormat format, VkI view_info.subresourceRange.levelCount = numMips; view_info.subresourceRange.baseArrayLayer = 0; view_info.subresourceRange.layerCount = 1; - view_info.image = image; res = vkCreateImageView(vulkan_->GetDevice(), &view_info, NULL, &view); - assert(res == VK_SUCCESS); + if (res != VK_SUCCESS) { + assert(res == VK_ERROR_OUT_OF_HOST_MEMORY || res == VK_ERROR_OUT_OF_DEVICE_MEMORY || res == VK_ERROR_TOO_MANY_OBJECTS); + return false; + } + return true; } void VulkanTexture::UploadMip(int mip, int mipWidth, int mipHeight, VkBuffer buffer, uint32_t offset, size_t rowLength) { diff --git a/Common/Vulkan/VulkanImage.h b/Common/Vulkan/VulkanImage.h index 9b8af3179f..98c6e9e29a 100644 --- a/Common/Vulkan/VulkanImage.h +++ b/Common/Vulkan/VulkanImage.h @@ -29,7 +29,7 @@ public: void Unlock(); // Fast uploads from buffer. Mipmaps supported. Usage must at least include VK_IMAGE_USAGE_TRANSFER_DST_BIT in order to use UploadMip. - void CreateDirect(int w, int h, int numMips, VkFormat format, VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, const VkComponentMapping *mapping = nullptr); + bool CreateDirect(int w, int h, int numMips, VkFormat format, VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, const VkComponentMapping *mapping = nullptr); void UploadMip(int mip, int mipWidth, int mipHeight, VkBuffer buffer, uint32_t offset, size_t rowLength); // rowLength is in pixels void EndCreate(); int GetNumMips() const { return numMips_; } diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index 66f608c3ce..07fff5b81b 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -1320,11 +1320,8 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) { VkFormat actualFmt = scaleFactor > 1 ? VULKAN_8888_FORMAT : dstFmt; - if (replaceImages) { - if (!entry->vkTex) { - Crash(); - } - } else { + if (!replaceImages || !entry->vkTex) { + delete entry->vkTex; entry->vkTex = new CachedTextureVulkan(); entry->vkTex->texture_ = new VulkanTexture(vulkan_, allocator_); VulkanTexture *image = entry->vkTex->texture_; @@ -1348,22 +1345,49 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) { break; } - image->CreateDirect(w * scaleFactor, h * scaleFactor, maxLevel + 1, actualFmt, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, mapping); + bool allocSuccess = image->CreateDirect(w * scaleFactor, h * scaleFactor, maxLevel + 1, actualFmt, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, mapping); + if (!allocSuccess && !lowMemoryMode_) { + WARN_LOG_REPORT(G3D, "Texture cache ran out of GPU memory; switching to low memory mode"); + lowMemoryMode_ = true; + decimationCounter_ = 0; + Decimate(); + // TODO: We should stall the GPU here and wipe things out of memory. + // As is, it will almost definitely fail the second time, but next frame it may recover. + + I18NCategory *err = GetI18NCategory("Error"); + if (scaleFactor > 1) { + osm.Show(err->T("Warning: Video memory FULL, reducing upscaling and switching to slow caching mode"), 2.0f); + } else { + osm.Show(err->T("Warning: Video memory FULL, switching to slow caching mode"), 2.0f); + } + + scaleFactor = 1; + actualFmt = dstFmt; + + allocSuccess = image->CreateDirect(w * scaleFactor, h * scaleFactor, maxLevel + 1, actualFmt, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, mapping); + } + + if (!allocSuccess) { + delete entry->vkTex; + entry->vkTex = nullptr; + } } lastBoundTexture = entry->vkTex; - // Upload the texture data. - for (int i = 0; i <= maxLevel; i++) { - int mipWidth = gstate.getTextureWidth(i) * scaleFactor; - int mipHeight = gstate.getTextureHeight(i) * scaleFactor; - int bpp = actualFmt == VULKAN_8888_FORMAT ? 4 : 2; - int stride = (mipWidth * bpp + 15) & ~15; - int size = stride * mipHeight; - uint32_t bufferOffset; - VkBuffer texBuf; - void *data = uploadBuffer->Push(size, &bufferOffset, &texBuf); - LoadTextureLevel(*entry, (uint8_t *)data, stride, i, replaceImages, scaleFactor, dstFmt); - entry->vkTex->texture_->UploadMip(i, mipWidth, mipHeight, texBuf, bufferOffset, stride / bpp); + if (entry->vkTex) { + // Upload the texture data. + for (int i = 0; i <= maxLevel; i++) { + int mipWidth = gstate.getTextureWidth(i) * scaleFactor; + int mipHeight = gstate.getTextureHeight(i) * scaleFactor; + int bpp = actualFmt == VULKAN_8888_FORMAT ? 4 : 2; + int stride = (mipWidth * bpp + 15) & ~15; + int size = stride * mipHeight; + uint32_t bufferOffset; + VkBuffer texBuf; + void *data = uploadBuffer->Push(size, &bufferOffset, &texBuf); + LoadTextureLevel(*entry, (uint8_t *)data, stride, i, replaceImages, scaleFactor, dstFmt); + entry->vkTex->texture_->UploadMip(i, mipWidth, mipHeight, texBuf, bufferOffset, stride / bpp); + } } gstate_c.textureFullAlpha = entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL; @@ -1658,29 +1682,4 @@ void TextureCacheVulkan::LoadTextureLevel(TexCacheEntry &entry, uint8_t *writePt memcpy(writePtr + rowPitch * y, (const uint8_t *)pixelData + decPitch * y, rowBytes); } } - - /* - if (!lowMemoryMode_) { - GLenum err = glGetError(); - if (err == GL_OUT_OF_MEMORY) { - WARN_LOG_REPORT(G3D, "Texture cache ran out of GPU memory; switching to low memory mode"); - lowMemoryMode_ = true; - decimationCounter_ = 0; - Decimate(); - // TODO: We need to stall the GPU here and wipe things out of memory. - - // Try again, now that we've cleared out textures in lowMemoryMode_. - glTexImage2D(GL_TEXTURE_2D, level, components, w, h, 0, components2, dstFmt, pixelData); - - I18NCategory *err = GetI18NCategory("Error"); - if (scaleFactor > 1) { - osm.Show(err->T("Warning: Video memory FULL, reducing upscaling and switching to slow caching mode"), 2.0f); - } else { - osm.Show(err->T("Warning: Video memory FULL, switching to slow caching mode"), 2.0f); - } - } else if (err != GL_NO_ERROR) { - // We checked the err anyway, might as well log if there is one. - WARN_LOG(G3D, "Got an error in texture upload: %08x", err); - } - }*/ } From 3352608027a342e5bd632dad1f275fcad054c34b Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 26 Mar 2016 20:19:58 -0700 Subject: [PATCH 10/11] Vulkan: Free texture slabs on exit. --- Common/Vulkan/VulkanMemory.cpp | 8 +++++++- GPU/Vulkan/TextureCacheVulkan.cpp | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Common/Vulkan/VulkanMemory.cpp b/Common/Vulkan/VulkanMemory.cpp index a8c3b48f1d..103e2188ac 100644 --- a/Common/Vulkan/VulkanMemory.cpp +++ b/Common/Vulkan/VulkanMemory.cpp @@ -113,7 +113,13 @@ VulkanDeviceAllocator::~VulkanDeviceAllocator() { void VulkanDeviceAllocator::Destroy() { for (Slab &slab : slabs_) { // Did anyone forget to free? - assert(slab.allocSizes.empty()); + for (auto pair : slab.allocSizes) { + if (slab.usage[pair.first] != 2) { + // If it's not 2 (queued), there's a problem. + // If it's zero, it means allocSizes is somehow out of sync. + Crash(); + } + } vulkan_->Delete().QueueDeleteDeviceMemory(slab.deviceMemory); } diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index 07fff5b81b..d552f07dbb 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -149,6 +149,7 @@ TextureCacheVulkan::TextureCacheVulkan(VulkanContext *vulkan) TextureCacheVulkan::~TextureCacheVulkan() { Clear(true); + allocator_->Destroy(); } void TextureCacheVulkan::DownloadFramebufferForClut(u32 clutAddr, u32 bytes) { From ed41785692ae5f27e5e17353cd628435663c7141 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 27 Mar 2016 08:33:22 -0700 Subject: [PATCH 11/11] Vulkan: Add EndFrame to texture cache. --- GPU/Vulkan/GPU_Vulkan.cpp | 1 + GPU/Vulkan/TextureCacheVulkan.cpp | 15 +++++++++------ GPU/Vulkan/TextureCacheVulkan.h | 1 + 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index bfe8f31117..93413cb4b5 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -520,6 +520,7 @@ void GPU_Vulkan::BeginHostFrame() { void GPU_Vulkan::EndHostFrame() { drawEngine_.EndFrame(); framebufferManager_->EndFrame(); + textureCache_.EndFrame(); } // Needs to be called on GPU thread, not reporting thread. diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index d552f07dbb..831e7c0498 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -564,15 +564,10 @@ void TextureCacheVulkan::SetFramebufferSamplingParams(u16 bufferWidth, u16 buffe } void TextureCacheVulkan::StartFrame() { - // TODO: Better place? - allocator_->End(); lastBoundTexture = nullptr; timesInvalidatedAllThisFrame_ = 0; - - if (texelsScaledThisFrame_) { - // INFO_LOG(G3D, "Scaled %i texels", texelsScaledThisFrame_); - } texelsScaledThisFrame_ = 0; + if (clearCacheNextFrame_) { Clear(true); clearCacheNextFrame_ = false; @@ -583,6 +578,14 @@ void TextureCacheVulkan::StartFrame() { allocator_->Begin(); } +void TextureCacheVulkan::EndFrame() { + allocator_->End(); + + if (texelsScaledThisFrame_) { + // INFO_LOG(G3D, "Scaled %i texels", texelsScaledThisFrame_); + } +} + static inline u32 MiniHash(const u32 *ptr) { return ptr[0]; } diff --git a/GPU/Vulkan/TextureCacheVulkan.h b/GPU/Vulkan/TextureCacheVulkan.h index f47164d77a..8f4a9b03aa 100644 --- a/GPU/Vulkan/TextureCacheVulkan.h +++ b/GPU/Vulkan/TextureCacheVulkan.h @@ -91,6 +91,7 @@ public: void Clear(bool delete_them); void StartFrame(); + void EndFrame(); void Invalidate(u32 addr, int size, GPUInvalidationType type); void InvalidateAll(GPUInvalidationType type); void ClearNextFrame();