summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorZephyron <zephyron@citron-emu.orgq>2025-02-22 19:08:42 +1000
committerZephyron <zephyron@citron-emu.orgq>2025-02-22 19:08:42 +1000
commitd9619b7eed13adc4bac329bf4b46ee254fe7a5a6 (patch)
tree4c78c4a9f0eea18b4b81fae09f8272da60722fdd /src
parentdbe5bf1d18e28b4bb1bceb5d9e3270a48a1c35cd (diff)
vulkan: Improve memory allocation robustness
Enhances the Vulkan memory allocator with better OOM handling and memory alignment: * Add memory recovery by cleaning up empty allocations before failing * Implement proper fallback to non-device-local memory * Simplify memory alignment handling for different vendors * Add better error logging for allocation failures * Add IsEmpty() helper to track unused allocations * Fix alignment requirements for Adreno (4KB) vs other vendors These changes improve the robustness of memory allocation, particularly in low-memory situations, and streamline vendor-specific alignment requirements.
Diffstat (limited to 'src')
-rw-r--r--src/video_core/vulkan_common/vulkan_memory_allocator.cpp76
1 files changed, 57 insertions, 19 deletions
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
index d62c13f66..0a0caefdc 100644
--- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
@@ -140,6 +140,10 @@ public:
return (flags & property_flags) == flags && (type_mask & shifted_memory_type) != 0;
}
+ [[nodiscard]] bool IsEmpty() const noexcept {
+ return commits.empty();
+ }
+
private:
[[nodiscard]] static constexpr u32 ShiftType(u32 type) {
return 1U << type;
@@ -284,44 +288,78 @@ MemoryCommit MemoryAllocator::Commit(const VkMemoryRequirements& requirements, M
const u32 type_mask = requirements.memoryTypeBits;
const VkMemoryPropertyFlags usage_flags = MemoryUsagePropertyFlags(usage);
const VkMemoryPropertyFlags flags = MemoryPropertyFlags(type_mask, usage_flags);
+
+ // First attempt
if (std::optional<MemoryCommit> commit = TryCommit(requirements, flags)) {
return std::move(*commit);
}
- // Commit has failed, allocate more memory.
+
+ // Commit has failed, allocate more memory
const u64 chunk_size = AllocationChunkSize(requirements.size);
- if (!TryAllocMemory(flags, type_mask, chunk_size)) {
- // TODO(Rodrigo): Handle out of memory situations in some way like flushing to guest memory.
- throw vk::Exception(VK_ERROR_OUT_OF_DEVICE_MEMORY);
+ if (TryAllocMemory(flags, type_mask, chunk_size)) {
+ return TryCommit(requirements, flags).value();
}
- // Commit again, this time it won't fail since there's a fresh allocation above.
- // If it does, there's a bug.
- return TryCommit(requirements, flags).value();
+
+ // Memory allocation failed - try to recover by releasing empty allocations
+ for (auto it = allocations.begin(); it != allocations.end();) {
+ if ((*it)->IsEmpty()) {
+ it = allocations.erase(it);
+ } else {
+ ++it;
+ }
+ }
+
+ // Try allocating again after cleanup
+ if (TryAllocMemory(flags, type_mask, chunk_size)) {
+ return TryCommit(requirements, flags).value();
+ }
+
+ // If still failing, try with non-device-local memory as a last resort
+ if (flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
+ const VkMemoryPropertyFlags fallback_flags = flags & ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
+ if (TryAllocMemory(fallback_flags, type_mask, chunk_size)) {
+ if (auto commit = TryCommit(requirements, fallback_flags)) {
+ LOG_WARNING(Render_Vulkan, "Falling back to non-device-local memory due to OOM");
+ return std::move(*commit);
+ }
+ }
+ }
+
+ LOG_CRITICAL(Render_Vulkan, "Vulkan memory allocation failed - out of device memory");
+ throw vk::Exception(VK_ERROR_OUT_OF_DEVICE_MEMORY);
}
bool MemoryAllocator::TryAllocMemory(VkMemoryPropertyFlags flags, u32 type_mask, u64 size) {
- const u32 type = FindType(flags, type_mask).value();
+ const auto type_opt = FindType(flags, type_mask);
+ if (!type_opt) {
+ if ((flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0) {
+ // Try to allocate non device local memory
+ return TryAllocMemory(flags & ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, type_mask, size);
+ }
+ return false;
+ }
+
+ const u64 aligned_size = (device.GetDriverID() == VK_DRIVER_ID_QUALCOMM_PROPRIETARY) ?
+ Common::AlignUp(size, 4096) : // Adreno requires 4KB alignment
+ size; // Others (NVIDIA, AMD, Intel, etc)
+
vk::DeviceMemory memory = device.GetLogical().TryAllocateMemory({
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.pNext = nullptr,
- /* AMD drivers (including Adreno) require 4KB alignment */
- .allocationSize = (device.GetDriverID() == VK_DRIVER_ID_AMD_PROPRIETARY ||
- device.GetDriverID() == VK_DRIVER_ID_AMD_OPEN_SOURCE ||
- device.GetDriverID() == VK_DRIVER_ID_QUALCOMM_PROPRIETARY) ?
- ((size + 4095) & ~4095) : /* AMD (AMDVLK, RADV, RadeonSI) & Adreno */
- size, /* Others (NVIDIA, Intel, Mali, etc) */
- .memoryTypeIndex = type,
+ .allocationSize = aligned_size,
+ .memoryTypeIndex = *type_opt,
});
+
if (!memory) {
if ((flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0) {
// Try to allocate non device local memory
return TryAllocMemory(flags & ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, type_mask, size);
- } else {
- // RIP
- return false;
}
+ return false;
}
+
allocations.push_back(
- std::make_unique<MemoryAllocation>(this, std::move(memory), flags, size, type));
+ std::make_unique<MemoryAllocation>(this, std::move(memory), flags, aligned_size, *type_opt));
return true;
}