aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/vulkan_common/vulkan_device.cpp
diff options
context:
space:
mode:
authorWael3rd <29479851+Wael3rd@users.noreply.github.com>2026-10-31 07:34:14 +0100
committerkraibse <33869366+kraibse@users.noreply.github.com>2026-10-31 18:04:23 +0100
commit2065b1431c0ecfeade61a7e7511889c4800b6bb9 (patch)
treeb38d89592c3cb1fe802089d13a8231a2adeefd6a /src/video_core/vulkan_common/vulkan_device.cpp
parentee2d55ee497c9467c8df82515165a21eac13fff5 (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.cpp54
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);