diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 77883b6484..82f0b68863 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -22,6 +22,7 @@ add_library(video_core STATIC buffer_cache/usage_tracker.h buffer_cache/virtual_range_cache.h buffer_cache/word_manager.h + cache_reclaim.h cache_types.h capture.h cdma_pusher.cpp diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 5705954ae6..061611bdb7 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -30,34 +30,52 @@ BufferCache

::BufferCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, R #ifdef YUZU_LEGACY immediately_free = (Settings::values.vram_usage_mode.GetValue() == Settings::VramUsageMode::Aggressive); #endif - if (!runtime.CanReportMemoryUsage()) { - minimum_memory = DEFAULT_EXPECTED_MEMORY; - critical_memory = DEFAULT_CRITICAL_MEMORY; - 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)); + device_local_memory = runtime.GetDeviceLocalMemory(); + const auto thresholds = VideoCommon::MakeReclaimThresholds( + device_local_memory, static_cast(TARGET_THRESHOLD), + static_cast(DEFAULT_EXPECTED_MEMORY), static_cast(DEFAULT_CRITICAL_MEMORY)); + minimum_memory = thresholds.minimum; + expected_memory = thresholds.expected; + critical_memory = thresholds.critical; } template BufferCache

::~BufferCache() = default; +template +void BufferCache

::ReclaimInline() { + if (total_used_memory < minimum_memory) { + return; + } + int num_iterations = 8; + const auto clean_up = [this, &num_iterations](BufferId buffer_id) { + if (num_iterations == 0) { + return true; + } + Buffer& buffer = slot_buffers[buffer_id]; + if (memory_tracker.IsRegionGpuModified(buffer.CpuAddr(), buffer.SizeBytes())) { + return false; + } + --num_iterations; + DeleteBuffer(buffer_id); + return false; + }; + lru_cache.ForEachItemBelow(frame_tick - INLINE_TICKS_TO_DESTROY, clean_up); +} + template void BufferCache

::RunGarbageCollector() { - 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 bool aggressive_gc = heap_pressure || total_used_memory >= critical_memory; + const bool priority_gc = aggressive_gc || total_used_memory >= expected_memory; + u64 ticks_to_destroy = 120; + int num_iterations = 32; + if (aggressive_gc) { + ticks_to_destroy = 30; + num_iterations = 64; + } else if (priority_gc) { + ticks_to_destroy = 60; + num_iterations = 48; + } const auto clean_up = [this, &num_iterations](BufferId buffer_id) { if (num_iterations == 0) { return true; @@ -96,11 +114,12 @@ void BufferCache

::TickFrame() { const bool skip_preferred = hits * 256 < shots * 251; channel_state->uniform_buffer_skip_cache_size = skip_preferred ? DEFAULT_SKIP_CACHE_SIZE : 0; - // If we can obtain the memory info, use it instead of the estimate. - if (runtime.CanReportMemoryUsage()) { - total_used_memory = runtime.GetDeviceMemoryUsage(); + heap_pressure = false; + if (device_local_memory != 0 && runtime.CanReportMemoryUsage()) { + heap_pressure = runtime.GetDeviceMemoryUsage() + HEAP_PRESSURE_HEADROOM >= + device_local_memory; } - if (total_used_memory >= minimum_memory) { + if (total_used_memory >= minimum_memory || heap_pressure) { RunGarbageCollector(); } ++frame_tick; @@ -1674,6 +1693,7 @@ void BufferCache

::JoinOverlap(BufferId new_buffer_id, BufferId overlap_id, template BufferId BufferCache

::CreateBuffer(DAddr device_addr, u32 wanted_size, bool sparse_compatible) { + ReclaimInline(); DAddr device_addr_end = Common::AlignUp(device_addr + wanted_size, CACHING_PAGESIZE); device_addr = Common::AlignDown(device_addr, CACHING_PAGESIZE); wanted_size = static_cast(device_addr_end - device_addr); diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index 18997088d3..8afa93b433 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -31,6 +31,7 @@ #include "video_core/buffer_cache/buffer_base.h" #include "video_core/buffer_cache/virtual_range_cache.h" #include "video_core/control/channel_state_cache.h" +#include "video_core/cache_reclaim.h" #include "video_core/delayed_destruction_ring.h" #include "video_core/dirty_flags.h" #include "video_core/engines/maxwell_3d.h" @@ -199,6 +200,8 @@ class BufferCache : public VideoCommon::ChannelSetupCaches compute_segments; u64 frame_tick = 0; u64 total_used_memory = 0; + u64 device_local_memory = 0; u64 minimum_memory = 0; + u64 expected_memory = 0; u64 critical_memory = 0; + bool heap_pressure = false; BufferId inline_buffer_id; #ifdef YUZU_LEGACY bool immediately_free = false; diff --git a/src/video_core/cache_reclaim.h b/src/video_core/cache_reclaim.h new file mode 100644 index 0000000000..aa5c43552f --- /dev/null +++ b/src/video_core/cache_reclaim.h @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "common/common_types.h" + +namespace VideoCommon { + +struct ReclaimThresholds { + u64 minimum{}; + u64 expected{}; + u64 critical{}; +}; + +[[nodiscard]] constexpr ReclaimThresholds MakeReclaimThresholds(u64 device_local_memory, + u64 target_threshold, + u64 default_expected, + u64 default_critical) { + u64 critical = default_critical; + if (device_local_memory != 0) { + const u64 budget = (std::min)(device_local_memory, target_threshold); + critical = (std::min)(critical, budget / 2); + } + const u64 expected = (std::min)(default_expected, (critical * 3) / 4); + return ReclaimThresholds{ + .minimum = (expected * 3) / 4, + .expected = expected, + .critical = critical, + }; +} + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 489ceda94e..2c3a19eb20 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -58,24 +58,14 @@ TextureCache

::TextureCache(Runtime& runtime_, Tegra::MaxwellDeviceMemoryManag void(slot_samplers.insert(runtime, sampler_descriptor)); 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); - } else { - expected_memory = DEFAULT_EXPECTED_MEMORY + 512_MiB; - critical_memory = DEFAULT_CRITICAL_MEMORY + 1_GiB; - minimum_memory = 0; + device_local_memory = runtime.GetDeviceLocalMemory(); } + const auto thresholds = VideoCommon::MakeReclaimThresholds( + device_local_memory, static_cast(TARGET_THRESHOLD), + static_cast(DEFAULT_EXPECTED_MEMORY), static_cast(DEFAULT_CRITICAL_MEMORY)); + minimum_memory = thresholds.minimum; + expected_memory = thresholds.expected; + critical_memory = thresholds.critical; } template @@ -85,8 +75,9 @@ void TextureCache

::RunGarbageCollector() { u64 ticks_to_destroy = 0; size_t num_iterations = 0; const auto Configure = [&](bool allow_aggressive) { - high_priority_mode = total_used_memory >= expected_memory; - aggressive_mode = allow_aggressive && total_used_memory >= critical_memory; + high_priority_mode = heap_pressure || total_used_memory >= expected_memory; + aggressive_mode = + allow_aggressive && (heap_pressure || 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); }; @@ -100,7 +91,7 @@ void TextureCache

::RunGarbageCollector() { return false; } const bool must_download = IsDownloadable(image) && False(image.flags & ImageFlagBits::BadOverlap); - if ((!aggressive_mode && True(image.flags & ImageFlagBits::CostlyLoad)) || (!high_priority_mode && must_download)) { + if (!aggressive_mode && (must_download || True(image.flags & ImageFlagBits::CostlyLoad))) { return false; } if (must_download) { @@ -126,7 +117,7 @@ void TextureCache

::RunGarbageCollector() { }; Configure(false); lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, Cleanup); - if (total_used_memory >= critical_memory) { + if (heap_pressure || total_used_memory >= critical_memory) { Configure(true); lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, Cleanup); } @@ -134,11 +125,12 @@ void TextureCache

::RunGarbageCollector() { template void TextureCache

::TickFrame() { - // If we can obtain the memory info, use it instead of the estimate. - if (runtime.CanReportMemoryUsage()) { - total_used_memory = runtime.GetDeviceMemoryUsage(); + heap_pressure = false; + if (device_local_memory != 0 && runtime.CanReportMemoryUsage()) { + heap_pressure = runtime.GetDeviceMemoryUsage() + HEAP_PRESSURE_HEADROOM >= + device_local_memory; } - if (total_used_memory > minimum_memory) { + if (total_used_memory > minimum_memory || heap_pressure) { RunGarbageCollector(); } sentenced_images.Tick(); diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h index 29f5bdde83..63b83050e6 100644 --- a/src/video_core/texture_cache/texture_cache_base.h +++ b/src/video_core/texture_cache/texture_cache_base.h @@ -31,6 +31,7 @@ #include "common/thread_worker.h" #include "video_core/compatible_formats.h" #include "video_core/control/channel_state_cache.h" +#include "video_core/cache_reclaim.h" #include "video_core/delayed_destruction_ring.h" #include "video_core/engines/fermi_2d.h" #include "video_core/surface.h" @@ -121,6 +122,7 @@ class TextureCache : public VideoCommon::ChannelSetupCaches