mirror of https://github.com/RPCS3/rpcs3.git
vk: Improve video memory manager to attempt recovery in out of memory situations
This commit is contained in:
parent
4d8de282f9
commit
b0c7ca6d1f
|
@ -660,49 +660,6 @@ namespace rsx
|
||||||
std::forward<Args>(extra_params)...);
|
std::forward<Args>(extra_params)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool check_memory_overload(u64 max_safe_memory) const
|
|
||||||
{
|
|
||||||
if (m_active_memory_used <= max_safe_memory) [[likely]]
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
rsx_log.warning("Surface cache is using too much memory! (%dM)", m_active_memory_used / 0x100000);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void handle_memory_overload(command_list_type cmd)
|
|
||||||
{
|
|
||||||
auto process_list_function = [&](std::unordered_map<u32, surface_storage_type>& data)
|
|
||||||
{
|
|
||||||
for (auto It = data.begin(); It != data.end();)
|
|
||||||
{
|
|
||||||
auto surface = Traits::get(It->second);
|
|
||||||
if (surface->dirty())
|
|
||||||
{
|
|
||||||
// Force memory barrier to release some resources
|
|
||||||
surface->memory_barrier(cmd, rsx::surface_access::read);
|
|
||||||
}
|
|
||||||
else if (!surface->test())
|
|
||||||
{
|
|
||||||
// Remove this
|
|
||||||
invalidate(It->second);
|
|
||||||
It = data.erase(It);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
++It;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Try and find old surfaces to remove
|
|
||||||
process_list_function(m_render_targets_storage);
|
|
||||||
process_list_function(m_depth_stencil_storage);
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* Update bound color and depth surface.
|
* Update bound color and depth surface.
|
||||||
|
@ -1122,5 +1079,52 @@ namespace rsx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool check_memory_usage(u64 max_safe_memory) const
|
||||||
|
{
|
||||||
|
if (m_active_memory_used <= max_safe_memory) [[likely]]
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rsx_log.warning("Surface cache is using too much memory! (%dM)", m_active_memory_used / 0x100000);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool handle_memory_pressure(command_list_type cmd, problem_severity /*severity*/)
|
||||||
|
{
|
||||||
|
auto process_list_function = [&](std::unordered_map<u32, surface_storage_type>& data)
|
||||||
|
{
|
||||||
|
for (auto It = data.begin(); It != data.end();)
|
||||||
|
{
|
||||||
|
auto surface = Traits::get(It->second);
|
||||||
|
if (surface->dirty())
|
||||||
|
{
|
||||||
|
// Force memory barrier to release some resources
|
||||||
|
surface->memory_barrier(cmd, rsx::surface_access::read);
|
||||||
|
}
|
||||||
|
else if (!surface->test())
|
||||||
|
{
|
||||||
|
// Remove this
|
||||||
|
invalidate(It->second);
|
||||||
|
It = data.erase(It);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++It;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto old_usage = m_active_memory_used;
|
||||||
|
|
||||||
|
// Try and find old surfaces to remove
|
||||||
|
process_list_function(m_render_targets_storage);
|
||||||
|
process_list_function(m_depth_stencil_storage);
|
||||||
|
|
||||||
|
return (m_active_memory_used < old_usage);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1342,6 +1342,23 @@ namespace rsx
|
||||||
m_storage.purge_unreleased_sections();
|
m_storage.purge_unreleased_sections();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool handle_memory_pressure(problem_severity severity)
|
||||||
|
{
|
||||||
|
if (m_storage.m_unreleased_texture_objects)
|
||||||
|
{
|
||||||
|
m_storage.purge_unreleased_sections();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (severity >= problem_severity::severe)
|
||||||
|
{
|
||||||
|
// Things are bad, previous check should have released 'unreleased' pool
|
||||||
|
return m_storage.purge_unlocked_sections();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
image_view_type create_temporary_subresource(commandbuffer_type &cmd, deferred_subresource& desc)
|
image_view_type create_temporary_subresource(commandbuffer_type &cmd, deferred_subresource& desc)
|
||||||
{
|
{
|
||||||
if (!desc.do_not_cache) [[likely]]
|
if (!desc.do_not_cache) [[likely]]
|
||||||
|
|
|
@ -682,6 +682,44 @@ namespace rsx
|
||||||
AUDIT(m_unreleased_texture_objects == 0);
|
AUDIT(m_unreleased_texture_objects == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool purge_unlocked_sections()
|
||||||
|
{
|
||||||
|
// Reclaims all graphics memory consumed by unlocked textures
|
||||||
|
bool any_released = false;
|
||||||
|
for (auto it = m_in_use.begin(); it != m_in_use.end();)
|
||||||
|
{
|
||||||
|
auto* block = *it;
|
||||||
|
|
||||||
|
if (block->get_exists_count() > block->get_locked_count())
|
||||||
|
{
|
||||||
|
for (auto& tex : *block)
|
||||||
|
{
|
||||||
|
if (tex.get_context() == rsx::texture_upload_context::framebuffer_storage ||
|
||||||
|
tex.is_locked() ||
|
||||||
|
!tex.exists())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT(!tex.is_locked() && tex.exists());
|
||||||
|
tex.destroy();
|
||||||
|
any_released = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block->get_exists_count() == 0)
|
||||||
|
{
|
||||||
|
it = m_in_use.erase(it);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return any_released;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callbacks
|
* Callbacks
|
||||||
|
|
|
@ -358,9 +358,9 @@ struct gl_render_targets : public rsx::surface_store<gl_render_target_traits>
|
||||||
std::vector<GLuint> free_invalidated(gl::command_context& cmd)
|
std::vector<GLuint> free_invalidated(gl::command_context& cmd)
|
||||||
{
|
{
|
||||||
// Do not allow more than 256M of RSX memory to be used by RTTs
|
// Do not allow more than 256M of RSX memory to be used by RTTs
|
||||||
if (check_memory_overload(256 * 0x100000))
|
if (check_memory_usage(256 * 0x100000))
|
||||||
{
|
{
|
||||||
handle_memory_overload(cmd);
|
handle_memory_pressure(cmd, rsx::problem_severity::moderate);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<GLuint> removed;
|
std::vector<GLuint> removed;
|
||||||
|
|
|
@ -787,6 +787,26 @@ void VKGSRender::on_semaphore_acquire_wait()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool VKGSRender::on_vram_exhausted(rsx::problem_severity severity)
|
||||||
|
{
|
||||||
|
ASSERT(!vk::is_uninterruptible() && rsx::get_current_renderer()->is_current_thread());
|
||||||
|
bool released = m_texture_cache.handle_memory_pressure(severity);
|
||||||
|
|
||||||
|
if (severity <= rsx::problem_severity::moderate)
|
||||||
|
{
|
||||||
|
released |= m_rtts.handle_memory_pressure(*m_current_command_buffer, severity);
|
||||||
|
return released;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (released && severity >= rsx::problem_severity::fatal)
|
||||||
|
{
|
||||||
|
// Imminent crash, full GPU sync is the least of our problems
|
||||||
|
flush_command_queue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return released;
|
||||||
|
}
|
||||||
|
|
||||||
void VKGSRender::notify_tile_unbound(u32 tile)
|
void VKGSRender::notify_tile_unbound(u32 tile)
|
||||||
{
|
{
|
||||||
//TODO: Handle texture writeback
|
//TODO: Handle texture writeback
|
||||||
|
|
|
@ -546,6 +546,9 @@ public:
|
||||||
// External callback in case we need to suddenly submit a commandlist unexpectedly, e.g in a violation handler
|
// External callback in case we need to suddenly submit a commandlist unexpectedly, e.g in a violation handler
|
||||||
void emergency_query_cleanup(vk::command_buffer* commands);
|
void emergency_query_cleanup(vk::command_buffer* commands);
|
||||||
|
|
||||||
|
// External callback to handle out of video memory problems
|
||||||
|
bool on_vram_exhausted(rsx::problem_severity severity);
|
||||||
|
|
||||||
// Conditional rendering
|
// Conditional rendering
|
||||||
void begin_conditional_rendering(const std::vector<rsx::reports::occlusion_query_info*>& sources) override;
|
void begin_conditional_rendering(const std::vector<rsx::reports::occlusion_query_info*>& sources) override;
|
||||||
void end_conditional_rendering() override;
|
void end_conditional_rendering() override;
|
||||||
|
|
|
@ -171,6 +171,8 @@ namespace vk
|
||||||
void vmm_notify_memory_allocated(void* handle, u32 memory_type, u64 memory_size);
|
void vmm_notify_memory_allocated(void* handle, u32 memory_type, u64 memory_size);
|
||||||
void vmm_notify_memory_freed(void* handle);
|
void vmm_notify_memory_freed(void* handle);
|
||||||
void vmm_reset();
|
void vmm_reset();
|
||||||
|
void vmm_check_memory_usage();
|
||||||
|
bool vmm_handle_memory_pressure(rsx::problem_severity severity);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allocate enough space in upload_buffer and write all mipmap/layer data into the subbuffer.
|
* Allocate enough space in upload_buffer and write all mipmap/layer data into the subbuffer.
|
||||||
|
@ -323,6 +325,7 @@ namespace vk
|
||||||
virtual void unmap(mem_handle_t mem_handle) = 0;
|
virtual void unmap(mem_handle_t mem_handle) = 0;
|
||||||
virtual VkDeviceMemory get_vk_device_memory(mem_handle_t mem_handle) = 0;
|
virtual VkDeviceMemory get_vk_device_memory(mem_handle_t mem_handle) = 0;
|
||||||
virtual u64 get_vk_device_memory_offset(mem_handle_t mem_handle) = 0;
|
virtual u64 get_vk_device_memory_offset(mem_handle_t mem_handle) = 0;
|
||||||
|
virtual f32 get_memory_usage() = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
VkDevice m_device;
|
VkDevice m_device;
|
||||||
|
@ -337,6 +340,9 @@ namespace vk
|
||||||
public:
|
public:
|
||||||
mem_allocator_vma(VkDevice dev, VkPhysicalDevice pdev) : mem_allocator_base(dev, pdev)
|
mem_allocator_vma(VkDevice dev, VkPhysicalDevice pdev) : mem_allocator_base(dev, pdev)
|
||||||
{
|
{
|
||||||
|
// Initialize stats pool
|
||||||
|
std::fill(stats.begin(), stats.end(), VmaBudget{});
|
||||||
|
|
||||||
VmaAllocatorCreateInfo allocatorInfo = {};
|
VmaAllocatorCreateInfo allocatorInfo = {};
|
||||||
allocatorInfo.physicalDevice = pdev;
|
allocatorInfo.physicalDevice = pdev;
|
||||||
allocatorInfo.device = dev;
|
allocatorInfo.device = dev;
|
||||||
|
@ -361,7 +367,26 @@ namespace vk
|
||||||
mem_req.size = block_sz;
|
mem_req.size = block_sz;
|
||||||
mem_req.alignment = alignment;
|
mem_req.alignment = alignment;
|
||||||
create_info.memoryTypeBits = 1u << memory_type_index;
|
create_info.memoryTypeBits = 1u << memory_type_index;
|
||||||
CHECK_RESULT(vmaAllocateMemory(m_allocator, &mem_req, &create_info, &vma_alloc, nullptr));
|
|
||||||
|
if (VkResult result = vmaAllocateMemory(m_allocator, &mem_req, &create_info, &vma_alloc, nullptr);
|
||||||
|
result != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
if (result == VK_ERROR_OUT_OF_DEVICE_MEMORY &&
|
||||||
|
vmm_handle_memory_pressure(rsx::problem_severity::fatal))
|
||||||
|
{
|
||||||
|
// If we just ran out of VRAM, attempt to release resources and try again
|
||||||
|
result = vmaAllocateMemory(m_allocator, &mem_req, &create_info, &vma_alloc, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
die_with_error(HERE, result);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rsx_log.warning("Renderer ran out of video memory but successfully recovered.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
vmm_notify_memory_allocated(vma_alloc, memory_type_index, block_sz);
|
vmm_notify_memory_allocated(vma_alloc, memory_type_index, block_sz);
|
||||||
return vma_alloc;
|
return vma_alloc;
|
||||||
|
@ -405,8 +430,28 @@ namespace vk
|
||||||
return alloc_info.offset;
|
return alloc_info.offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
f32 get_memory_usage() override
|
||||||
|
{
|
||||||
|
vmaGetBudget(m_allocator, stats.data());
|
||||||
|
|
||||||
|
float max_usage = 0.f;
|
||||||
|
for (const auto& info : stats)
|
||||||
|
{
|
||||||
|
if (!info.budget)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float this_usage = (info.usage * 100.f) / info.budget;
|
||||||
|
max_usage = std::max(max_usage, this_usage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return max_usage;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
VmaAllocator m_allocator;
|
VmaAllocator m_allocator;
|
||||||
|
std::array<VmaBudget, VK_MAX_MEMORY_HEAPS> stats;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Memory Allocator - built-in Vulkan device memory allocate/free
|
// Memory Allocator - built-in Vulkan device memory allocate/free
|
||||||
|
@ -426,7 +471,26 @@ namespace vk
|
||||||
info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||||
info.allocationSize = block_sz;
|
info.allocationSize = block_sz;
|
||||||
info.memoryTypeIndex = memory_type_index;
|
info.memoryTypeIndex = memory_type_index;
|
||||||
CHECK_RESULT(vkAllocateMemory(m_device, &info, nullptr, &memory));
|
|
||||||
|
if (VkResult result = vkAllocateMemory(m_device, &info, nullptr, &memory);
|
||||||
|
result != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
if (result == VK_ERROR_OUT_OF_DEVICE_MEMORY &&
|
||||||
|
vmm_handle_memory_pressure(rsx::problem_severity::fatal))
|
||||||
|
{
|
||||||
|
// If we just ran out of VRAM, attempt to release resources and try again
|
||||||
|
result = vkAllocateMemory(m_device, &info, nullptr, &memory);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
die_with_error(HERE, result);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rsx_log.warning("Renderer ran out of video memory but successfully recovered.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
vmm_notify_memory_allocated(memory, memory_type_index, block_sz);
|
vmm_notify_memory_allocated(memory, memory_type_index, block_sz);
|
||||||
return memory;
|
return memory;
|
||||||
|
@ -460,6 +524,11 @@ namespace vk
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
f32 get_memory_usage() override
|
||||||
|
{
|
||||||
|
return 0.f;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -104,6 +104,9 @@ void VKGSRender::advance_queued_frames()
|
||||||
// Check all other frames for completion and clear resources
|
// Check all other frames for completion and clear resources
|
||||||
check_present_status();
|
check_present_status();
|
||||||
|
|
||||||
|
// Run video memory balancer
|
||||||
|
vk::vmm_check_memory_usage();
|
||||||
|
|
||||||
// m_rtts storage is double buffered and should be safe to tag on frame boundary
|
// m_rtts storage is double buffered and should be safe to tag on frame boundary
|
||||||
m_rtts.free_invalidated(*m_current_command_buffer);
|
m_rtts.free_invalidated(*m_current_command_buffer);
|
||||||
|
|
||||||
|
|
|
@ -936,14 +936,14 @@ namespace rsx
|
||||||
void free_invalidated(vk::command_buffer& cmd)
|
void free_invalidated(vk::command_buffer& cmd)
|
||||||
{
|
{
|
||||||
// Do not allow more than 256M of RSX memory to be used by RTTs
|
// Do not allow more than 256M of RSX memory to be used by RTTs
|
||||||
if (check_memory_overload(256 * 0x100000))
|
if (check_memory_usage(256 * 0x100000))
|
||||||
{
|
{
|
||||||
if (!cmd.is_recording())
|
if (!cmd.is_recording())
|
||||||
{
|
{
|
||||||
cmd.begin();
|
cmd.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
handle_memory_overload(cmd);
|
handle_memory_pressure(cmd, rsx::problem_severity::moderate);
|
||||||
}
|
}
|
||||||
|
|
||||||
const u64 last_finished_frame = vk::get_last_completed_frame_id();
|
const u64 last_finished_frame = vk::get_last_completed_frame_id();
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include "stdafx.h"
|
#include "stdafx.h"
|
||||||
#include "VKResourceManager.h"
|
#include "VKResourceManager.h"
|
||||||
|
#include "VKGSRender.h"
|
||||||
|
|
||||||
namespace vk
|
namespace vk
|
||||||
{
|
{
|
||||||
|
@ -75,4 +76,36 @@ namespace vk
|
||||||
g_vmm_memory_usage.clear();
|
g_vmm_memory_usage.clear();
|
||||||
g_vmm_allocations.clear();
|
g_vmm_allocations.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool vmm_handle_memory_pressure(rsx::problem_severity severity)
|
||||||
|
{
|
||||||
|
if (auto vkthr = dynamic_cast<VKGSRender*>(rsx::get_current_renderer()))
|
||||||
|
{
|
||||||
|
return vkthr->on_vram_exhausted(severity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void vmm_check_memory_usage()
|
||||||
|
{
|
||||||
|
const auto vmm_load = get_current_mem_allocator()->get_memory_usage();
|
||||||
|
rsx::problem_severity load_severity = rsx::problem_severity::low;
|
||||||
|
|
||||||
|
if (vmm_load > 90.f)
|
||||||
|
{
|
||||||
|
rsx_log.warning("Video memory usage exceeding 90%. Will attempt to reclaim resources.");
|
||||||
|
load_severity = rsx::problem_severity::severe;
|
||||||
|
}
|
||||||
|
else if (vmm_load > 75.f)
|
||||||
|
{
|
||||||
|
rsx_log.notice("Video memory usage exceeding 75%. Will attempt to reclaim resources.");
|
||||||
|
load_severity = rsx::problem_severity::moderate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (load_severity >= rsx::problem_severity::moderate)
|
||||||
|
{
|
||||||
|
vmm_handle_memory_pressure(load_severity);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,14 @@ namespace rsx
|
||||||
|
|
||||||
extern atomic_t<u64> g_rsx_shared_tag;
|
extern atomic_t<u64> g_rsx_shared_tag;
|
||||||
|
|
||||||
|
enum class problem_severity : u8
|
||||||
|
{
|
||||||
|
low,
|
||||||
|
moderate,
|
||||||
|
severe,
|
||||||
|
fatal
|
||||||
|
};
|
||||||
|
|
||||||
//Base for resources with reference counting
|
//Base for resources with reference counting
|
||||||
class ref_counted
|
class ref_counted
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue