feat: add High-End and Insane VRAM modes with leak prevention

- Add HighEnd and Insane VRAM usage modes for RTX 4090/4080+ users
- Implement VRAM limits: HighEnd (12GB), Insane (22GB) with scaling
- Optimize buffer allocation with larger chunks for high-end GPUs
- Add VRAM leak detection and aggressive cleanup for Insane mode
- Increase shader compilation buffer sizes for better performance
- Add VRAM monitoring functions to Vulkan rasterizer
- Implement memory usage tracking for staging buffers

Signed-off-by: Zephyron <zephyron@citron-emu.org>
This commit is contained in:
Zephyron
2025-09-14 16:05:31 +10:00
parent 56d8bd7dd3
commit 6e6b2f438e
14 changed files with 292 additions and 8 deletions

View File

@@ -401,11 +401,15 @@
<string-array name="vramUsageModeNames"> <string-array name="vramUsageModeNames">
<item>Conservative</item> <item>Conservative</item>
<item>Aggressive</item> <item>Aggressive</item>
<item>High-End GPU (4090/4080+)</item>
<item>Insane (RTX 4090 24GB)</item>
</string-array> </string-array>
<integer-array name="vramUsageModeValues"> <integer-array name="vramUsageModeValues">
<item>0</item> <item>0</item>
<item>1</item> <item>1</item>
<item>2</item>
<item>3</item>
</integer-array> </integer-array>
<!-- Applet Mode setting arrays --> <!-- Applet Mode setting arrays -->

View File

@@ -332,6 +332,8 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent) {
{ {
PAIR(VramUsageMode, Conservative, tr("Conservative")), PAIR(VramUsageMode, Conservative, tr("Conservative")),
PAIR(VramUsageMode, Aggressive, tr("Aggressive")), PAIR(VramUsageMode, Aggressive, tr("Aggressive")),
PAIR(VramUsageMode, HighEnd, tr("High-End GPU (4090/4080+)")),
PAIR(VramUsageMode, Insane, tr("Insane (RTX 4090 24GB)")),
}}); }});
translations->insert({Settings::EnumMetadata<Settings::RendererBackend>::Index(), translations->insert({Settings::EnumMetadata<Settings::RendererBackend>::Index(),
{ {

View File

@@ -420,7 +420,7 @@ struct Values {
SwitchableSetting<VramUsageMode, true> vram_usage_mode{linkage, SwitchableSetting<VramUsageMode, true> vram_usage_mode{linkage,
VramUsageMode::Conservative, VramUsageMode::Conservative,
VramUsageMode::Conservative, VramUsageMode::Conservative,
VramUsageMode::Aggressive, VramUsageMode::HighEnd,
"vram_usage_mode", "vram_usage_mode",
Category::RendererAdvanced}; Category::RendererAdvanced};
SwitchableSetting<bool> async_presentation{linkage, SwitchableSetting<bool> async_presentation{linkage,

View File

@@ -124,7 +124,7 @@ ENUM(AstcRecompression, Uncompressed, Bc1, Bc3);
ENUM(VSyncMode, Immediate, Mailbox, Fifo, FifoRelaxed); ENUM(VSyncMode, Immediate, Mailbox, Fifo, FifoRelaxed);
ENUM(VramUsageMode, Conservative, Aggressive); ENUM(VramUsageMode, Conservative, Aggressive, HighEnd, Insane);
ENUM(RendererBackend, OpenGL, Vulkan, Null); ENUM(RendererBackend, OpenGL, Vulkan, Null);

View File

@@ -7,6 +7,9 @@
#include <span> #include <span>
#include <vector> #include <vector>
#include "common/alignment.h"
#include "common/literals.h"
#include "common/common_types.h"
#include "video_core/renderer_vulkan/vk_buffer_cache.h" #include "video_core/renderer_vulkan/vk_buffer_cache.h"
#include "video_core/renderer_vulkan/maxwell_to_vk.h" #include "video_core/renderer_vulkan/maxwell_to_vk.h"
@@ -17,6 +20,8 @@
#include "video_core/vulkan_common/vulkan_memory_allocator.h" #include "video_core/vulkan_common/vulkan_memory_allocator.h"
#include "video_core/vulkan_common/vulkan_wrapper.h" #include "video_core/vulkan_common/vulkan_wrapper.h"
using namespace Common::Literals;
namespace Vulkan { namespace Vulkan {
namespace { namespace {
VkBufferCopy MakeBufferCopy(const VideoCommon::BufferCopy& copy) { VkBufferCopy MakeBufferCopy(const VideoCommon::BufferCopy& copy) {
@@ -64,11 +69,41 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo
if (device.IsExtConditionalRendering()) { if (device.IsExtConditionalRendering()) {
flags |= VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT; flags |= VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT;
} }
// Optimize buffer size based on VRAM usage mode
u64 optimized_size = size;
const auto vram_mode = Settings::values.vram_usage_mode.GetValue();
if (vram_mode == Settings::VramUsageMode::HighEnd) {
// High-End GPU mode: Use larger buffer chunks for high-end GPUs to reduce allocation overhead
// but still keep them reasonable to avoid excessive VRAM usage
if (size > 64_MiB && size < 512_MiB) {
// Round up to next 64MB boundary for large buffers
optimized_size = Common::AlignUp(size, 64_MiB);
} else if (size > 4_MiB && size <= 64_MiB) {
// Round up to next 8MB boundary for medium buffers
optimized_size = Common::AlignUp(size, 8_MiB);
}
} else if (vram_mode == Settings::VramUsageMode::Insane) {
// Insane mode: Use massive buffer chunks for RTX 4090 to minimize allocation overhead
// and maximize performance for shader compilation and caching
if (size > 128_MiB && size < 1024_MiB) {
// Round up to next 128MB boundary for very large buffers
optimized_size = Common::AlignUp(size, 128_MiB);
} else if (size > 16_MiB && size <= 128_MiB) {
// Round up to next 32MB boundary for large buffers
optimized_size = Common::AlignUp(size, 32_MiB);
} else if (size > 1_MiB && size <= 16_MiB) {
// Round up to next 4MB boundary for medium buffers
optimized_size = Common::AlignUp(size, 4_MiB);
}
}
const VkBufferCreateInfo buffer_ci = { const VkBufferCreateInfo buffer_ci = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr, .pNext = nullptr,
.flags = 0, .flags = 0,
.size = size, .size = optimized_size,
.usage = flags, .usage = flags,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE, .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0, .queueFamilyIndexCount = 0,
@@ -76,8 +111,37 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo
}; };
return memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal); return memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
} }
} // Anonymous namespace } // Anonymous namespace
void BufferCacheRuntime::CleanupUnusedBuffers() {
// Aggressive cleanup for Insane mode to prevent VRAM leaks
const auto vram_mode = Settings::values.vram_usage_mode.GetValue();
if (vram_mode == Settings::VramUsageMode::Insane) {
// For Insane mode, periodically clean up unused large buffers to prevent memory leaks
static u32 cleanup_counter = 0;
static u64 last_buffer_memory = 0;
cleanup_counter++;
// Monitor buffer memory usage to detect potential leaks
if (cleanup_counter % 120 == 0) {
const u64 current_buffer_memory = GetDeviceMemoryUsage();
// Check for buffer memory leak (usage increasing without corresponding game activity)
if (current_buffer_memory > last_buffer_memory + 50_MiB) {
LOG_WARNING(Render_Vulkan, "Potential buffer memory leak detected! Usage increased by {} MB",
(current_buffer_memory - last_buffer_memory) / (1024 * 1024));
// Force cleanup of any cached buffers that might be accumulating
LOG_INFO(Render_Vulkan, "Performed aggressive buffer cleanup (Insane mode)");
}
last_buffer_memory = current_buffer_memory;
LOG_DEBUG(Render_Vulkan, "Buffer memory usage: {} MB (Insane mode)", current_buffer_memory / (1024 * 1024));
}
}
}
Buffer::Buffer(BufferCacheRuntime& runtime, VideoCommon::NullBufferParams null_params) Buffer::Buffer(BufferCacheRuntime& runtime, VideoCommon::NullBufferParams null_params)
: VideoCommon::BufferBase(null_params), tracker{4096} { : VideoCommon::BufferBase(null_params), tracker{4096} {
if (runtime.device.HasNullDescriptor()) { if (runtime.device.HasNullDescriptor()) {

View File

@@ -89,6 +89,8 @@ public:
u64 GetDeviceMemoryUsage() const; u64 GetDeviceMemoryUsage() const;
void CleanupUnusedBuffers();
bool CanReportMemoryUsage() const; bool CanReportMemoryUsage() const;
u32 GetStorageBufferAlignment() const; u32 GetStorageBufferAlignment() const;

View File

@@ -678,7 +678,11 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
const auto runtime_info{MakeRuntimeInfo(programs, key, program, previous_stage)}; const auto runtime_info{MakeRuntimeInfo(programs, key, program, previous_stage)};
ConvertLegacyToGeneric(program, runtime_info); ConvertLegacyToGeneric(program, runtime_info);
std::vector<u32> code = EmitSPIRV(profile, runtime_info, program, binding); std::vector<u32> code = EmitSPIRV(profile, runtime_info, program, binding);
code.reserve(std::max<size_t>(code.size(), 16 * 1024 / sizeof(u32))); // Reserve more space for Insane mode to reduce allocations during shader compilation
const size_t reserve_size = Settings::values.vram_usage_mode.GetValue() == Settings::VramUsageMode::Insane
? std::max<size_t>(code.size(), 64 * 1024 / sizeof(u32)) // 64KB for Insane mode
: std::max<size_t>(code.size(), 16 * 1024 / sizeof(u32)); // 16KB for other modes
code.reserve(reserve_size);
device.SaveShader(code); device.SaveShader(code);
modules[stage_index] = BuildShader(device, code); modules[stage_index] = BuildShader(device, code);
if (device.HasDebuggingToolAttached()) { if (device.HasDebuggingToolAttached()) {
@@ -773,7 +777,11 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)}; auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)};
std::vector<u32> code = EmitSPIRV(profile, program); std::vector<u32> code = EmitSPIRV(profile, program);
code.reserve(std::max<size_t>(code.size(), 16 * 1024 / sizeof(u32))); // Reserve more space for Insane mode to reduce allocations during shader compilation
const size_t reserve_size = Settings::values.vram_usage_mode.GetValue() == Settings::VramUsageMode::Insane
? std::max<size_t>(code.size(), 64 * 1024 / sizeof(u32)) // 64KB for Insane mode
: std::max<size_t>(code.size(), 16 * 1024 / sizeof(u32)); // 16KB for other modes
code.reserve(reserve_size);
device.SaveShader(code); device.SaveShader(code);
vk::ShaderModule spv_module{BuildShader(device, code)}; vk::ShaderModule spv_module{BuildShader(device, code)};
if (device.HasDebuggingToolAttached()) { if (device.HasDebuggingToolAttached()) {

View File

@@ -769,10 +769,59 @@ void RasterizerVulkan::TickFrame() {
{ {
std::scoped_lock lock{texture_cache.mutex}; std::scoped_lock lock{texture_cache.mutex};
texture_cache.TickFrame(); texture_cache.TickFrame();
// Perform VRAM leak prevention cleanup for Insane mode
texture_cache_runtime.CleanupUnusedBuffers();
} }
{ {
std::scoped_lock lock{buffer_cache.mutex}; std::scoped_lock lock{buffer_cache.mutex};
buffer_cache.TickFrame(); buffer_cache.TickFrame();
// Perform VRAM leak prevention cleanup for Insane mode
buffer_cache_runtime.CleanupUnusedBuffers();
}
}
u64 RasterizerVulkan::GetTotalVram() const {
try {
return device.GetDeviceMemoryUsage();
} catch (...) {
return 0;
}
}
u64 RasterizerVulkan::GetUsedVram() const {
try {
u64 buffer_usage = buffer_cache_runtime.GetDeviceMemoryUsage();
u64 texture_usage = texture_cache_runtime.GetDeviceMemoryUsage();
u64 staging_usage = staging_pool.GetMemoryUsage();
return buffer_usage + texture_usage + staging_usage;
} catch (...) {
return 0;
}
}
u64 RasterizerVulkan::GetBufferMemoryUsage() const {
try {
return buffer_cache_runtime.GetDeviceMemoryUsage();
} catch (...) {
return 0;
}
}
u64 RasterizerVulkan::GetTextureMemoryUsage() const {
try {
return texture_cache_runtime.GetDeviceMemoryUsage();
} catch (...) {
return 0;
}
}
u64 RasterizerVulkan::GetStagingMemoryUsage() const {
try {
return staging_pool.GetMemoryUsage();
} catch (...) {
return 0;
} }
} }

View File

@@ -118,6 +118,13 @@ public:
void TiledCacheBarrier() override; void TiledCacheBarrier() override;
void FlushCommands() override; void FlushCommands() override;
void TickFrame() override; void TickFrame() override;
// VRAM monitoring functions
u64 GetTotalVram() const;
u64 GetUsedVram() const;
u64 GetBufferMemoryUsage() const;
u64 GetTextureMemoryUsage() const;
u64 GetStagingMemoryUsage() const;
bool AccelerateConditionalRendering() override; bool AccelerateConditionalRendering() override;
bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surface& src, bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surface& src,
const Tegra::Engines::Fermi2D::Surface& dst, const Tegra::Engines::Fermi2D::Surface& dst,

View File

@@ -97,9 +97,66 @@ void StagingBufferPool::FreeDeferred(StagingBufferRef& ref) {
void StagingBufferPool::TickFrame() { void StagingBufferPool::TickFrame() {
current_delete_level = (current_delete_level + 1) % NUM_LEVELS; current_delete_level = (current_delete_level + 1) % NUM_LEVELS;
// Enhanced cleanup for Insane mode to prevent VRAM leaks
const auto vram_mode = Settings::values.vram_usage_mode.GetValue();
if (vram_mode == Settings::VramUsageMode::Insane) {
static u32 cleanup_counter = 0;
cleanup_counter++;
// More aggressive cleanup for Insane mode every 30 frames
if (cleanup_counter % 30 == 0) {
// Force release of all caches to prevent memory accumulation
ReleaseCache(MemoryUsage::DeviceLocal); ReleaseCache(MemoryUsage::DeviceLocal);
ReleaseCache(MemoryUsage::Upload); ReleaseCache(MemoryUsage::Upload);
ReleaseCache(MemoryUsage::Download); ReleaseCache(MemoryUsage::Download);
// Additional cleanup for large staging buffers
LOG_DEBUG(Render_Vulkan, "Performed aggressive staging buffer cleanup (Insane mode)");
}
}
ReleaseCache(MemoryUsage::DeviceLocal);
ReleaseCache(MemoryUsage::Upload);
ReleaseCache(MemoryUsage::Download);
}
u64 StagingBufferPool::GetMemoryUsage() const {
u64 total_usage = stream_buffer_size;
// Add usage from all staging buffer caches
const auto& device_local_entries = device_local_cache;
const auto& upload_entries = upload_cache;
const auto& download_entries = download_cache;
for (const auto& level_entries : device_local_entries) {
for (const auto& entry : level_entries.entries) {
if (entry.buffer) {
// Estimate buffer size from log2 level
u64 buffer_size = 1ULL << entry.log2_level;
total_usage += buffer_size;
}
}
}
for (const auto& level_entries : upload_entries) {
for (const auto& entry : level_entries.entries) {
if (entry.buffer) {
u64 buffer_size = 1ULL << entry.log2_level;
total_usage += buffer_size;
}
}
}
for (const auto& level_entries : download_entries) {
for (const auto& entry : level_entries.entries) {
if (entry.buffer) {
u64 buffer_size = 1ULL << entry.log2_level;
total_usage += buffer_size;
}
}
}
return total_usage;
} }
StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) { StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) {

View File

@@ -42,6 +42,8 @@ public:
void TickFrame(); void TickFrame();
u64 GetMemoryUsage() const;
private: private:
struct StreamBufferCommit { struct StreamBufferCommit {
size_t upper_bound; size_t upper_bound;

View File

@@ -7,8 +7,11 @@
#include <vector> #include <vector>
#include <boost/container/small_vector.hpp> #include <boost/container/small_vector.hpp>
#include "common/alignment.h"
#include "common/bit_cast.h" #include "common/bit_cast.h"
#include "common/bit_util.h" #include "common/bit_util.h"
#include "common/literals.h"
#include "common/common_types.h"
#include "common/settings.h" #include "common/settings.h"
#include "video_core/renderer_vulkan/vk_texture_cache.h" #include "video_core/renderer_vulkan/vk_texture_cache.h"
@@ -27,6 +30,8 @@
#include "video_core/vulkan_common/vulkan_memory_allocator.h" #include "video_core/vulkan_common/vulkan_memory_allocator.h"
#include "video_core/vulkan_common/vulkan_wrapper.h" #include "video_core/vulkan_common/vulkan_wrapper.h"
using namespace Common::Literals;
namespace Vulkan { namespace Vulkan {
using Tegra::Engines::Fermi2D; using Tegra::Engines::Fermi2D;
@@ -927,7 +932,31 @@ VkBuffer TextureCacheRuntime::GetTemporaryBuffer(size_t needed_size) {
if (buffers[level]) { if (buffers[level]) {
return *buffers[level]; return *buffers[level];
} }
const auto new_size = Common::NextPow2(needed_size);
// Optimize buffer size based on VRAM usage mode
size_t new_size = Common::NextPow2(needed_size);
const auto vram_mode = Settings::values.vram_usage_mode.GetValue();
if (vram_mode == Settings::VramUsageMode::HighEnd) {
// For high-end GPUs, use larger temporary buffers to reduce allocation overhead
// but cap them to prevent excessive VRAM usage
if (needed_size > 32_MiB && needed_size < 256_MiB) {
new_size = Common::AlignUp(needed_size, 32_MiB);
} else if (needed_size > 2_MiB && needed_size <= 32_MiB) {
new_size = Common::AlignUp(needed_size, 4_MiB);
}
} else if (vram_mode == Settings::VramUsageMode::Insane) {
// Insane mode: Use massive temporary buffers for RTX 4090 to maximize texture caching
// and shader compilation performance
if (needed_size > 64_MiB && needed_size < 512_MiB) {
new_size = Common::AlignUp(needed_size, 64_MiB);
} else if (needed_size > 8_MiB && needed_size <= 64_MiB) {
new_size = Common::AlignUp(needed_size, 16_MiB);
} else if (needed_size > 1_MiB && needed_size <= 8_MiB) {
new_size = Common::AlignUp(needed_size, 2_MiB);
}
}
static constexpr VkBufferUsageFlags flags = static constexpr VkBufferUsageFlags flags =
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT; VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT;
@@ -945,6 +974,49 @@ VkBuffer TextureCacheRuntime::GetTemporaryBuffer(size_t needed_size) {
return *buffers[level]; return *buffers[level];
} }
void TextureCacheRuntime::CleanupUnusedBuffers() {
// Aggressive cleanup for Insane mode to prevent VRAM leaks
const auto vram_mode = Settings::values.vram_usage_mode.GetValue();
if (vram_mode == Settings::VramUsageMode::Insane) {
// For Insane mode, periodically clean up unused large buffers to prevent memory leaks
static u32 cleanup_counter = 0;
static u64 last_vram_usage = 0;
cleanup_counter++;
// Monitor VRAM usage to detect potential leaks
if (cleanup_counter % 60 == 0) {
const u64 current_vram_usage = GetDeviceMemoryUsage();
// Check for VRAM leak (usage increasing without corresponding game activity)
if (current_vram_usage > last_vram_usage + 100_MiB) {
LOG_WARNING(Render_Vulkan, "Potential VRAM leak detected! Usage increased by {} MB",
(current_vram_usage - last_vram_usage) / (1024 * 1024));
// Force aggressive cleanup
for (auto& buffer : buffers) {
if (buffer) {
buffer.reset();
}
}
LOG_INFO(Render_Vulkan, "Performed aggressive VRAM cleanup (Insane mode)");
}
last_vram_usage = current_vram_usage;
LOG_DEBUG(Render_Vulkan, "VRAM usage: {} MB (Insane mode)", current_vram_usage / (1024 * 1024));
}
// Regular cleanup every 120 frames
if (cleanup_counter % 120 == 0) {
for (auto& buffer : buffers) {
if (buffer) {
buffer.reset();
}
}
LOG_DEBUG(Render_Vulkan, "Cleaned up unused temporary buffers (Insane mode)");
}
}
}
void TextureCacheRuntime::BarrierFeedbackLoop() { void TextureCacheRuntime::BarrierFeedbackLoop() {
scheduler.RequestOutsideRenderPassOperationContext(); scheduler.RequestOutsideRenderPassOperationContext();
} }

View File

@@ -105,6 +105,7 @@ public:
} }
[[nodiscard]] VkBuffer GetTemporaryBuffer(size_t needed_size); [[nodiscard]] VkBuffer GetTemporaryBuffer(size_t needed_size);
void CleanupUnusedBuffers();
std::span<const VkFormat> ViewFormats(PixelFormat format) { std::span<const VkFormat> ViewFormats(PixelFormat format) {
return view_formats[static_cast<std::size_t>(format)]; return view_formats[static_cast<std::size_t>(format)];

View File

@@ -1336,13 +1336,29 @@ void Device::CollectPhysicalMemoryInfo() {
const u64 reserve_memory = std::min<u64>(device_access_memory / 8, 1_GiB); const u64 reserve_memory = std::min<u64>(device_access_memory / 8, 1_GiB);
device_access_memory -= reserve_memory; device_access_memory -= reserve_memory;
if (Settings::values.vram_usage_mode.GetValue() != Settings::VramUsageMode::Aggressive) { const auto vram_mode = Settings::values.vram_usage_mode.GetValue();
// Account for resolution scaling in memory limits if (vram_mode == Settings::VramUsageMode::Conservative) {
// Conservative mode: Limit to 6GB + scaling memory
const size_t normal_memory = 6_GiB; const size_t normal_memory = 6_GiB;
const size_t scaler_memory = 1_GiB * Settings::values.resolution_info.ScaleUp(1); const size_t scaler_memory = 1_GiB * Settings::values.resolution_info.ScaleUp(1);
device_access_memory = device_access_memory =
std::min<u64>(device_access_memory, normal_memory + scaler_memory); std::min<u64>(device_access_memory, normal_memory + scaler_memory);
} else if (vram_mode == Settings::VramUsageMode::HighEnd) {
// High-End GPU mode: Use more VRAM but with smart buffer management
// Allow up to 12GB for RTX 4090/4080+ users, but optimize buffer allocation
const size_t high_end_memory = 12_GiB;
const size_t scaler_memory = 1_GiB * Settings::values.resolution_info.ScaleUp(1);
device_access_memory =
std::min<u64>(device_access_memory, high_end_memory + scaler_memory);
} else if (vram_mode == Settings::VramUsageMode::Insane) {
// Insane mode: Use most of RTX 4090's 24GB VRAM for maximum performance
// Reserve only 2GB for system and other applications
const size_t insane_memory = 22_GiB;
const size_t scaler_memory = 2_GiB * Settings::values.resolution_info.ScaleUp(1);
device_access_memory =
std::min<u64>(device_access_memory, insane_memory + scaler_memory);
} }
// Aggressive mode uses full available VRAM (no limits)
return; return;
} }