From 373cbb374575d10285fadea048fe19402f9cf06c Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Mon, 13 Jul 2026 11:42:40 -0700 Subject: [PATCH] texture_cache: Add sampler garbage collection (#4700) --- src/video_core/renderer_vulkan/vk_instance.h | 11 +++ src/video_core/texture_cache/sampler.h | 2 + .../texture_cache/texture_cache.cpp | 67 +++++++++++++++++-- src/video_core/texture_cache/texture_cache.h | 9 +++ 4 files changed, 85 insertions(+), 4 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index 1ac45eaa5..5ff77fe71 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -400,6 +400,17 @@ public: return properties.limits.maxFramebufferHeight; } + /// Returns the maximum number of samplers that can be allocated at once. + u32 GetMaxSamplerAllocationCount() const { + if (driver_id == vk::DriverId::eMesaKosmickrisp) { + // FIXME: KosmicKrisp has an internal 1024 unique sampler limit before + // vkCreateSampler starts returning VK_ERROR_OUT_OF_HOST_MEMORY. Work + // around this for now by reducing the value to 1024. + return 1024; + } + return properties.limits.maxSamplerAllocationCount; + } + /// Returns the sample count flags supported by color buffers. vk::SampleCountFlags GetColorSampleCounts() const { return properties.limits.framebufferColorSampleCounts; diff --git a/src/video_core/texture_cache/sampler.h b/src/video_core/texture_cache/sampler.h index 459cc9db5..e60c0f379 100644 --- a/src/video_core/texture_cache/sampler.h +++ b/src/video_core/texture_cache/sampler.h @@ -29,6 +29,8 @@ public: return *handle; } + size_t lru_id{}; + private: vk::UniqueSampler handle; }; diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index 7cb1539af..0e750a267 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -33,6 +33,11 @@ TextureCache::TextureCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& const auto null_id = GetNullImage(vk::Format::eR8G8B8A8Unorm); ASSERT(null_id.index == NULL_IMAGE_ID.index); + u32 max_samplers = instance.GetMaxSamplerAllocationCount(); + trigger_gc_samplers = max_samplers * 3 / 4; + pressure_gc_samplers = max_samplers * 7 / 8; + critical_gc_samplers = max_samplers * 15 / 16; + // Set up garbage collection parameters. if (!instance.CanReportMemoryUsage()) { trigger_gc_memory = 0; @@ -820,7 +825,15 @@ void TextureCache::RefreshImage(Image& image) { vk::Sampler TextureCache::GetSampler(const AmdGpu::Sampler& sampler, AmdGpu::BorderColorBuffer border_color_base) { const u64 hash = XXH3_64bits(&sampler, sizeof(sampler)); + + std::scoped_lock lock{samplers_mutex}; const auto [it, new_sampler] = samplers.try_emplace(hash, instance, sampler, border_color_base); + if (new_sampler) { + samplers.at(hash).lru_id = sampler_lru_cache.Insert(hash, gc_tick); + } else { + sampler_lru_cache.Touch(it->second.lru_id, gc_tick); + } + return it->second.Handle(); } @@ -966,10 +979,7 @@ void TextureCache::UntrackImageTail(ImageId image_id) { tracker.UpdatePageWatchers(addr, size); } -void TextureCache::RunGarbageCollector() { - SCOPE_EXIT { - ++gc_tick; - }; +void TextureCache::GarbageCollectImages() { if (instance.CanReportMemoryUsage()) { total_used_memory = instance.GetDeviceMemoryUsage(); } @@ -1033,6 +1043,55 @@ void TextureCache::RunGarbageCollector() { } } +void TextureCache::GarbageCollectSamplers() { + total_used_samplers = samplers.size(); + if (total_used_samplers < trigger_gc_samplers) { + return; + } + std::scoped_lock lock{samplers_mutex}; + bool pressured = false; + bool aggresive = false; + u64 ticks_to_destroy = 0; + size_t num_deletions = 0; + + const auto configure = [&](bool allow_aggressive) { + pressured = total_used_samplers >= pressure_gc_samplers; + aggresive = allow_aggressive && total_used_samplers >= critical_gc_samplers; + ticks_to_destroy = aggresive ? 160 : pressured ? 80 : 16; + ticks_to_destroy = std::min(ticks_to_destroy, gc_tick); + num_deletions = aggresive ? 40 : pressured ? 20 : 10; + }; + const auto clean_up = [&](u64 hash) { + if (num_deletions == 0) { + return true; + } + --num_deletions; + const size_t lru_id = samplers.at(hash).lru_id; + samplers.erase(hash); + sampler_lru_cache.Free(lru_id); + return false; + }; + + // Try to remove anything old enough and not high priority. + configure(false); + sampler_lru_cache.ForEachItemBelow(gc_tick - ticks_to_destroy, clean_up); + + if (total_used_samplers >= critical_gc_samplers) { + // If we are still over the critical limit, run an aggressive GC + configure(true); + sampler_lru_cache.ForEachItemBelow(gc_tick - ticks_to_destroy, clean_up); + } +} + +void TextureCache::RunGarbageCollector() { + SCOPE_EXIT { + ++gc_tick; + }; + + GarbageCollectImages(); + GarbageCollectSamplers(); +} + void TextureCache::TouchImage(const Image& image) { lru_cache.Touch(image.lru_id, gc_tick); } diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 7ad29bcc6..30923a03e 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -310,6 +310,9 @@ private: DeleteImage(image_id); } + void GarbageCollectImages(); + void GarbageCollectSamplers(); + private: const Vulkan::Instance& instance; Vulkan::Scheduler& scheduler; @@ -327,11 +330,17 @@ private: u64 trigger_gc_memory = 0; u64 pressure_gc_memory = 0; u64 critical_gc_memory = 0; + u64 total_used_samplers = 0; + u64 trigger_gc_samplers = 0; + u64 pressure_gc_samplers = 0; + u64 critical_gc_samplers = 0; u64 gc_tick = 0; Common::LeastRecentlyUsedCache lru_cache; + Common::LeastRecentlyUsedCache sampler_lru_cache; bool readback_linear_images; PageTable page_table; std::mutex mutex; + std::mutex samplers_mutex; struct MetaDataInfo { enum class Type { CMask,