diff options
| author | Wael3rd <29479851+Wael3rd@users.noreply.github.com> | 2026-10-31 07:34:14 +0100 |
|---|---|---|
| committer | kraibse <33869366+kraibse@users.noreply.github.com> | 2026-10-31 18:04:23 +0100 |
| commit | 2065b1431c0ecfeade61a7e7511889c4800b6bb9 (patch) | |
| tree | b38d89592c3cb1fe802089d13a8231a2adeefd6a /src/video_core/vulkan_common/vulkan_device.cpp | |
| parent | ee2d55ee497c9467c8df82515165a21eac13fff5 (diff) | |
texture_cache: keep Switch 2 VRAM use under the driver's budget
Mario Kart World filled the GPU after a few minutes on 8 GB cards, and on
16 GB cards with uncompressed ASTC: textures were created without memory
(garbage), then the emulator crashed. The Switch 2 collector thresholds
came from the sum of all heap budgets, but on AMD the host-visible
device-local heap is the same VRAM, so the critical threshold ended above
the real budget and the collector never kicked in. Unbacked images were
also counted at their full size.
Thresholds now follow the main device-local heap's budget, re-read every
30 frames, and images count the memory they really have. Switch 2 titles
on GPUs under 12 GiB re-compress ASTC to BC3 for the session when it is
set to Uncompressed (EDEN_NX2_KEEP_ASTC=1 opts out), and
EDEN_NX2_VRAM_LIMIT_MIB simulates a smaller GPU for testing.
Assisted-by: Claude Opus 5.5 (diagnosis and code)
Diffstat (limited to 'src/video_core/vulkan_common/vulkan_device.cpp')
| -rw-r--r-- | src/video_core/vulkan_common/vulkan_device.cpp | 54 |
1 files changed, 48 insertions, 6 deletions
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 69c3ba14fa..915b22aaf5 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -8,6 +8,7 @@ #include <array> #include <bitset> #include <chrono> +#include <cstdlib> #include <filesystem> #include <fstream> #include <optional> @@ -749,6 +750,17 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR if (extensions.buffer_device_address) { flags |= VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; } + // EDEN_NX2_VRAM_LIMIT_MIB: VMA fails allocations past the limit, like a smaller card. + std::array<VkDeviceSize, VK_MAX_MEMORY_HEAPS> heap_size_limits; + heap_size_limits.fill(VK_WHOLE_SIZE); + if (heap_size_limit != 0) { + const auto memory_properties = physical.GetMemoryProperties().memoryProperties; + for (u32 heap = 0; heap < memory_properties.memoryHeapCount; ++heap) { + if (memory_properties.memoryHeaps[heap].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) { + heap_size_limits[heap] = heap_size_limit; + } + } + } const VmaAllocatorCreateInfo allocator_info{ .flags = flags, .physicalDevice = physical, @@ -759,7 +771,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR .preferredLargeHeapBlockSize = 64u * 1024u * 1024u, .pAllocationCallbacks = nullptr, .pDeviceMemoryCallbacks = nullptr, - .pHeapSizeLimit = nullptr, + .pHeapSizeLimit = heap_size_limit != 0 ? heap_size_limits.data() : nullptr, .pVulkanFunctions = &functions, .instance = instance, .vulkanApiVersion = ApiVersion(), @@ -1634,9 +1646,23 @@ u64 Device::GetDeviceMemoryBudget() const { VkPhysicalDeviceMemoryBudgetPropertiesEXT budget{}; budget.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT; physical.GetMemoryProperties(&budget); - u64 result{}; + std::array<VmaBudget, VK_MAX_MEMORY_HEAPS> heap_budgets{}; + if (allocator != nullptr) { + vmaGetHeapBudgets(allocator, heap_budgets.data()); + } + // Only the main heap's budget: summing every heap's counted VRAM twice on AMD (an 8 GB card + // reported 8.5 GB) and kept the collector's thresholds above the heap the images fill. What + // the other heaps use is added, as GetDeviceMemoryUsage() counts it too. + u64 result = budget.heapBudget[main_heap]; + if (heap_size_limit != 0) { + result = (std::min)(result, heap_size_limit); + } for (const size_t heap : valid_heap_memory) { - result += budget.heapBudget[heap]; + if (heap != main_heap) { + const VmaStatistics& statistics = heap_budgets[heap].statistics; + const u64 unused = statistics.blockBytes - statistics.allocationBytes; + result += budget.heapUsage[heap] - (std::min)(budget.heapUsage[heap], unused); + } } return result; } @@ -1652,6 +1678,15 @@ void Device::CollectPhysicalMemoryInfo() { device_access_memory = 0; u64 device_initial_usage = 0; u64 local_memory = 0; + // EDEN_NX2_VRAM_LIMIT_MIB=<n>: treat every device-local heap as n MiB (small card tests). + if (const char* value = std::getenv("EDEN_NX2_VRAM_LIMIT_MIB")) { + heap_size_limit = std::strtoull(value, nullptr, 10) << 20; + LOG_INFO(Render_Vulkan, "EDEN_NX2_VRAM_LIMIT_MIB: device-local heaps limited to {} MiB", + heap_size_limit >> 20); + } + const auto limit = [this](u64 size) { + return heap_size_limit != 0 ? (std::min)(size, heap_size_limit) : size; + }; for (size_t element = 0; element < num_properties; ++element) { const bool is_heap_local = (mem_properties.memoryHeaps[element].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0; @@ -1659,15 +1694,22 @@ void Device::CollectPhysicalMemoryInfo() { continue; } valid_heap_memory.push_back(element); + const VkMemoryHeap& main = mem_properties.memoryHeaps[main_heap]; + u64 heap_size = mem_properties.memoryHeaps[element].size; if (is_heap_local) { - local_memory += mem_properties.memoryHeaps[element].size; + if ((main.flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) == 0 || heap_size > main.size) { + main_heap = element; + } + heap_size = limit(heap_size); + local_memory += heap_size; } if (extensions.memory_budget) { device_initial_usage += budget.heapUsage[element]; - device_access_memory += budget.heapBudget[element]; + device_access_memory += is_heap_local ? limit(budget.heapBudget[element]) + : budget.heapBudget[element]; continue; } - device_access_memory += mem_properties.memoryHeaps[element].size; + device_access_memory += heap_size; } if (is_integrated) { const s64 available_memory = static_cast<s64>(device_access_memory - device_initial_usage); |
