inline std::string CanonicalizeEnum(Type id) {
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index 9302d6ace..c8d14c78c 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -26,24 +26,60 @@ BufferCache::BufferCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, R
gpu_modified_ranges.Clear();
inline_buffer_id = NULL_BUFFER_ID;
+ // FIXED: VRAM leak prevention - Initialize buffer VRAM management from settings
+ const u32 configured_limit_mb = Settings::values.vram_limit_mb.GetValue();
+
if (!runtime.CanReportMemoryUsage()) {
minimum_memory = DEFAULT_EXPECTED_MEMORY;
critical_memory = DEFAULT_CRITICAL_MEMORY;
+ vram_limit_bytes = configured_limit_mb > 0 ? static_cast(configured_limit_mb) * 1_MiB
+ : 6_GiB;
return;
}
const s64 device_local_memory = static_cast(runtime.GetDeviceLocalMemory());
- const s64 min_spacing_expected = device_local_memory - 1_GiB;
- const s64 min_spacing_critical = device_local_memory - 512_MiB;
- const s64 mem_threshold = std::min(device_local_memory, TARGET_THRESHOLD);
- const s64 min_vacancy_expected = (6 * mem_threshold) / 10;
- const s64 min_vacancy_critical = (2 * mem_threshold) / 10;
- minimum_memory = static_cast(
- std::max(std::min(device_local_memory - min_vacancy_expected, min_spacing_expected),
- DEFAULT_EXPECTED_MEMORY));
- critical_memory = static_cast(
- std::max(std::min(device_local_memory - min_vacancy_critical, min_spacing_critical),
- DEFAULT_CRITICAL_MEMORY));
+
+ // FIXED: VRAM leak prevention - Use configured limit or auto-detect
+ if (configured_limit_mb > 0) {
+ vram_limit_bytes = static_cast(configured_limit_mb) * 1_MiB;
+ } else {
+ vram_limit_bytes = static_cast(device_local_memory * 0.80);
+ }
+
+ // Adjust thresholds based on GC aggressiveness setting
+ const auto gc_level = Settings::values.gc_aggressiveness.GetValue();
+ f32 expected_ratio = 0.5f;
+ f32 critical_ratio = 0.7f;
+
+ switch (gc_level) {
+ case Settings::GCAggressiveness::Off:
+ expected_ratio = 0.90f;
+ critical_ratio = 0.95f;
+ break;
+ case Settings::GCAggressiveness::Light:
+ expected_ratio = 0.70f;
+ critical_ratio = 0.85f;
+ break;
+ case Settings::GCAggressiveness::Moderate:
+ expected_ratio = 0.50f;
+ critical_ratio = 0.70f;
+ break;
+ case Settings::GCAggressiveness::Heavy:
+ expected_ratio = 0.40f;
+ critical_ratio = 0.60f;
+ break;
+ case Settings::GCAggressiveness::Extreme:
+ expected_ratio = 0.30f;
+ critical_ratio = 0.50f;
+ break;
+ }
+
+ minimum_memory = static_cast(vram_limit_bytes * expected_ratio);
+ critical_memory = static_cast(vram_limit_bytes * critical_ratio);
+
+ LOG_INFO(Render_Vulkan,
+ "Buffer cache VRAM initialized: limit={}MB, minimum={}MB, critical={}MB",
+ vram_limit_bytes / 1_MiB, minimum_memory / 1_MiB, critical_memory / 1_MiB);
}
template
@@ -51,20 +87,90 @@ BufferCache::~BufferCache() = default;
template
void BufferCache::RunGarbageCollector() {
+ // FIXED: VRAM leak prevention - Enhanced buffer GC with settings integration
+
+ const auto gc_level = Settings::values.gc_aggressiveness.GetValue();
+ if (gc_level == Settings::GCAggressiveness::Off) {
+ return; // GC disabled by user
+ }
+
const bool aggressive_gc = total_used_memory >= critical_memory;
- const u64 ticks_to_destroy = aggressive_gc ? 60 : 120;
- int num_iterations = aggressive_gc ? 64 : 32;
- const auto clean_up = [this, &num_iterations](BufferId buffer_id) {
+ const bool emergency_gc = total_used_memory >= static_cast(vram_limit_bytes * BUFFER_VRAM_CRITICAL_THRESHOLD);
+
+ // FIXED: VRAM leak prevention - Get eviction frames from settings
+ const u64 eviction_frames = Settings::values.buffer_eviction_frames.GetValue();
+
+ // Adjust based on GC level
+ u64 base_ticks = eviction_frames;
+ int base_iterations = 32;
+
+ switch (gc_level) {
+ case Settings::GCAggressiveness::Light:
+ base_ticks = eviction_frames * 2;
+ base_iterations = 16;
+ break;
+ case Settings::GCAggressiveness::Moderate:
+ base_ticks = eviction_frames;
+ base_iterations = 32;
+ break;
+ case Settings::GCAggressiveness::Heavy:
+ base_ticks = std::max(1ULL, eviction_frames / 2);
+ base_iterations = 64;
+ break;
+ case Settings::GCAggressiveness::Extreme:
+ base_ticks = 1;
+ base_iterations = 128;
+ break;
+ default:
+ break;
+ }
+
+ u64 ticks_to_destroy;
+ int num_iterations;
+
+ if (emergency_gc) {
+ ticks_to_destroy = 1;
+ num_iterations = base_iterations * 4;
+ LOG_WARNING(Render_Vulkan, "Buffer cache emergency GC: usage={}MB, limit={}MB",
+ total_used_memory / 1_MiB, vram_limit_bytes / 1_MiB);
+ } else if (aggressive_gc) {
+ ticks_to_destroy = std::max(1ULL, base_ticks / 2);
+ num_iterations = base_iterations * 2;
+ } else {
+ ticks_to_destroy = base_ticks;
+ num_iterations = base_iterations;
+ }
+
+ u64 bytes_freed = 0;
+ const auto clean_up = [this, &num_iterations, &bytes_freed](BufferId buffer_id) {
if (num_iterations == 0) {
return true;
}
--num_iterations;
auto& buffer = slot_buffers[buffer_id];
+ const u64 buffer_size = buffer.SizeBytes();
+
DownloadBufferMemory(buffer);
DeleteBuffer(buffer_id);
+
+ bytes_freed += buffer_size;
+ --buffer_count;
+ if (buffer_size >= LARGE_BUFFER_THRESHOLD) {
+ large_buffer_memory -= buffer_size;
+ --large_buffer_count;
+ }
return false;
};
lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, clean_up);
+
+ evicted_buffer_bytes += bytes_freed;
+
+ // FIXED: VRAM leak prevention - Log buffer eviction if enabled
+ if (Settings::values.log_vram_usage.GetValue() && bytes_freed > 0) {
+ LOG_INFO(Render_Vulkan, "Buffer GC: evicted {}MB, total={}MB, usage={}MB/{}MB",
+ bytes_freed / 1_MiB, evicted_buffer_bytes / 1_MiB, total_used_memory / 1_MiB,
+ vram_limit_bytes / 1_MiB);
+ }
}
template
@@ -96,9 +202,22 @@ void BufferCache::TickFrame() {
if (runtime.CanReportMemoryUsage()) {
total_used_memory = runtime.GetDeviceMemoryUsage();
}
- if (total_used_memory >= minimum_memory) {
+
+ // FIXED: VRAM leak prevention - Enhanced buffer GC triggering
+ const auto gc_level = Settings::values.gc_aggressiveness.GetValue();
+ const bool should_gc = gc_level != Settings::GCAggressiveness::Off &&
+ (total_used_memory >= minimum_memory ||
+ total_used_memory >= static_cast(vram_limit_bytes * BUFFER_VRAM_WARNING_THRESHOLD));
+
+ if (should_gc) {
RunGarbageCollector();
}
+
+ // FIXED: VRAM leak prevention - Force additional GC if still above critical
+ if (total_used_memory >= critical_memory && gc_level != Settings::GCAggressiveness::Off) {
+ RunGarbageCollector();
+ }
+
++frame_tick;
delayed_destruction_ring.Tick();
@@ -1420,12 +1539,31 @@ template
void BufferCache::ChangeRegister(BufferId buffer_id) {
Buffer& buffer = slot_buffers[buffer_id];
const auto size = buffer.SizeBytes();
+ const u64 aligned_size = Common::AlignUp(size, 1024);
+ const bool is_large = aligned_size >= LARGE_BUFFER_THRESHOLD;
+
if (insert) {
- total_used_memory += Common::AlignUp(size, 1024);
+ total_used_memory += aligned_size;
buffer.setLRUID(lru_cache.Insert(buffer_id, frame_tick));
+
+ // FIXED: VRAM leak prevention - Track buffer statistics
+ ++buffer_count;
+ if (is_large) {
+ large_buffer_memory += aligned_size;
+ ++large_buffer_count;
+ }
} else {
- total_used_memory -= Common::AlignUp(size, 1024);
+ total_used_memory -= aligned_size;
lru_cache.Free(buffer.getLRUID());
+
+ // FIXED: VRAM leak prevention - Update buffer statistics on removal
+ if (buffer_count > 0) {
+ --buffer_count;
+ }
+ if (is_large && large_buffer_count > 0) {
+ large_buffer_memory -= aligned_size;
+ --large_buffer_count;
+ }
}
const DAddr device_addr_begin = buffer.CpuAddr();
const DAddr device_addr_end = device_addr_begin + size;
diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h
index 2e6501419..1d885136c 100644
--- a/src/video_core/buffer_cache/buffer_cache_base.h
+++ b/src/video_core/buffer_cache/buffer_cache_base.h
@@ -175,6 +175,12 @@ class BufferCache : public VideoCommon::ChannelSetupCaches= minimum_memory;
+ }
+
void BindHostIndexBuffer();
void BindHostVertexBuffers();
@@ -488,6 +519,13 @@ public:
u64 critical_memory = 0;
BufferId inline_buffer_id;
+ // FIXED: VRAM leak prevention - Enhanced buffer memory tracking
+ u64 vram_limit_bytes = 0; // Configured VRAM limit for buffers
+ u64 large_buffer_memory = 0; // Memory used by large buffers (>8MB)
+ u64 evicted_buffer_bytes = 0; // Total bytes evicted since start
+ u32 buffer_count = 0; // Total buffer count
+ u32 large_buffer_count = 0; // Large buffer count
+
std::array> CACHING_PAGEBITS)> page_table;
Common::ScratchBuffer tmp_buffer;
};
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index ae70dc479..3e24cd84d 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -159,6 +159,25 @@ void RendererVulkan::Composite(std::span framebu
render_window.OnFrameDisplayed();
};
+ // FIXED: VRAM leak prevention - Check VRAM pressure before rendering
+ if (device.CanReportMemoryUsage()) {
+ const u64 current_usage = device.GetDeviceMemoryUsage();
+ const u64 total_vram = device.GetDeviceLocalMemory();
+ const u32 configured_limit = Settings::values.vram_limit_mb.GetValue();
+ const u64 vram_limit = configured_limit > 0
+ ? static_cast(configured_limit) * 1024ULL * 1024ULL
+ : static_cast(total_vram * 0.80);
+
+ // If VRAM usage is above 90% of limit, trigger emergency GC on texture/buffer caches
+ if (current_usage >= static_cast(vram_limit * 0.90)) {
+ LOG_WARNING(Render_Vulkan,
+ "VRAM pressure critical: {}MB/{}MB ({:.1f}%), triggering emergency GC",
+ current_usage / (1024ULL * 1024ULL), vram_limit / (1024ULL * 1024ULL),
+ (static_cast(current_usage) / vram_limit) * 100.0f);
+ rasterizer.TriggerMemoryGC();
+ }
+ }
+
RenderAppletCaptureLayer(framebuffers);
if (!render_window.IsShown()) {
@@ -201,6 +220,30 @@ void RendererVulkan::Report() const {
LOG_INFO(Render_Vulkan, "Vulkan: {}", api_version);
LOG_INFO(Render_Vulkan, "Available VRAM: {:.2f} GiB", available_vram);
+ // FIXED: VRAM leak prevention - Report VRAM management settings
+ const u32 vram_limit_mb = Settings::values.vram_limit_mb.GetValue();
+ const auto gc_level = Settings::values.gc_aggressiveness.GetValue();
+ const u32 texture_eviction = Settings::values.texture_eviction_frames.GetValue();
+ const u32 buffer_eviction = Settings::values.buffer_eviction_frames.GetValue();
+
+ if (vram_limit_mb > 0) {
+ LOG_INFO(Render_Vulkan, "VRAM Limit: {} MB (configured)", vram_limit_mb);
+ } else {
+ LOG_INFO(Render_Vulkan, "VRAM Limit: Auto ({:.0f} MB, 80% of available)",
+ available_vram * 0.8 * 1024.0);
+ }
+ LOG_INFO(Render_Vulkan, "GC Aggressiveness: {}, Texture eviction: {} frames, Buffer eviction: {} frames",
+ static_cast(gc_level), texture_eviction, buffer_eviction);
+
+ // FIXED: VRAM leak prevention - Report VK_EXT_memory_budget support
+ if (device.CanReportMemoryUsage()) {
+ const auto current_usage = device.GetDeviceMemoryUsage();
+ LOG_INFO(Render_Vulkan, "VK_EXT_memory_budget: Supported, Current usage: {:.2f} GiB",
+ static_cast(current_usage) / f64{1_GiB});
+ } else {
+ LOG_INFO(Render_Vulkan, "VK_EXT_memory_budget: Not supported (using estimates)");
+ }
+
static constexpr auto field = Common::Telemetry::FieldType::UserSystem;
telemetry_session.AddField(field, "GPU_Vendor", vendor_name);
telemetry_session.AddField(field, "GPU_Model", model_name);
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
index c786207f3..e7e8fd58b 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
@@ -70,40 +70,11 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo
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 = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
- .size = optimized_size,
+ .size = size,
.usage = flags,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
@@ -115,31 +86,8 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo
} // 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));
- }
- }
+ // Cleanup is now handled by the VRAM management system (gc_aggressiveness setting)
+ // This function is kept for compatibility but no longer performs mode-specific cleanup
}
Buffer::Buffer(BufferCacheRuntime& runtime, VideoCommon::NullBufferParams null_params)
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index c69f496da..520375840 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -748,11 +748,8 @@ std::unique_ptr PipelineCache::CreateGraphicsPipeline(
const auto runtime_info{MakeRuntimeInfo(programs, key, program, previous_stage)};
ConvertLegacyToGeneric(program, runtime_info);
std::vector code = EmitSPIRV(profile, runtime_info, program, binding);
- // 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(code.size(), 64 * 1024 / sizeof(u32)) // 64KB for Insane mode
- : std::max(code.size(), 16 * 1024 / sizeof(u32)); // 16KB for other modes
- code.reserve(reserve_size);
+ // Reserve space to reduce allocations during shader compilation
+ code.reserve(std::max(code.size(), 16 * 1024 / sizeof(u32)));
device.SaveShader(code);
modules[stage_index] = BuildShader(device, code);
if (device.HasDebuggingToolAttached()) {
@@ -854,11 +851,8 @@ std::unique_ptr PipelineCache::CreateComputePipeline(
auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)};
std::vector code = EmitSPIRV(profile, program);
- // 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(code.size(), 64 * 1024 / sizeof(u32)) // 64KB for Insane mode
- : std::max(code.size(), 16 * 1024 / sizeof(u32)); // 16KB for other modes
- code.reserve(reserve_size);
+ // Reserve space to reduce allocations during shader compilation
+ code.reserve(std::max(code.size(), 16 * 1024 / sizeof(u32)));
device.SaveShader(code);
vk::ShaderModule spv_module{BuildShader(device, code)};
if (device.HasDebuggingToolAttached()) {
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index e9e4bc52a..e0f90c402 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -861,6 +861,17 @@ u64 RasterizerVulkan::GetStagingMemoryUsage() const {
}
}
+// FIXED: VRAM leak prevention - Trigger garbage collection on texture/buffer caches
+void RasterizerVulkan::TriggerMemoryGC() {
+ std::scoped_lock lock{texture_cache.mutex, buffer_cache.mutex};
+
+ // Trigger GC on both caches
+ texture_cache.TriggerGarbageCollection();
+ buffer_cache.TriggerGarbageCollection();
+
+ LOG_DEBUG(Render_Vulkan, "Manual memory GC triggered");
+}
+
bool RasterizerVulkan::AccelerateConditionalRendering() {
gpu_memory->FlushCaching();
return query_cache.AccelerateHostConditionalRendering();
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h
index 9107efa61..e79b86c6e 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.h
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.h
@@ -125,6 +125,10 @@ public:
u64 GetBufferMemoryUsage() const;
u64 GetTextureMemoryUsage() const;
u64 GetStagingMemoryUsage() const;
+
+ // FIXED: VRAM leak prevention - Trigger garbage collection on texture/buffer caches
+ void TriggerMemoryGC();
+
bool AccelerateConditionalRendering() override;
bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surface& src,
const Tegra::Engines::Fermi2D::Surface& dst,
diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
index a6b331e05..deacda44e 100644
--- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
+++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
@@ -99,24 +99,7 @@ void StagingBufferPool::FreeDeferred(StagingBufferRef& ref) {
void StagingBufferPool::TickFrame() {
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::Upload);
- ReleaseCache(MemoryUsage::Download);
-
- // Additional cleanup for large staging buffers
- LOG_DEBUG(Render_Vulkan, "Performed aggressive staging buffer cleanup (Insane mode)");
- }
- }
-
+ // Cleanup is now handled by the VRAM management system (gc_aggressiveness setting)
ReleaseCache(MemoryUsage::DeviceLocal);
ReleaseCache(MemoryUsage::Upload);
ReleaseCache(MemoryUsage::Download);
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index a5ca9b70b..07e1cd614 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -939,29 +939,8 @@ VkBuffer TextureCacheRuntime::GetTemporaryBuffer(size_t needed_size) {
return *buffers[level];
}
- // 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);
- }
- }
+ // Use power-of-2 buffer sizes for efficient allocation
+ const size_t new_size = Common::NextPow2(needed_size);
static constexpr VkBufferUsageFlags flags =
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
@@ -981,46 +960,8 @@ VkBuffer TextureCacheRuntime::GetTemporaryBuffer(size_t needed_size) {
}
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)");
- }
- }
+ // Cleanup is now handled by the VRAM management system (gc_aggressiveness setting)
+ // This function is kept for compatibility but no longer performs mode-specific cleanup
}
void TextureCacheRuntime::BarrierFeedbackLoop() {
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index be4c3c948..f16b0c6fe 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -50,21 +50,59 @@ TextureCache::TextureCache(Runtime& runtime_, Tegra::MaxwellDeviceMemoryManag
void(slot_image_views.insert(runtime, NullImageViewParams{}));
void(slot_samplers.insert(runtime, sampler_descriptor));
+ // FIXED: VRAM leak prevention - Initialize VRAM limit from settings
+ const u32 configured_limit_mb = Settings::values.vram_limit_mb.GetValue();
+
if constexpr (HAS_DEVICE_MEMORY_INFO) {
const s64 device_local_memory = static_cast(runtime.GetDeviceLocalMemory());
- const s64 min_spacing_expected = device_local_memory - 1_GiB;
- const s64 min_spacing_critical = device_local_memory - 512_MiB;
- const s64 mem_threshold = std::min(device_local_memory, TARGET_THRESHOLD);
- const s64 min_vacancy_expected = (6 * mem_threshold) / 10;
- const s64 min_vacancy_critical = (2 * mem_threshold) / 10;
- expected_memory = static_cast(
- std::max(std::min(device_local_memory - min_vacancy_expected, min_spacing_expected),
- DEFAULT_EXPECTED_MEMORY));
- critical_memory = static_cast(
- std::max(std::min(device_local_memory - min_vacancy_critical, min_spacing_critical),
- DEFAULT_CRITICAL_MEMORY));
- minimum_memory = static_cast((device_local_memory - mem_threshold) / 2);
+
+ // FIXED: VRAM leak prevention - Use configured limit or auto-detect (80% of VRAM)
+ if (configured_limit_mb > 0) {
+ vram_limit_bytes = static_cast(configured_limit_mb) * 1_MiB;
+ } else {
+ // Auto-detect: use 80% of available VRAM as limit
+ vram_limit_bytes = static_cast(device_local_memory * 0.80);
+ }
+
+ // Adjust thresholds based on VRAM limit and GC aggressiveness setting
+ const auto gc_level = Settings::values.gc_aggressiveness.GetValue();
+ f32 expected_ratio = 0.6f;
+ f32 critical_ratio = 0.8f;
+
+ switch (gc_level) {
+ case Settings::GCAggressiveness::Off:
+ expected_ratio = 0.95f;
+ critical_ratio = 0.99f;
+ break;
+ case Settings::GCAggressiveness::Light:
+ expected_ratio = 0.75f;
+ critical_ratio = 0.90f;
+ break;
+ case Settings::GCAggressiveness::Moderate:
+ expected_ratio = 0.60f;
+ critical_ratio = 0.80f;
+ break;
+ case Settings::GCAggressiveness::Heavy:
+ expected_ratio = 0.50f;
+ critical_ratio = 0.70f;
+ break;
+ case Settings::GCAggressiveness::Extreme:
+ expected_ratio = 0.40f;
+ critical_ratio = 0.60f;
+ break;
+ }
+
+ expected_memory = static_cast(vram_limit_bytes * expected_ratio);
+ critical_memory = static_cast(vram_limit_bytes * critical_ratio);
+ minimum_memory = static_cast(vram_limit_bytes * 0.25f);
+
+ LOG_INFO(Render_Vulkan,
+ "VRAM Management initialized: limit={}MB, expected={}MB, critical={}MB, gc_level={}",
+ vram_limit_bytes / 1_MiB, expected_memory / 1_MiB, critical_memory / 1_MiB,
+ static_cast(gc_level));
} else {
+ vram_limit_bytes = configured_limit_mb > 0 ? static_cast(configured_limit_mb) * 1_MiB
+ : 6_GiB; // Default 6GB if no info
expected_memory = DEFAULT_EXPECTED_MEMORY + 512_MiB;
critical_memory = DEFAULT_CRITICAL_MEMORY + 1_GiB;
minimum_memory = 0;
@@ -73,37 +111,111 @@ TextureCache::TextureCache(Runtime& runtime_, Tegra::MaxwellDeviceMemoryManag
template
void TextureCache::RunGarbageCollector() {
+ // FIXED: VRAM leak prevention - Enhanced garbage collector with settings integration
+
+ const auto gc_level = Settings::values.gc_aggressiveness.GetValue();
+ if (gc_level == Settings::GCAggressiveness::Off) {
+ return; // GC disabled by user
+ }
+
+ // Reset per-frame stats
+ if (last_gc_frame != frame_tick) {
+ evicted_this_frame = 0;
+ gc_runs_this_frame = 0;
+ last_gc_frame = frame_tick;
+ }
+ ++gc_runs_this_frame;
+
bool high_priority_mode = false;
bool aggressive_mode = false;
+ bool emergency_mode = false;
u64 ticks_to_destroy = 0;
size_t num_iterations = 0;
+ u64 bytes_freed = 0;
- const auto Configure = [&](bool allow_aggressive) {
+ // FIXED: VRAM leak prevention - Get eviction frames from settings
+ const u64 eviction_frames = Settings::values.texture_eviction_frames.GetValue();
+ const bool sparse_priority = Settings::values.sparse_texture_priority_eviction.GetValue();
+
+ const auto Configure = [&](bool allow_aggressive, bool allow_emergency) {
high_priority_mode = total_used_memory >= expected_memory;
aggressive_mode = allow_aggressive && total_used_memory >= critical_memory;
- ticks_to_destroy = aggressive_mode ? 10ULL : high_priority_mode ? 25ULL : 50ULL;
- num_iterations = aggressive_mode ? 40 : (high_priority_mode ? 20 : 10);
+ emergency_mode = allow_emergency && total_used_memory >= static_cast(vram_limit_bytes * VRAM_USAGE_EMERGENCY_THRESHOLD);
+
+ // FIXED: VRAM leak prevention - Adjust iterations based on GC level
+ u64 base_ticks = eviction_frames;
+ size_t base_iterations = 10;
+
+ switch (gc_level) {
+ case Settings::GCAggressiveness::Light:
+ base_ticks = eviction_frames * 2;
+ base_iterations = 5;
+ break;
+ case Settings::GCAggressiveness::Moderate:
+ base_ticks = eviction_frames;
+ base_iterations = 10;
+ break;
+ case Settings::GCAggressiveness::Heavy:
+ base_ticks = std::max(1ULL, eviction_frames / 2);
+ base_iterations = 20;
+ break;
+ case Settings::GCAggressiveness::Extreme:
+ base_ticks = 1;
+ base_iterations = 40;
+ break;
+ default:
+ break;
+ }
+
+ if (emergency_mode) {
+ ticks_to_destroy = 1;
+ num_iterations = base_iterations * 4;
+ } else if (aggressive_mode) {
+ ticks_to_destroy = std::max(1ULL, base_ticks / 2);
+ num_iterations = base_iterations * 2;
+ } else if (high_priority_mode) {
+ ticks_to_destroy = base_ticks;
+ num_iterations = static_cast(base_iterations * 1.5);
+ } else {
+ ticks_to_destroy = base_ticks * 2;
+ num_iterations = base_iterations;
+ }
};
- const auto Cleanup = [this, &num_iterations, &high_priority_mode,
- &aggressive_mode](ImageId image_id) {
+
+ const auto Cleanup = [this, &num_iterations, &high_priority_mode, &aggressive_mode,
+ &emergency_mode, &bytes_freed, sparse_priority](ImageId image_id) {
if (num_iterations == 0) {
return true;
}
--num_iterations;
auto& image = slot_images[image_id];
+
+ // Skip images being decoded
if (True(image.flags & ImageFlagBits::IsDecoding)) {
- // This image is still being decoded, deleting it will invalidate the slot
- // used by the async decoder thread.
return false;
}
- if (!aggressive_mode && True(image.flags & ImageFlagBits::CostlyLoad)) {
- return false;
+
+ // FIXED: VRAM leak prevention - Prioritize sparse textures if enabled
+ const bool is_sparse = True(image.flags & ImageFlagBits::Sparse);
+ const u64 image_size = std::max(image.guest_size_bytes, image.unswizzled_size_bytes);
+ const bool is_large = image_size >= LARGE_TEXTURE_THRESHOLD;
+
+ // Skip costly loads unless aggressive/emergency mode, unless it's a large sparse texture
+ if (!aggressive_mode && !emergency_mode && True(image.flags & ImageFlagBits::CostlyLoad)) {
+ if (!(sparse_priority && is_sparse && image_size >= SPARSE_EVICTION_PRIORITY_THRESHOLD)) {
+ return false;
+ }
}
+
const bool must_download =
image.IsSafeDownload() && False(image.flags & ImageFlagBits::BadOverlap);
- if (!high_priority_mode && must_download) {
+
+ // Skip downloads unless high priority or emergency
+ if (!high_priority_mode && !emergency_mode && must_download) {
return false;
}
+
+ // Perform download if needed
if (must_download) {
auto map = runtime.DownloadStagingBuffer(image.unswizzled_size_bytes);
const auto copies = FullDownloadCopies(image.info);
@@ -112,16 +224,29 @@ void TextureCache::RunGarbageCollector() {
SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span,
swizzle_data_buffer);
}
+
+ // Track eviction statistics
+ bytes_freed += Common::AlignUp(image_size, 1024);
+ if (is_sparse) {
+ sparse_texture_memory -= Common::AlignUp(image_size, 1024);
+ --sparse_texture_count;
+ }
+ if (is_large) {
+ large_texture_memory -= Common::AlignUp(image_size, 1024);
+ }
+
if (True(image.flags & ImageFlagBits::Tracked)) {
UntrackImage(image, image_id);
}
UnregisterImage(image_id);
DeleteImage(image_id, image.scale_tick > frame_tick + 5);
+
+ // Adjust mode based on remaining memory pressure
if (total_used_memory < critical_memory) {
- if (aggressive_mode) {
- // Sink the aggresiveness.
+ if (aggressive_mode || emergency_mode) {
num_iterations >>= 2;
aggressive_mode = false;
+ emergency_mode = false;
return false;
}
if (high_priority_mode && total_used_memory < expected_memory) {
@@ -132,26 +257,80 @@ void TextureCache
::RunGarbageCollector() {
return false;
};
- // Try to remove anything old enough and not high priority.
- Configure(false);
+ // FIXED: VRAM leak prevention - First pass: evict sparse textures if priority enabled
+ if (sparse_priority && sparse_texture_memory > 0 && total_used_memory >= expected_memory) {
+ Configure(false, false);
+ // Target sparse textures specifically
+ lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, [this, &Cleanup](ImageId image_id) {
+ auto& image = slot_images[image_id];
+ if (True(image.flags & ImageFlagBits::Sparse)) {
+ return Cleanup(image_id);
+ }
+ return false;
+ });
+ }
+
+ // Normal pass: remove anything old enough
+ Configure(false, false);
lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, Cleanup);
- // If pressure is still too high, prune aggressively.
+ // Aggressive pass if still above critical
if (total_used_memory >= critical_memory) {
- Configure(true);
+ Configure(true, false);
lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, Cleanup);
}
+
+ // FIXED: VRAM leak prevention - Emergency pass if still above emergency threshold
+ if (total_used_memory >= static_cast(vram_limit_bytes * VRAM_USAGE_EMERGENCY_THRESHOLD)) {
+ Configure(true, true);
+ emergency_gc_triggered = true;
+ LOG_WARNING(Render_Vulkan, "VRAM Emergency GC triggered: usage={}MB, limit={}MB",
+ total_used_memory / 1_MiB, vram_limit_bytes / 1_MiB);
+ lru_cache.ForEachItemBelow(frame_tick, Cleanup); // Evict everything below current frame
+ }
+
+ // Update statistics
+ evicted_this_frame += bytes_freed;
+ evicted_total += bytes_freed;
+
+ // FIXED: VRAM leak prevention - Log VRAM usage if enabled
+ if (Settings::values.log_vram_usage.GetValue() && bytes_freed > 0) {
+ LOG_INFO(Render_Vulkan,
+ "VRAM GC: evicted {}MB this frame, total={}MB, usage={}MB/{}MB ({:.1f}%)",
+ bytes_freed / 1_MiB, evicted_total / 1_MiB, total_used_memory / 1_MiB,
+ vram_limit_bytes / 1_MiB,
+ (static_cast(total_used_memory) / vram_limit_bytes) * 100.0f);
+ }
}
template
void TextureCache::TickFrame() {
+ // FIXED: VRAM leak prevention - Enhanced frame tick with VRAM monitoring
+
+ // Reset emergency flag at start of frame
+ emergency_gc_triggered = false;
+
// If we can obtain the memory info, use it instead of the estimate.
if (runtime.CanReportMemoryUsage()) {
total_used_memory = runtime.GetDeviceMemoryUsage();
}
- if (total_used_memory > minimum_memory) {
+
+ // FIXED: VRAM leak prevention - Check if GC should run based on settings
+ const auto gc_level = Settings::values.gc_aggressiveness.GetValue();
+ const bool should_gc = gc_level != Settings::GCAggressiveness::Off &&
+ (total_used_memory > minimum_memory ||
+ total_used_memory >= static_cast(vram_limit_bytes * VRAM_USAGE_WARNING_THRESHOLD));
+
+ if (should_gc) {
RunGarbageCollector();
}
+
+ // FIXED: VRAM leak prevention - Force additional GC if still above critical after normal GC
+ if (total_used_memory >= critical_memory && gc_level != Settings::GCAggressiveness::Off) {
+ // Run GC again if we're still above critical
+ RunGarbageCollector();
+ }
+
sentenced_images.Tick();
sentenced_framebuffers.Tick();
sentenced_image_view.Tick();
@@ -166,6 +345,183 @@ void TextureCache::TickFrame() {
}
async_buffers_death_ring.clear();
}
+
+ // FIXED: VRAM leak prevention - Periodic VRAM usage logging
+ if (Settings::values.log_vram_usage.GetValue() && (frame_tick % 300 == 0)) {
+ const f32 usage_ratio = vram_limit_bytes > 0
+ ? static_cast(total_used_memory) / vram_limit_bytes
+ : 0.0f;
+ LOG_INFO(Render_Vulkan,
+ "VRAM Status: {}MB/{}MB ({:.1f}%), textures={}, sparse={}, evicted_total={}MB",
+ total_used_memory / 1_MiB, vram_limit_bytes / 1_MiB, usage_ratio * 100.0f,
+ texture_count, sparse_texture_count, evicted_total / 1_MiB);
+ }
+}
+
+// FIXED: VRAM leak prevention - Implementation of new VRAM management methods
+
+template
+void TextureCache::ForceEmergencyGC() {
+ LOG_WARNING(Render_Vulkan, "Force emergency GC triggered: usage={}MB, limit={}MB",
+ total_used_memory / 1_MiB, vram_limit_bytes / 1_MiB);
+
+ emergency_gc_triggered = true;
+ u64 bytes_freed = 0;
+
+ // Evict 10% of textures immediately, prioritizing sparse and large textures
+ const u64 target_bytes = total_used_memory / 10;
+ bytes_freed += EvictSparseTexturesPriority(target_bytes / 2);
+ bytes_freed += EvictToFreeMemory(target_bytes - bytes_freed);
+
+ evicted_this_frame += bytes_freed;
+ evicted_total += bytes_freed;
+
+ LOG_INFO(Render_Vulkan, "Emergency GC freed {}MB", bytes_freed / 1_MiB);
+}
+
+template
+typename TextureCache::VRAMStats TextureCache
::GetVRAMStats() const noexcept {
+ const f32 usage_ratio = vram_limit_bytes > 0
+ ? static_cast(total_used_memory) / vram_limit_bytes
+ : 0.0f;
+ return VRAMStats{
+ .total_used_bytes = total_used_memory,
+ .texture_bytes = total_used_memory - sparse_texture_memory,
+ .sparse_texture_bytes = sparse_texture_memory,
+ .evicted_this_frame = evicted_this_frame,
+ .evicted_total = evicted_total,
+ .texture_count = texture_count,
+ .sparse_texture_count = sparse_texture_count,
+ .usage_ratio = usage_ratio,
+ };
+}
+
+template
+void TextureCache::SetVRAMLimit(u64 limit_bytes) {
+ vram_limit_bytes = limit_bytes;
+
+ // Recalculate thresholds
+ const auto gc_level = Settings::values.gc_aggressiveness.GetValue();
+ f32 expected_ratio = 0.6f;
+ f32 critical_ratio = 0.8f;
+
+ switch (gc_level) {
+ case Settings::GCAggressiveness::Off:
+ expected_ratio = 0.95f;
+ critical_ratio = 0.99f;
+ break;
+ case Settings::GCAggressiveness::Light:
+ expected_ratio = 0.75f;
+ critical_ratio = 0.90f;
+ break;
+ case Settings::GCAggressiveness::Moderate:
+ expected_ratio = 0.60f;
+ critical_ratio = 0.80f;
+ break;
+ case Settings::GCAggressiveness::Heavy:
+ expected_ratio = 0.50f;
+ critical_ratio = 0.70f;
+ break;
+ case Settings::GCAggressiveness::Extreme:
+ expected_ratio = 0.40f;
+ critical_ratio = 0.60f;
+ break;
+ }
+
+ expected_memory = static_cast(vram_limit_bytes * expected_ratio);
+ critical_memory = static_cast(vram_limit_bytes * critical_ratio);
+ minimum_memory = static_cast(vram_limit_bytes * 0.25f);
+
+ LOG_INFO(Render_Vulkan, "VRAM limit updated: {}MB, expected={}MB, critical={}MB",
+ vram_limit_bytes / 1_MiB, expected_memory / 1_MiB, critical_memory / 1_MiB);
+}
+
+template
+bool TextureCache::IsVRAMPressureHigh() const noexcept {
+ return total_used_memory >= expected_memory;
+}
+
+template
+bool TextureCache::IsVRAMPressureCritical() const noexcept {
+ return total_used_memory >= static_cast(vram_limit_bytes * VRAM_USAGE_EMERGENCY_THRESHOLD);
+}
+
+template
+u64 TextureCache::EvictToFreeMemory(u64 target_bytes) {
+ u64 bytes_freed = 0;
+ const u64 start_memory = total_used_memory;
+
+ lru_cache.ForEachItemBelow(frame_tick, [this, &bytes_freed, target_bytes](ImageId image_id) {
+ if (bytes_freed >= target_bytes) {
+ return true;
+ }
+
+ auto& image = slot_images[image_id];
+ if (True(image.flags & ImageFlagBits::IsDecoding)) {
+ return false;
+ }
+
+ const u64 image_size = std::max(image.guest_size_bytes, image.unswizzled_size_bytes);
+
+ if (True(image.flags & ImageFlagBits::Tracked)) {
+ UntrackImage(image, image_id);
+ }
+ UnregisterImage(image_id);
+ DeleteImage(image_id, false);
+
+ bytes_freed += Common::AlignUp(image_size, 1024);
+ return false;
+ });
+
+ return start_memory - total_used_memory;
+}
+
+template
+u64 TextureCache::EvictSparseTexturesPriority(u64 target_bytes) {
+ if (!Settings::values.sparse_texture_priority_eviction.GetValue()) {
+ return 0;
+ }
+
+ u64 bytes_freed = 0;
+
+ // Collect sparse textures and sort by size (largest first)
+ std::vector> sparse_textures;
+ lru_cache.ForEachItemBelow(frame_tick, [this, &sparse_textures](ImageId image_id) {
+ auto& image = slot_images[image_id];
+ if (True(image.flags & ImageFlagBits::Sparse) &&
+ False(image.flags & ImageFlagBits::IsDecoding)) {
+ const u64 size = std::max(image.guest_size_bytes, image.unswizzled_size_bytes);
+ sparse_textures.emplace_back(image_id, size);
+ }
+ return false;
+ });
+
+ // Sort by size descending (largest first for priority eviction)
+ std::sort(sparse_textures.begin(), sparse_textures.end(),
+ [](const auto& a, const auto& b) { return a.second > b.second; });
+
+ for (const auto& [image_id, size] : sparse_textures) {
+ if (bytes_freed >= target_bytes) {
+ break;
+ }
+
+ auto& image = slot_images[image_id];
+ if (True(image.flags & ImageFlagBits::Tracked)) {
+ UntrackImage(image, image_id);
+ }
+ UnregisterImage(image_id);
+ DeleteImage(image_id, false);
+
+ bytes_freed += Common::AlignUp(size, 1024);
+ --sparse_texture_count;
+ sparse_texture_memory -= Common::AlignUp(size, 1024);
+ }
+
+ if (bytes_freed > 0) {
+ LOG_DEBUG(Render_Vulkan, "Sparse texture priority eviction freed {}MB", bytes_freed / 1_MiB);
+ }
+
+ return bytes_freed;
}
template
@@ -2018,7 +2374,22 @@ void TextureCache::RegisterImage(ImageId image_id) {
True(image.flags & ImageFlagBits::Converted)) {
tentative_size = TranscodedAstcSize(tentative_size, image.info.format);
}
- total_used_memory += Common::AlignUp(tentative_size, 1024);
+ const u64 aligned_size = Common::AlignUp(tentative_size, 1024);
+ total_used_memory += aligned_size;
+
+ // FIXED: VRAM leak prevention - Track texture statistics
+ ++texture_count;
+ const bool is_sparse = True(image.flags & ImageFlagBits::Sparse);
+ const bool is_large = aligned_size >= LARGE_TEXTURE_THRESHOLD;
+
+ if (is_sparse) {
+ sparse_texture_memory += aligned_size;
+ ++sparse_texture_count;
+ }
+ if (is_large) {
+ large_texture_memory += aligned_size;
+ }
+
image.lru_index = lru_cache.Insert(image_id, frame_tick);
ForEachGPUPage(image.gpu_addr, image.guest_size_bytes, [this, image_id](u64 page) {
diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h
index ee3ce8be1..70a91e5d4 100644
--- a/src/video_core/texture_cache/texture_cache_base.h
+++ b/src/video_core/texture_cache/texture_cache_base.h
@@ -113,6 +113,14 @@ class TextureCache : public VideoCommon::ChannelSetupCaches 4MB
+ static constexpr size_t LARGE_TEXTURE_THRESHOLD = 16_MiB; // Large texture threshold
+ static constexpr u64 DEFAULT_EVICTION_FRAMES = 2; // Default frames before eviction
+ static constexpr f32 VRAM_USAGE_WARNING_THRESHOLD = 0.75f; // 75% - start warning
+ static constexpr f32 VRAM_USAGE_CRITICAL_THRESHOLD = 0.85f; // 85% - aggressive GC
+ static constexpr f32 VRAM_USAGE_EMERGENCY_THRESHOLD = 0.95f; // 95% - emergency eviction
+
using Runtime = typename P::Runtime;
using Image = typename P::Image;
using ImageAlloc = typename P::ImageAlloc;
@@ -296,6 +304,42 @@ public:
RunGarbageCollector();
}
+ // FIXED: VRAM leak prevention - Enhanced public interface for VRAM management
+
+ /// Force emergency garbage collection when VRAM pressure is critical
+ void ForceEmergencyGC();
+
+ /// Get current VRAM usage statistics
+ struct VRAMStats {
+ u64 total_used_bytes;
+ u64 texture_bytes;
+ u64 sparse_texture_bytes;
+ u64 evicted_this_frame;
+ u64 evicted_total;
+ u32 texture_count;
+ u32 sparse_texture_count;
+ f32 usage_ratio; // Current usage / limit
+ };
+ [[nodiscard]] VRAMStats GetVRAMStats() const noexcept;
+
+ /// Get configured VRAM limit in bytes
+ [[nodiscard]] u64 GetVRAMLimit() const noexcept { return vram_limit_bytes; }
+
+ /// Set VRAM limit (0 = auto-detect)
+ void SetVRAMLimit(u64 limit_bytes);
+
+ /// Check if VRAM pressure is high
+ [[nodiscard]] bool IsVRAMPressureHigh() const noexcept;
+
+ /// Check if VRAM pressure is critical (emergency)
+ [[nodiscard]] bool IsVRAMPressureCritical() const noexcept;
+
+ /// Evict oldest textures to free target_bytes of VRAM
+ u64 EvictToFreeMemory(u64 target_bytes);
+
+ /// Evict sparse textures with priority (large unmapped pages first)
+ u64 EvictSparseTexturesPriority(u64 target_bytes);
+
/// Fills image_view_ids in the image views in indices
template
void FillImageViews(DescriptorTable& table,
@@ -450,6 +494,18 @@ public:
u64 expected_memory;
u64 critical_memory;
+ // FIXED: VRAM leak prevention - Enhanced memory tracking
+ u64 vram_limit_bytes = 0; // Configured VRAM limit (0 = auto)
+ u64 sparse_texture_memory = 0; // Memory used by sparse textures
+ u64 large_texture_memory = 0; // Memory used by large textures (>16MB)
+ u64 evicted_this_frame = 0; // Bytes evicted in current frame
+ u64 evicted_total = 0; // Total bytes evicted since start
+ u32 gc_runs_this_frame = 0; // Number of GC runs this frame
+ u32 texture_count = 0; // Total texture count
+ u32 sparse_texture_count = 0; // Sparse texture count
+ u64 last_gc_frame = 0; // Last frame GC was run
+ bool emergency_gc_triggered = false; // Emergency GC flag
+
struct BufferDownload {
GPUVAddr address;
size_t size;
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index f05ff2372..f1318a045 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -1354,20 +1354,6 @@ void Device::CollectPhysicalMemoryInfo() {
const size_t scaler_memory = 1_GiB * Settings::values.resolution_info.ScaleUp(1);
device_access_memory =
std::min(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(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(device_access_memory, insane_memory + scaler_memory);
}
// Aggressive mode uses full available VRAM (no limits)