From 16570f10bdc0f565cdc4ebacca5c48ed4a8046b7 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 26 Mar 2016 18:41:53 -0700 Subject: [PATCH] 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); - } - }*/ }