diff --git a/Common/Vulkan/VulkanContext.h b/Common/Vulkan/VulkanContext.h index 7e01e0290e..ab87a7f5ac 100644 --- a/Common/Vulkan/VulkanContext.h +++ b/Common/Vulkan/VulkanContext.h @@ -56,6 +56,15 @@ struct VulkanPhysicalDeviceInfo { // This is a bit repetitive... class VulkanDeleteList { + struct Callback { + explicit Callback(void (*f)(void *userdata), void *u) + : func(f), userdata(u) { + } + + void (*func)(void *userdata); + void *userdata; + }; + public: void QueueDeleteDescriptorPool(VkDescriptorPool pool) { descPools_.push_back(pool); } void QueueDeleteShaderModule(VkShaderModule module) { modules_.push_back(module); } @@ -68,6 +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 *userdata), void *userdata) { callbacks_.push_back(Callback(func, userdata)); } void Take(VulkanDeleteList &del) { assert(descPools_.size() == 0); @@ -81,6 +91,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 +103,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 +151,10 @@ public: vkDestroyFramebuffer(device, framebuffer, nullptr); } framebuffers_.clear(); + for (auto &callback : callbacks_) { + callback.func(callback.userdata); + } + callbacks_.clear(); } private: @@ -153,6 +169,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/VulkanImage.cpp b/Common/Vulkan/VulkanImage.cpp index 86484b0547..8f885015bb 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; @@ -205,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; @@ -220,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); } @@ -234,13 +232,16 @@ 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; } } -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(); @@ -264,22 +265,41 @@ 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); - 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); + if (offset_ == VulkanDeviceAllocator::ALLOCATE_FAILED) { + return false; + } + } 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); - assert(res == VK_SUCCESS); + res = vkAllocateMemory(vulkan_->GetDevice(), &mem_alloc, NULL, &mem); + 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_); + 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, @@ -288,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) { @@ -307,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) { @@ -347,11 +368,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..98c6e9e29a 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() { @@ -26,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_; } @@ -47,4 +50,6 @@ private: VkDeviceMemory mappableMemory; VkMemoryRequirements mem_reqs; bool needStaging; + VulkanDeviceAllocator *allocator_; + size_t offset_; }; diff --git a/Common/Vulkan/VulkanMemory.cpp b/Common/Vulkan/VulkanMemory.cpp index 06097bdaa6..103e2188ac 100644 --- a/Common/Vulkan/VulkanMemory.cpp +++ b/Common/Vulkan/VulkanMemory.cpp @@ -100,3 +100,246 @@ void VulkanPushBuffer::Defragment(VulkanContext *vulkan) { bool res = AddBuffer(); assert(res); } + +VulkanDeviceAllocator::VulkanDeviceAllocator(VulkanContext *vulkan, size_t minSlabSize, size_t maxSlabSize) + : vulkan_(vulkan), lastSlab_(0), minSlabSize_(minSlabSize), maxSlabSize_(maxSlabSize), 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? + 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); + } + 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; + + 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()) { + start = (start + align - 1) & ~(align - 1); + if (AllocateFromSlab(slab, start, blocks)) { + // Allocated? Great, let's return right away. + *deviceMemory = slab.deviceMemory; + lastSlab_ = actualSlab; + 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; + lastSlab_ = slabs_.size() - 1; + 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; + + 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. + 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) { + // 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(this, deviceMemory, offset); + vulkan_->Delete().QueueCallback(&DispatchFree, 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_) { + if (slab.deviceMemory != deviceMemory) { + continue; + } + + auto it = slab.allocSizes.find(start); + 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. + if (!found) { + Crash(); + } + + delete 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_; + + while (alloc.allocationSize < minBytes) { + alloc.allocationSize <<= 1; + } + + VkDeviceMemory deviceMemory; + VkResult res = vkAllocateMemory(vulkan_->GetDevice(), &alloc, NULL, &deviceMemory); + if (res != VK_SUCCESS) { + // 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; + } + + 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) { + // 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; + } + + 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_[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 2838e2b7f3..28cd31325f 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,76 @@ private: uint32_t memoryTypeIndex_; 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() { + Decimate(); + } + + 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; + +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; + } + }; + + struct FreeInfo { + 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 *userdata) { + auto freeInfo = static_cast(userdata); + freeInfo->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 *const vulkan_; + std::vector slabs_; + size_t lastSlab_; + size_t minSlabSize_; + const size_t maxSlabSize_; + uint32_t memoryTypeIndex_; +}; 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 e2b4fa0fe2..831e7c0498 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(); @@ -145,6 +149,7 @@ TextureCacheVulkan::TextureCacheVulkan(VulkanContext *vulkan) TextureCacheVulkan::~TextureCacheVulkan() { Clear(true); + allocator_->Destroy(); } void TextureCacheVulkan::DownloadFramebufferForClut(u32 clutAddr, u32 bytes) { @@ -561,17 +566,24 @@ void TextureCacheVulkan::SetFramebufferSamplingParams(u16 bufferWidth, u16 buffe void TextureCacheVulkan::StartFrame() { lastBoundTexture = nullptr; timesInvalidatedAllThisFrame_ = 0; - - if (texelsScaledThisFrame_) { - // INFO_LOG(G3D, "Scaled %i texels", texelsScaledThisFrame_); - } texelsScaledThisFrame_ = 0; + if (clearCacheNextFrame_) { Clear(true); clearCacheNextFrame_ = false; } else { Decimate(); } + + allocator_->Begin(); +} + +void TextureCacheVulkan::EndFrame() { + allocator_->End(); + + if (texelsScaledThisFrame_) { + // INFO_LOG(G3D, "Scaled %i texels", texelsScaledThisFrame_); + } } static inline u32 MiniHash(const u32 *ptr) { @@ -1312,13 +1324,10 @@ 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_); + entry->vkTex->texture_ = new VulkanTexture(vulkan_, allocator_); VulkanTexture *image = entry->vkTex->texture_; const VkComponentMapping *mapping; @@ -1340,22 +1349,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; @@ -1650,29 +1686,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); - } - }*/ } diff --git a/GPU/Vulkan/TextureCacheVulkan.h b/GPU/Vulkan/TextureCacheVulkan.h index 590ae4edbc..8f4a9b03aa 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) {} @@ -90,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(); @@ -140,6 +142,7 @@ private: void SetFramebufferSamplingParams(u16 bufferWidth, u16 bufferHeight, SamplerCacheKey &key); VulkanContext *vulkan_; + VulkanDeviceAllocator *allocator_; TexCache secondCache; u32 secondCacheSizeEstimate_;