mirror of
https://git.citron-emu.org/Citron/Citron.git
synced 2025-03-15 03:14:50 +00:00
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.
This commit is contained in:
parent
dbe5bf1d18
commit
d9619b7eed
1 changed files with 57 additions and 19 deletions
|
@ -140,6 +140,10 @@ public:
|
||||||
return (flags & property_flags) == flags && (type_mask & shifted_memory_type) != 0;
|
return (flags & property_flags) == flags && (type_mask & shifted_memory_type) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsEmpty() const noexcept {
|
||||||
|
return commits.empty();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
[[nodiscard]] static constexpr u32 ShiftType(u32 type) {
|
[[nodiscard]] static constexpr u32 ShiftType(u32 type) {
|
||||||
return 1U << type;
|
return 1U << type;
|
||||||
|
@ -284,44 +288,78 @@ MemoryCommit MemoryAllocator::Commit(const VkMemoryRequirements& requirements, M
|
||||||
const u32 type_mask = requirements.memoryTypeBits;
|
const u32 type_mask = requirements.memoryTypeBits;
|
||||||
const VkMemoryPropertyFlags usage_flags = MemoryUsagePropertyFlags(usage);
|
const VkMemoryPropertyFlags usage_flags = MemoryUsagePropertyFlags(usage);
|
||||||
const VkMemoryPropertyFlags flags = MemoryPropertyFlags(type_mask, usage_flags);
|
const VkMemoryPropertyFlags flags = MemoryPropertyFlags(type_mask, usage_flags);
|
||||||
|
|
||||||
|
// First attempt
|
||||||
if (std::optional<MemoryCommit> commit = TryCommit(requirements, flags)) {
|
if (std::optional<MemoryCommit> commit = TryCommit(requirements, flags)) {
|
||||||
return std::move(*commit);
|
return std::move(*commit);
|
||||||
}
|
}
|
||||||
// Commit has failed, allocate more memory.
|
|
||||||
|
// Commit has failed, allocate more memory
|
||||||
const u64 chunk_size = AllocationChunkSize(requirements.size);
|
const u64 chunk_size = AllocationChunkSize(requirements.size);
|
||||||
if (!TryAllocMemory(flags, type_mask, chunk_size)) {
|
if (TryAllocMemory(flags, type_mask, chunk_size)) {
|
||||||
// TODO(Rodrigo): Handle out of memory situations in some way like flushing to guest memory.
|
return TryCommit(requirements, flags).value();
|
||||||
throw vk::Exception(VK_ERROR_OUT_OF_DEVICE_MEMORY);
|
|
||||||
}
|
}
|
||||||
// Commit again, this time it won't fail since there's a fresh allocation above.
|
|
||||||
// If it does, there's a bug.
|
// Memory allocation failed - try to recover by releasing empty allocations
|
||||||
return TryCommit(requirements, flags).value();
|
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) {
|
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({
|
vk::DeviceMemory memory = device.GetLogical().TryAllocateMemory({
|
||||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
/* AMD drivers (including Adreno) require 4KB alignment */
|
.allocationSize = aligned_size,
|
||||||
.allocationSize = (device.GetDriverID() == VK_DRIVER_ID_AMD_PROPRIETARY ||
|
.memoryTypeIndex = *type_opt,
|
||||||
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,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!memory) {
|
if (!memory) {
|
||||||
if ((flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0) {
|
if ((flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0) {
|
||||||
// Try to allocate non device local memory
|
// Try to allocate non device local memory
|
||||||
return TryAllocMemory(flags & ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, type_mask, size);
|
return TryAllocMemory(flags & ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, type_mask, size);
|
||||||
} else {
|
|
||||||
// RIP
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
allocations.push_back(
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue