From 9d11c8cbb5bac60b124aeca720ccdf5b7c90cba2 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Mon, 26 Jul 2021 21:12:05 +0300 Subject: [PATCH] vk: Allow creating temporary subresources to fail if we run out of memory. --- rpcs3/Emu/RSX/VK/VKTextureCache.cpp | 32 +++++++++++++++++++++++++++- rpcs3/Emu/RSX/VK/vkutils/image.cpp | 20 +++++++++++++---- rpcs3/Emu/RSX/VK/vkutils/image.h | 3 ++- rpcs3/Emu/RSX/VK/vkutils/memory.cpp | 32 +++++++++++++++++++++------- rpcs3/Emu/RSX/VK/vkutils/memory.h | 8 +++---- rpcs3/Emu/RSX/VK/vkutils/scratch.cpp | 9 +++++++- 6 files changed, 85 insertions(+), 19 deletions(-) diff --git a/rpcs3/Emu/RSX/VK/VKTextureCache.cpp b/rpcs3/Emu/RSX/VK/VKTextureCache.cpp index b9c2c055f7..eb3d187696 100644 --- a/rpcs3/Emu/RSX/VK/VKTextureCache.cpp +++ b/rpcs3/Emu/RSX/VK/VKTextureCache.cpp @@ -505,8 +505,14 @@ namespace vk image_type, dst_format, w, h, d, mips, layers, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_LAYOUT_UNDEFINED, - VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, image_flags, + VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, image_flags | VK_IMAGE_CREATE_ALLOW_NULL, VMM_ALLOCATION_POOL_TEXTURE_CACHE, rsx::classify_format(gcm_format)); + + if (!image->value) + { + // OOM, bail + return nullptr; + } } // This method is almost exclusively used to work on framebuffer resources @@ -572,6 +578,12 @@ namespace vk auto result = create_temporary_subresource_view_impl(cmd, _template, VK_IMAGE_TYPE_2D, VK_IMAGE_VIEW_TYPE_CUBE, gcm_format, 0, 0, size, size, 1, 1, remap_vector, false); + if (!result) + { + // Failed to create temporary object, bail + return nullptr; + } + const auto image = result->image(); VkImageAspectFlags dst_aspect = vk::get_aspect_flags(result->info.format); VkImageSubresourceRange dst_range = { dst_aspect, 0, 1, 0, 6 }; @@ -601,6 +613,12 @@ namespace vk auto result = create_temporary_subresource_view_impl(cmd, _template, VK_IMAGE_TYPE_3D, VK_IMAGE_VIEW_TYPE_3D, gcm_format, 0, 0, width, height, depth, 1, remap_vector, false); + if (!result) + { + // Failed to create temporary object, bail + return nullptr; + } + const auto image = result->image(); VkImageAspectFlags dst_aspect = vk::get_aspect_flags(result->info.format); VkImageSubresourceRange dst_range = { dst_aspect, 0, 1, 0, 1 }; @@ -630,6 +648,12 @@ namespace vk auto result = create_temporary_subresource_view_impl(cmd, _template, VK_IMAGE_TYPE_2D, VK_IMAGE_VIEW_TYPE_2D, gcm_format, 0, 0, width, height, 1, 1, remap_vector, false); + if (!result) + { + // Failed to create temporary object, bail + return nullptr; + } + const auto image = result->image(); VkImageAspectFlags dst_aspect = vk::get_aspect_flags(result->info.format); VkImageSubresourceRange dst_range = { dst_aspect, 0, 1, 0, 1 }; @@ -660,6 +684,12 @@ namespace vk auto result = create_temporary_subresource_view_impl(cmd, _template, VK_IMAGE_TYPE_2D, VK_IMAGE_VIEW_TYPE_2D, gcm_format, 0, 0, width, height, 1, mipmaps, remap_vector, false); + if (!result) + { + // Failed to create temporary object, bail + return nullptr; + } + const auto image = result->image(); VkImageAspectFlags dst_aspect = vk::get_aspect_flags(result->info.format); VkImageSubresourceRange dst_range = { dst_aspect, 0, mipmaps, 0, 1 }; diff --git a/rpcs3/Emu/RSX/VK/vkutils/image.cpp b/rpcs3/Emu/RSX/VK/vkutils/image.cpp index 387d4c040d..b2ed85dd8f 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/image.cpp +++ b/rpcs3/Emu/RSX/VK/vkutils/image.cpp @@ -100,6 +100,9 @@ namespace vk ensure(!value && !memory); validate(dev, info); + const bool nullable = !!(info.flags & VK_IMAGE_CREATE_ALLOW_NULL); + info.flags &= ~VK_IMAGE_CREATE_ALLOW_NULL; + CHECK_RESULT(vkCreateImage(m_device, &info, nullptr, &value)); VkMemoryRequirements memory_req; @@ -111,10 +114,19 @@ namespace vk fmt::throw_exception("No compatible memory type was found!"); } - memory = std::make_shared(m_device, memory_req.size, memory_req.alignment, allocation_type_info, allocation_pool); - CHECK_RESULT(vkBindImageMemory(m_device, value, memory->get_vk_device_memory(), memory->get_vk_device_memory_offset())); - - current_layout = info.initialLayout; + memory = std::make_shared(m_device, memory_req.size, memory_req.alignment, allocation_type_info, allocation_pool, nullable); + if (auto device_mem = memory->get_vk_device_memory(); + device_mem != VK_NULL_HANDLE) [[likely]] + { + CHECK_RESULT(vkBindImageMemory(m_device, value, device_mem, memory->get_vk_device_memory_offset())); + current_layout = info.initialLayout; + } + else + { + ensure(nullable); + vkDestroyImage(m_device, value, nullptr); + value = VK_NULL_HANDLE; + } } u32 image::width() const diff --git a/rpcs3/Emu/RSX/VK/vkutils/image.h b/rpcs3/Emu/RSX/VK/vkutils/image.h index 360e83b3b2..3299b8f4c9 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/image.h +++ b/rpcs3/Emu/RSX/VK/vkutils/image.h @@ -23,7 +23,8 @@ namespace vk enum : u32// special remap_encoding enums { VK_REMAP_IDENTITY = 0xCAFEBABE, // Special view encoding to return an identity image view - VK_REMAP_VIEW_MULTISAMPLED = 0xDEADBEEF // Special encoding for multisampled images; returns a multisampled image view + VK_REMAP_VIEW_MULTISAMPLED = 0xDEADBEEF, // Special encoding for multisampled images; returns a multisampled image view + VK_IMAGE_CREATE_ALLOW_NULL = 0x80000000, // Special flag that allows null images to be created if there is no memory }; class image diff --git a/rpcs3/Emu/RSX/VK/vkutils/memory.cpp b/rpcs3/Emu/RSX/VK/vkutils/memory.cpp index 29fe3c1b7f..839387c743 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/memory.cpp +++ b/rpcs3/Emu/RSX/VK/vkutils/memory.cpp @@ -150,7 +150,7 @@ namespace vk vmaDestroyAllocator(m_allocator); } - mem_allocator_vk::mem_handle_t mem_allocator_vma::alloc(u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool) + mem_allocator_vk::mem_handle_t mem_allocator_vma::alloc(u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool, bool throw_on_fail) { VmaAllocation vma_alloc; VkMemoryRequirements mem_req = {}; @@ -187,8 +187,9 @@ namespace vk } } + const auto severity = (throw_on_fail) ? rsx::problem_severity::fatal : rsx::problem_severity::severe; if (error_code == VK_ERROR_OUT_OF_DEVICE_MEMORY && - vmm_handle_memory_pressure(rsx::problem_severity::fatal)) + vmm_handle_memory_pressure(severity)) { // Out of memory. Try again. const auto [status, type] = do_vma_alloc(); @@ -200,6 +201,11 @@ namespace vk } } + if (!throw_on_fail) + { + return VK_NULL_HANDLE; + } + die_with_error(error_code); fmt::throw_exception("Unreachable! Error_code=0x%x", static_cast(error_code)); } @@ -228,8 +234,12 @@ namespace vk VkDeviceMemory mem_allocator_vma::get_vk_device_memory(mem_handle_t mem_handle) { - VmaAllocationInfo alloc_info; + if (!mem_handle) + { + return VK_NULL_HANDLE; + } + VmaAllocationInfo alloc_info; vmaGetAllocationInfo(m_allocator, static_cast(mem_handle), &alloc_info); return alloc_info.deviceMemory; } @@ -271,7 +281,7 @@ namespace vk m_allocation_flags = VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT; } - mem_allocator_vk::mem_handle_t mem_allocator_vk::alloc(u64 block_sz, u64 /*alignment*/, const memory_type_info& memory_type, vmm_allocation_pool pool) + mem_allocator_vk::mem_handle_t mem_allocator_vk::alloc(u64 block_sz, u64 /*alignment*/, const memory_type_info& memory_type, vmm_allocation_pool pool, bool throw_on_fail) { VkResult error_code; VkDeviceMemory memory; @@ -304,8 +314,9 @@ namespace vk } } + const auto severity = (throw_on_fail) ? rsx::problem_severity::fatal : rsx::problem_severity::severe; if (error_code == VK_ERROR_OUT_OF_DEVICE_MEMORY && - vmm_handle_memory_pressure(rsx::problem_severity::fatal)) + vmm_handle_memory_pressure(severity)) { // Out of memory. Try again. const auto [status, type] = do_vk_alloc(); @@ -317,6 +328,11 @@ namespace vk } } + if (!throw_on_fail) + { + return VK_NULL_HANDLE; + } + die_with_error(error_code); fmt::throw_exception("Unreachable! Error_code=0x%x", static_cast(error_code)); } @@ -359,16 +375,16 @@ namespace vk return g_render_device->get_allocator(); } - memory_block::memory_block(VkDevice dev, u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool) + memory_block::memory_block(VkDevice dev, u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool, bool nullable) : m_device(dev), m_size(block_sz) { m_mem_allocator = get_current_mem_allocator(); - m_mem_handle = m_mem_allocator->alloc(block_sz, alignment, memory_type, pool); + m_mem_handle = m_mem_allocator->alloc(block_sz, alignment, memory_type, pool, !nullable); } memory_block::~memory_block() { - if (m_mem_allocator) + if (m_mem_allocator && m_mem_handle) { m_mem_allocator->free(m_mem_handle); } diff --git a/rpcs3/Emu/RSX/VK/vkutils/memory.h b/rpcs3/Emu/RSX/VK/vkutils/memory.h index 2bbabae65d..1ac7085314 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/memory.h +++ b/rpcs3/Emu/RSX/VK/vkutils/memory.h @@ -58,7 +58,7 @@ namespace vk virtual void destroy() = 0; - virtual mem_handle_t alloc(u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool) = 0; + virtual mem_handle_t alloc(u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool, bool throw_on_fail) = 0; virtual void free(mem_handle_t mem_handle) = 0; virtual void* map(mem_handle_t mem_handle, u64 offset, u64 size) = 0; virtual void unmap(mem_handle_t mem_handle) = 0; @@ -86,7 +86,7 @@ namespace vk void destroy() override; - mem_handle_t alloc(u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool) override; + mem_handle_t alloc(u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool, bool throw_on_fail) override; void free(mem_handle_t mem_handle) override; void* map(mem_handle_t mem_handle, u64 offset, u64 /*size*/) override; @@ -115,7 +115,7 @@ namespace vk void destroy() override {} - mem_handle_t alloc(u64 block_sz, u64 /*alignment*/, const memory_type_info& memory_type, vmm_allocation_pool pool) override; + mem_handle_t alloc(u64 block_sz, u64 /*alignment*/, const memory_type_info& memory_type, vmm_allocation_pool pool, bool throw_on_fail) override; void free(mem_handle_t mem_handle) override; void* map(mem_handle_t mem_handle, u64 offset, u64 size) override; @@ -128,7 +128,7 @@ namespace vk struct memory_block { - memory_block(VkDevice dev, u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool); + memory_block(VkDevice dev, u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool, bool nullable = false); virtual ~memory_block(); virtual VkDeviceMemory get_vk_device_memory(); diff --git a/rpcs3/Emu/RSX/VK/vkutils/scratch.cpp b/rpcs3/Emu/RSX/VK/vkutils/scratch.cpp index d87edaaabd..00a23701f5 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/scratch.cpp +++ b/rpcs3/Emu/RSX/VK/vkutils/scratch.cpp @@ -97,7 +97,14 @@ namespace vk auto& tex = g_null_image_views[type]; tex = std::make_unique(*g_render_device, g_render_device->get_memory_mapping().device_local, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, image_type, VK_FORMAT_B8G8R8A8_UNORM, size, size, 1, 1, num_layers, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_LAYOUT_UNDEFINED, - VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, flags, VMM_ALLOCATION_POOL_SCRATCH); + VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, flags | VK_IMAGE_CREATE_ALLOW_NULL, VMM_ALLOCATION_POOL_SCRATCH); + + if (!tex->value) + { + // If we cannot create a 1x1 placeholder, things are truly hopeless. + // The null view is 'nullable' because it is meant for use in emergency situations and we do not wish to invalidate any handles. + fmt::throw_exception("Renderer is out of memory. We could not even squeeze in a 1x1 texture, things are bad."); + } // Initialize memory to transparent black tex->change_layout(cmd, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);