From d2ac2275247a9ecb3e1404606f7026c6402533b8 Mon Sep 17 00:00:00 2001 From: néo Luigi <85024393+neoluigi4123@users.noreply.github.com> Date: Fri, 30 Oct 2026 18:04:26 +0100 Subject: Fixed game support about docked/handheld mode, applying only to ns2 games. Tested on windows. --- src/core/core.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/core/core.cpp') diff --git a/src/core/core.cpp b/src/core/core.cpp index 64e1d8de06..8f9d18c141 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -1059,6 +1059,8 @@ void System::ApplySettings() { if (IsPoweredOn()) { Renderer().RefreshBaseSettings(); + // Frontends switch docked/handheld through the settings; let the running game know. + GetAppletManager().OperationModeChanged(); } } -- cgit v1.2.3 From 2065b1431c0ecfeade61a7e7511889c4800b6bb9 Mon Sep 17 00:00:00 2001 From: Wael3rd <29479851+Wael3rd@users.noreply.github.com> Date: Sat, 31 Oct 2026 07:34:14 +0100 Subject: 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) --- src/common/settings.cpp | 10 +++ src/common/settings.h | 4 ++ src/core/core.cpp | 1 + .../renderer_vulkan/vk_texture_cache.cpp | 28 ++++++++ src/video_core/renderer_vulkan/vk_texture_cache.h | 5 ++ src/video_core/texture_cache/texture_cache.h | 74 +++++++++++++++------- src/video_core/texture_cache/texture_cache_base.h | 8 +++ src/video_core/vulkan_common/vulkan_device.cpp | 54 ++++++++++++++-- src/video_core/vulkan_common/vulkan_device.h | 6 +- 9 files changed, 161 insertions(+), 29 deletions(-) (limited to 'src/core/core.cpp') diff --git a/src/common/settings.cpp b/src/common/settings.cpp index dc46a0ec87..7ce410b99c 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -225,6 +225,16 @@ u64 GetCurrentProgramID() { return current_program_id; } +static bool current_program_is_switch2 = false; + +void SetCurrentProgramIsSwitch2(bool is_switch2) { + current_program_is_switch2 = is_switch2; +} + +bool CurrentProgramIsSwitch2() { + return current_program_is_switch2; +} + bool IsDockedMode() { return values.use_docked_mode.GetValue() == Settings::ConsoleMode::Docked; } diff --git a/src/common/settings.h b/src/common/settings.h index d48e7b0b87..e703254f12 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -986,6 +986,10 @@ bool IsNceEnabled(); void SetCurrentProgramID(u64 program_id); u64 GetCurrentProgramID(); +// Whether the program being loaded is a Switch 2 one (set by the core before the GPU starts). +void SetCurrentProgramIsSwitch2(bool is_switch2); +bool CurrentProgramIsSwitch2(); + bool IsOpenGL(); bool IsDockedMode(); diff --git a/src/core/core.cpp b/src/core/core.cpp index 8f9d18c141..ac5e23e9c0 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -318,6 +318,7 @@ struct System::Impl { const bool switch2_program = virtual_filesystem != nullptr && IsSwitch2Program(GetGameFileFromPath(virtual_filesystem, filepath)); + Settings::SetCurrentProgramIsSwitch2(switch2_program); // Switch 2 titles size their heap from the total memory and need the 12 GiB layout. Set it // as a per-game value, so RestoreGlobalState brings back the configured layout afterwards. diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 6eb22fa263..f3b28c12b7 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -19,6 +19,7 @@ #include "common/alignment.h" #include "common/bit_util.h" #include "common/div_ceil.h" +#include "common/literals.h" #include "common/settings.h" #include "video_core/renderer_vulkan/vk_texture_cache.h" @@ -1082,6 +1083,31 @@ void BlitScale(Scheduler& scheduler, VkImage src_image, VkImage dst_image, const 0, nullptr, nullptr, write_barriers); }); } + +// Uncompressed ASTC needs more video memory than a Switch 2 title leaves on cards under 12 GiB +// (Mario Kart World filled a 16 GB card's 13.5 GB budget): there the textures went unbacked +// (garbage) and the game crashed. Use BC3 for this session; the configuration is left as is. +void UseBc3ForSwitch2OnSmallCards(const Device& device) { + using namespace Common::Literals; + if (!Settings::CurrentProgramIsSwitch2() || device.IsOptimalAstcSupported() || + Settings::values.astc_recompression.GetValue() != Settings::AstcRecompression::Uncompressed) { + return; + } + const u64 budget = device.GetDeviceMemoryBudget(); + const u64 memory = budget != 0 ? budget : device.GetDeviceLocalMemory(); + const char* keep = std::getenv("EDEN_NX2_KEEP_ASTC"); + if (memory >= 12_GiB || (keep != nullptr && keep[0] == '1')) { + return; + } + LOG_WARNING(Render_Vulkan, + "Switch 2 program with {} MiB of video memory: ASTC textures are re-compressed to " + "BC3 for this session (uncompressed ASTC needs 12 GiB or more). Set " + "EDEN_NX2_KEEP_ASTC=1 to keep them uncompressed", + memory >> 20); + // Per-game value, like the Switch 2 memory layout: RestoreGlobalState undoes it. + Settings::values.astc_recompression.SetGlobal(false); + Settings::values.astc_recompression.SetValue(Settings::AstcRecompression::Bc3); +} } // Anonymous namespace TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& scheduler_, @@ -1094,6 +1120,8 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched : device{device_}, scheduler{scheduler_}, memory_allocator{memory_allocator_}, staging_buffer_pool{staging_buffer_pool_}, blit_image_helper{blit_image_helper_}, render_pass_cache{render_pass_cache_}, resolution{Settings::values.resolution_info} { + // Before anything below reads astc_recompression (passes, view formats). + UseBc3ForSwitch2OnSmallCards(device); // Huge ASTC arrays always decode on the GPU (IsHugeAstcArray), whatever the decode setting. if (Settings::values.accelerate_astc.GetValue() == Settings::AstcDecodeMode::Gpu || !device.IsOptimalAstcSupported()) { diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 13919f1270..3944e183fd 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -389,6 +389,11 @@ public: /// layers that have memory afterwards (a cap per image can leave some out). std::vector BindLayers(std::span layers); + /// Device memory BindLayers has bound to the image's layers (an unbacked image has no other). + u64 SparseBoundBytes() const noexcept { + return sparse_layers.bound_bytes; + } + bool IsRescaled() const noexcept; bool ScaleUp(bool ignore = false); diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 63abffed0f..c8aaf8dff5 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -225,19 +225,24 @@ void TextureCache

::TickFrame() { // VRAM (another game: 7.0 -> 6.4 GB), and collecting only past it means the driver pages // textures out. It is also the room to use: Eden's device memory figure is capped near // 7 GB, which on a 12 GB card (11.2 GB budget) kept title 1 evicting textures with 3 GB - // unused. EDEN_NX2_TC_*_MIB overrides still win. + // unused. Both thresholds stay under it with a margin (1/8 of it, at most 1 GiB): past it + // allocations fail and images end up unbacked (garbage textures), and Windows shrinks the + // budget as the card fills (16 GB card: 13.5 GB left mid-race), hence the re-read every 30 + // frames. EDEN_NX2_TC_*_MIB overrides still win. if constexpr (requires { runtime.GetDeviceMemoryBudget(); }) { - if (native_thresholds_applied && budget_check_frames++ % 60 == 0) { + if (native_thresholds_applied && budget_check_frames++ % 30 == 0) { const u64 budget = runtime.GetDeviceMemoryBudget(); if (budget > 2_GiB) { const u64 old_critical = critical_memory; - critical_memory = budget - 768_MiB; - expected_memory = budget - 1536_MiB; - // Log only real changes (the budget moves by a few MiB all the time). + const u64 margin = (std::min)(budget / 8, 1_GiB); + critical_memory = budget - margin; + expected_memory = budget - 2 * margin; + // Log the first values and real changes (the budget moves by a few MiB all + // the time). const u64 change = critical_memory > old_critical ? critical_memory - old_critical : old_critical - critical_memory; - ApplyThresholdOverrides(change >= 256_MiB); + ApplyThresholdOverrides(budget_check_frames == 1 || change >= 256_MiB); } } } @@ -1434,7 +1439,8 @@ void TextureCache

::RefreshSparseLayers(Image& image) { } } image.dirty_layers.clear(); - const std::vector bound = layers.empty() ? std::vector{} : image.BindLayers(layers); + const std::vector bound = + layers.empty() ? std::vector{} : BindSparseLayers(image, layers); if (bound.empty()) { runtime.TransitionImageLayout(image); return; @@ -1444,6 +1450,21 @@ void TextureCache

::RefreshSparseLayers(Image& image) { } } +template +std::vector TextureCache

::BindSparseLayers(Image& image, std::span layers) { + if constexpr (requires { &Image::BindLayers; }) { + const u64 bound_bytes = ImageMemoryBytes(image); + std::vector bound = image.BindLayers(layers); + // RegisterImage counts what an image has bound by then; later binds are added here. + if (True(image.flags & ImageFlagBits::Registered)) { + total_used_memory += ImageMemoryBytes(image) - bound_bytes; + } + return bound; + } else { + return {}; + } +} + template void TextureCache

::UploadLayers(Image& image, std::span layers) { if constexpr (requires { &Runtime::AccelerateImageUploadLayers; }) { @@ -1713,6 +1734,26 @@ u64 TextureCache

::GetScaledImageSizeBytes(const ImageBase& image) { return fitted_size; } +template +u64 TextureCache

::ImageMemoryBytes(const Image& image) const { + if (True(image.flags & ImageFlagBits::Unbacked)) { + // No device memory but what BindLayers gave a SparseLayers image: at its full size (GBs + // for title 1's sparse terrain images), deleting it would look to the collector like + // freeing that much. + if constexpr (requires { image.SparseBoundBytes(); }) { + return image.SparseBoundBytes(); + } + return 0; + } + u64 tentative_size = (std::max)(image.guest_size_bytes, image.unswizzled_size_bytes); + if ((IsPixelFormatASTC(image.info.format) && + True(image.flags & ImageFlagBits::AcceleratedUpload)) || + True(image.flags & ImageFlagBits::Converted)) { + tentative_size = TranscodedAstcSize(tentative_size, image.info.format); + } + return Common::AlignUp(tentative_size, 1024); +} + template void TextureCache

::QueueAsyncDecode(Image& image, ImageId image_id) { UNIMPLEMENTED_IF(False(image.flags & ImageFlagBits::Converted)); @@ -2613,13 +2654,7 @@ void TextureCache

::RegisterImage(ImageId image_id) { ASSERT_MSG(False(image.flags & ImageFlagBits::Registered), "Trying to register an already registered image"); image.flags |= ImageFlagBits::Registered; - u64 tentative_size = (std::max)(image.guest_size_bytes, image.unswizzled_size_bytes); - if ((IsPixelFormatASTC(image.info.format) && - True(image.flags & ImageFlagBits::AcceleratedUpload)) || - True(image.flags & ImageFlagBits::Converted)) { - tentative_size = TranscodedAstcSize(tentative_size, image.info.format); - } - total_used_memory += Common::AlignUp(tentative_size, 1024); + total_used_memory += ImageMemoryBytes(slot_images[image_id]); image.lru_index = lru_cache.Insert(image_id, frame_tick); ForEachGPUPage(image.gpu_addr, image.guest_size_bytes, [this, image_id](u64 page) { @@ -2795,13 +2830,8 @@ void TextureCache

::DeleteImage(ImageId image_id, bool immediate_delete) { if (image.HasScaled()) { total_used_memory -= GetScaledImageSizeBytes(image); } - u64 tentative_size = (std::max)(image.guest_size_bytes, image.unswizzled_size_bytes); - if ((IsPixelFormatASTC(image.info.format) && - True(image.flags & ImageFlagBits::AcceleratedUpload)) || - True(image.flags & ImageFlagBits::Converted)) { - tentative_size = TranscodedAstcSize(tentative_size, image.info.format); - } - total_used_memory -= Common::AlignUp(tentative_size, 1024); + // TickFrame sets the total from the driver, which can be under the sum of these estimates. + total_used_memory -= (std::min)(total_used_memory, ImageMemoryBytes(slot_images[image_id])); const GPUVAddr gpu_addr = image.gpu_addr; const auto alloc_it = image_allocs_table.find(gpu_addr); if (alloc_it == image_allocs_table.end()) { @@ -3000,7 +3030,7 @@ void TextureCache

::PrepareImage(ImageId image_id, bool is_modification, bool std::vector layers(static_cast(image.info.resources.layers)); std::iota(layers.begin(), layers.end(), 0U); // Layers over the image's memory cap stay unbacked (logged once). - image.BindLayers(layers); + BindSparseLayers(image, layers); } } if (True(image.flags & ImageFlagBits::UnwrittenSparse) && diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h index 11e95f98b3..94102cd261 100644 --- a/src/video_core/texture_cache/texture_cache_base.h +++ b/src/video_core/texture_cache/texture_cache_base.h @@ -170,6 +170,9 @@ class TextureCache : public VideoCommon::ChannelSetupCaches BindSparseLayers(Image& image, std::span layers); + /// Uploads `layers` of the image from guest memory. void UploadLayers(Image& image, std::span layers); @@ -464,6 +467,11 @@ private: bool ScaleDown(Image& image); u64 GetScaledImageSizeBytes(const ImageBase& image); + /// The device memory an image counts for in total_used_memory between two driver readings: + /// none for an unbacked image (too large to back, or over unwritten sparse blocks), what is + /// bound to its layers for a SparseLayers one. + u64 ImageMemoryBytes(const Image& image) const; + void QueueAsyncDecode(Image& image, ImageId image_id); void TickAsyncDecode(); 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 #include #include +#include #include #include #include @@ -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 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 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=: 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(device_access_memory - device_initial_usage); diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 39ae25f43a..d8524f6575 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -968,7 +968,9 @@ FN_MAX_LIMIT_LIST u64 GetDeviceMemoryUsage() const; - /// The driver's current memory budget for this process (VK_EXT_memory_budget), 0 without it. + /// The driver's current memory budget for this process (VK_EXT_memory_budget), in + /// GetDeviceMemoryUsage() terms: the usage at which the main device-local heap reaches its + /// budget. 0 without the extension. u64 GetDeviceMemoryBudget() const; u32 GetSetsPerPool() const { @@ -1257,6 +1259,8 @@ private: std::set> supported_extensions; ///< Reported Vulkan extensions. std::set> loaded_extensions; ///< Loaded Vulkan extensions. std::vector valid_heap_memory; ///< Heaps used. + size_t main_heap{}; ///< Largest device-local heap, where images live. + u64 heap_size_limit{}; ///< EDEN_NX2_VRAM_LIMIT_MIB for device-local heaps, 0 if unset. /// Format properties dictionary. ::Common::unordered_map format_properties; -- cgit v1.2.3 From 82963e6750cef756d2ad4d1115dc9dca420d4313 Mon Sep 17 00:00:00 2001 From: néo Luigi <85024393+neoluigi4123@users.noreply.github.com> Date: Sat, 31 Oct 2026 15:33:09 +0100 Subject: 1st pass of review: code cleanup & windows test Assisted-by: Claude Opus 5.5 (review, cleanup and log analysis) --- src/common/settings.cpp | 10 +++++ src/common/settings.h | 6 +++ src/core/core.cpp | 1 + .../renderer_opengl/gl_texture_cache.cpp | 8 ++-- src/video_core/renderer_vulkan/maxwell_to_vk.cpp | 2 +- .../renderer_vulkan/vk_texture_cache.cpp | 33 +++++++-------- src/video_core/surface.cpp | 2 +- src/video_core/texture_cache/util.cpp | 10 ++--- src/video_core/vulkan_common/vulkan_device.cpp | 48 ++++++++++++---------- 9 files changed, 67 insertions(+), 53 deletions(-) (limited to 'src/core/core.cpp') diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 7ce410b99c..c63943a9d3 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -235,6 +235,16 @@ bool CurrentProgramIsSwitch2() { return current_program_is_switch2; } +static std::optional astc_recompression_override; + +AstcRecompression GetAstcRecompression() { + return astc_recompression_override.value_or(values.astc_recompression.GetValue()); +} + +void SetAstcRecompressionOverride(std::optional recompression) { + astc_recompression_override = recompression; +} + bool IsDockedMode() { return values.use_docked_mode.GetValue() == Settings::ConsoleMode::Docked; } diff --git a/src/common/settings.h b/src/common/settings.h index e703254f12..97103f457d 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -990,6 +991,11 @@ u64 GetCurrentProgramID(); void SetCurrentProgramIsSwitch2(bool is_switch2); bool CurrentProgramIsSwitch2(); +// The ASTC recompression in effect: astc_recompression, unless the renderer picked another one +// for the running program (the configuration is left as is). The core clears it on load. +AstcRecompression GetAstcRecompression(); +void SetAstcRecompressionOverride(std::optional recompression); + bool IsOpenGL(); bool IsDockedMode(); diff --git a/src/core/core.cpp b/src/core/core.cpp index ac5e23e9c0..41871df870 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -319,6 +319,7 @@ struct System::Impl { virtual_filesystem != nullptr && IsSwitch2Program(GetGameFileFromPath(virtual_filesystem, filepath)); Settings::SetCurrentProgramIsSwitch2(switch2_program); + Settings::SetAstcRecompressionOverride(std::nullopt); // Switch 2 titles size their heap from the total memory and need the 12 GiB layout. Set it // as a per-game value, so RestoreGlobalState brings back the configured layout afterwards. diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp index 680cb2179f..8cca26aee9 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.cpp +++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp @@ -236,8 +236,7 @@ void ApplySwizzle(GLuint handle, PixelFormat format, std::array OpaqueAstcViewFormats{VK_FORMAT_BC1_RGBA_UNORM return false; } return Settings::values.accelerate_astc.GetValue() == Settings::AstcDecodeMode::Gpu && - Settings::values.astc_recompression.GetValue() != Settings::AstcRecompression::Uncompressed; + Settings::GetAstcRecompression() != Settings::AstcRecompression::Uncompressed; } [[nodiscard]] bool WillUseAcceleratedAstcDecode(const Device& device, const ImageInfo& info) { @@ -253,9 +253,8 @@ constexpr std::array OpaqueAstcViewFormats{VK_FORMAT_BC1_RGBA_UNORM if (Settings::values.accelerate_astc.GetValue() != Settings::AstcDecodeMode::Gpu) { return false; } - return Settings::values.astc_recompression.GetValue() == - Settings::AstcRecompression::Uncompressed && - info.size.depth == 1; + return Settings::GetAstcRecompression() == Settings::AstcRecompression::Uncompressed && + info.size.depth == 1; } // The host shape of an image whose decoded copy would not fit in VRAM: a GPU-decoded ASTC array @@ -1084,13 +1083,13 @@ void BlitScale(Scheduler& scheduler, VkImage src_image, VkImage dst_image, const }); } -// Uncompressed ASTC needs more video memory than a Switch 2 title leaves on cards under 12 GiB -// (Mario Kart World filled a 16 GB card's 13.5 GB budget): there the textures went unbacked -// (garbage) and the game crashed. Use BC3 for this session; the configuration is left as is. +// Uncompressed ASTC needs more video memory than a Switch 2 title leaves under a 12 GiB budget +// (Mario Kart World filled a 16 GB card's 13.5 GB budget; a 12 GB card has ~11.2 GB): there the +// textures went unbacked (garbage) and the game crashed. Use BC3 for this session. void UseBc3ForSwitch2OnSmallCards(const Device& device) { using namespace Common::Literals; if (!Settings::CurrentProgramIsSwitch2() || device.IsOptimalAstcSupported() || - Settings::values.astc_recompression.GetValue() != Settings::AstcRecompression::Uncompressed) { + Settings::GetAstcRecompression() != Settings::AstcRecompression::Uncompressed) { return; } const u64 budget = device.GetDeviceMemoryBudget(); @@ -1100,13 +1099,11 @@ void UseBc3ForSwitch2OnSmallCards(const Device& device) { return; } LOG_WARNING(Render_Vulkan, - "Switch 2 program with {} MiB of video memory: ASTC textures are re-compressed to " - "BC3 for this session (uncompressed ASTC needs 12 GiB or more). Set " - "EDEN_NX2_KEEP_ASTC=1 to keep them uncompressed", + "Switch 2 program with a {} MiB video memory budget: ASTC textures are " + "re-compressed to BC3 for this session (uncompressed ASTC needs a 12 GiB budget). " + "Set EDEN_NX2_KEEP_ASTC=1 to keep them uncompressed", memory >> 20); - // Per-game value, like the Switch 2 memory layout: RestoreGlobalState undoes it. - Settings::values.astc_recompression.SetGlobal(false); - Settings::values.astc_recompression.SetValue(Settings::AstcRecompression::Bc3); + Settings::SetAstcRecompressionOverride(Settings::AstcRecompression::Bc3); } } // Anonymous namespace @@ -1127,8 +1124,7 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched !device.IsOptimalAstcSupported()) { astc_decoder_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, compute_pass_descriptor_queue, memory_allocator); - if (Settings::values.astc_recompression.GetValue() != - Settings::AstcRecompression::Uncompressed) { + if (Settings::GetAstcRecompression() != Settings::AstcRecompression::Uncompressed) { bc_encoder_pass.emplace(device, scheduler, descriptor_pool, compute_pass_descriptor_queue); } @@ -2016,7 +2012,7 @@ bool TextureCacheRuntime::UseOpaqueAstcFormat() const { return value == nullptr || value[0] != '0'; }(); return enabled && !device.IsOptimalAstcSupported() && - Settings::values.astc_recompression.GetValue() == Settings::AstcRecompression::Bc3; + Settings::GetAstcRecompression() == Settings::AstcRecompression::Bc3; } void TextureCacheRuntime::FlushDeferredClear() { @@ -3702,8 +3698,7 @@ void TextureCacheRuntime::TranscodeAstcToBcn( // Encode every level into one buffer, then copy it into the image. const bool bc3 = !IsOpaqueBc1Astc(device, info) && - Settings::values.astc_recompression.GetValue() != - Settings::AstcRecompression::Bc1; + Settings::GetAstcRecompression() != Settings::AstcRecompression::Bc1; const u64 block_bytes = bc3 ? 16 : 8; boost::container::small_vector copies; u64 total = 0; diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp index 4603870bfa..66b9577cb5 100644 --- a/src/video_core/surface.cpp +++ b/src/video_core/surface.cpp @@ -469,7 +469,7 @@ u64 TranscodedAstcSize(u64 base_size, PixelFormat format) { static_cast(DefaultBlockHeight(format)) * RGBA8_PIXEL_SIZE; const u64 uncompressed_size = (base_size * base_block_size) / BytesPerBlock(format); - switch (Settings::values.astc_recompression.GetValue()) { + switch (Settings::GetAstcRecompression()) { case Settings::AstcRecompression::Bc1: return uncompressed_size / 8; case Settings::AstcRecompression::Bc3: diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp index ffc1d59ec3..ef34019a08 100644 --- a/src/video_core/texture_cache/util.cpp +++ b/src/video_core/texture_cache/util.cpp @@ -613,10 +613,10 @@ u32 CalculateConvertedSizeBytes(const ImageInfo& info) noexcept { return info.size.width * BytesPerBlock(info.format); } static constexpr Extent2D TILE_SIZE{1, 1}; - if (IsPixelFormatASTC(info.format) && Settings::values.astc_recompression.GetValue() != - Settings::AstcRecompression::Uncompressed) { + if (IsPixelFormatASTC(info.format) && + Settings::GetAstcRecompression() != Settings::AstcRecompression::Uncompressed) { const u32 bpp_div = - Settings::values.astc_recompression.GetValue() == Settings::AstcRecompression::Bc1 || + Settings::GetAstcRecompression() == Settings::AstcRecompression::Bc1 || info.astc_opaque ? 2 : 1; @@ -931,7 +931,7 @@ boost::container::small_vector UnswizzleImage(Tegra::Memory void ConvertedLayout(const ImageInfo& info, std::span copies) { // Same offsets and sizes as ConvertImage below. - const auto recompression = Settings::values.astc_recompression.GetValue(); + const auto recompression = Settings::GetAstcRecompression(); const bool astc = IsPixelFormatASTC(info.format); u64 output_offset = 0; for (BufferImageCopy& copy : copies) { @@ -976,7 +976,7 @@ void ConvertImage(std::span input, const ImageInfo& info, std::span heap_budgets{}; if (allocator != nullptr) { - std::array heap_budgets{}; vmaGetHeapBudgets(allocator, heap_budgets.data()); - for (const size_t heap : valid_heap_memory) { - const VmaStatistics& statistics = heap_budgets[heap].statistics; - const u64 unused = statistics.blockBytes - statistics.allocationBytes; - result -= (std::min)(result, unused); - } + } + u64 result{}; + for (const size_t heap : valid_heap_memory) { + result += AllocatedUsage(budget.heapUsage[heap], heap_budgets[heap]); } return result; } @@ -1652,16 +1652,15 @@ u64 Device::GetDeviceMemoryBudget() const { } // 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. + // the other heaps use is added the way GetDeviceMemoryUsage() counts it, so the usage reaches + // this when the main heap's does its budget. 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) { 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); + result += AllocatedUsage(budget.heapUsage[heap], heap_budgets[heap]); } } return result; @@ -1681,8 +1680,13 @@ void Device::CollectPhysicalMemoryInfo() { // EDEN_NX2_VRAM_LIMIT_MIB=: 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); + if (heap_size_limit != 0) { + LOG_INFO(Render_Vulkan, "EDEN_NX2_VRAM_LIMIT_MIB: device-local heaps limited to {} MiB", + heap_size_limit >> 20); + } else { + LOG_WARNING(Render_Vulkan, "EDEN_NX2_VRAM_LIMIT_MIB={} is not a size in MiB, ignored", + value); + } } const auto limit = [this](u64 size) { return heap_size_limit != 0 ? (std::min)(size, heap_size_limit) : size; -- cgit v1.2.3