diff options
| author | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-31 23:24:24 -0400 |
|---|---|---|
| committer | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-11-01 08:39:49 -0500 |
| commit | 2f94fb9680cb6b1c35bee33f802ff25fa3560bd2 (patch) | |
| tree | 865c6308d8dac13f28c1825ba54184b44881dbc4 /src | |
| parent | 5de94bdd1d2441c0dba30456a7865756980a4ab1 (diff) | |
vk_pipeline_cache: destroy graphics pipelines unused for 9000 frames
Each built pipeline holds ~0.45 MB of driver memory and none was ever
destroyed, so it grew with every place a session went. One unused for
9000 frames is destroyed once the GPU is past it and built again from
the driver's pipeline cache (2-3 ms) if it is needed later; the
transitions between pipelines are cleared, and pipelines used this
session still go to the used list. Mario Kart World's title screen, 10
minutes: 1,714 evicted, 1,700-1,850 kept instead of a count that only
grows.
(cherry picked from commit 24a37800034f83ec1d8fe9e8e5642d27eb2935b8)
Diffstat (limited to 'src')
4 files changed, 82 insertions, 1 deletions
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h index b1ac5a3fcc..dcb9f48abe 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h @@ -119,6 +119,21 @@ public: : nullptr; } + /// The frame the pipeline cache last handed this pipeline out (evicts long unused ones). + void MarkUsed(u64 frame) noexcept { + last_use_frame = frame; + } + + [[nodiscard]] u64 LastUse() const noexcept { + return last_use_frame; + } + + /// Forgets the transitions, which may point to pipelines the cache evicted. + void ClearTransitions() noexcept { + transition_keys.clear(); + transitions.clear(); + } + [[nodiscard]] bool IsBuilt() const noexcept { return is_built.load(std::memory_order::relaxed); } @@ -159,6 +174,7 @@ private: std::vector<GraphicsPipelineCacheKey> transition_keys; std::vector<GraphicsPipeline*> transitions; + u64 last_use_frame{}; std::array<vk::ShaderModule, NUM_STAGES> spv_modules; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index f877b9d341..2eee5dbf45 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -613,7 +613,8 @@ void PipelineCache::SaveUsedPipelines() { used_pipelines_saved = std::chrono::steady_clock::now(); // A session too short to say what the game uses (a boot to the menu) keeps the old list. constexpr size_t MinimumUsed = 64; - if (used_pipelines_filename.empty() || used_this_session.size() < MinimumUsed) { + if (used_pipelines_filename.empty() || + used_this_session.size() + used_and_evicted.size() < MinimumUsed) { return; } std::unordered_map<u64, u8> next; @@ -623,6 +624,9 @@ void PipelineCache::SaveUsedPipelines() { next.emplace(key.Hash(), u8{0}); } } + for (const u64 hash : used_and_evicted) { + next.emplace(hash, u8{0}); + } for (const auto& [hash, age] : used_pipelines) { if (age + 1 < UsedPipelinesMaxAge) { next.try_emplace(hash, static_cast<u8>(age + 1)); @@ -650,6 +654,7 @@ GraphicsPipeline* PipelineCache::CurrentGraphicsPipeline() { GraphicsPipeline* const next{current_pipeline->Next(graphics_key)}; if (next) { current_pipeline = next; + current_pipeline->MarkUsed(frame_count); return BuiltPipeline(current_pipeline); } } @@ -929,9 +934,58 @@ GraphicsPipeline* PipelineCache::CurrentGraphicsPipelineSlowPath() { current_pipeline->AddTransition(pipeline.get()); } current_pipeline = pipeline.get(); + current_pipeline->MarkUsed(frame_count); return BuiltPipeline(current_pipeline); } +void PipelineCache::TickFrame() { + ++frame_count; + while (!evicted_pipelines.empty() && scheduler.IsFree(evicted_pipelines.front().first)) { + evicted_pipelines.pop_front(); + } + constexpr u64 EvictCheckFrames = 600; + if (frame_count % EvictCheckFrames == 0) { + EvictUnusedPipelines(); + } +} + +void PipelineCache::EvictUnusedPipelines() { + // Each built pipeline holds ~0.45 MB of driver memory and none was ever destroyed, so it grew + // with every place a session went (title 1: 1,500 on its title screen alone). One unused for + // this long is destroyed; it takes 2-3 ms to build again from the driver's pipeline cache. + constexpr u64 EvictAfterFrames = 9000; + if (frame_count <= EvictAfterFrames) { + return; + } + const u64 cutoff = frame_count - EvictAfterFrames; + const u64 tick = scheduler.CurrentTick(); + size_t evicted = 0; + for (auto it = graphics_cache.begin(); it != graphics_cache.end();) { + GraphicsPipeline* const pipeline = it->second.get(); + if (pipeline == nullptr || pipeline == current_pipeline || !pipeline->IsBuilt() || + pipeline->LastUse() >= cutoff) { + ++it; + continue; + } + if (used_this_session.erase(pipeline) != 0) { + used_and_evicted.insert(it->first.Hash()); + } + evicted_pipelines.emplace_back(tick, std::move(it->second)); + it = graphics_cache.erase(it); + ++evicted; + } + if (evicted == 0) { + return; + } + for (auto& [key, pipeline] : graphics_cache) { + if (pipeline) { + pipeline->ClearTransitions(); + } + } + LOG_INFO(Render_Vulkan, "Evicted {} graphics pipelines unused for {} frames, {} left", evicted, + EvictAfterFrames, graphics_cache.size()); +} + GraphicsPipeline* PipelineCache::BuiltPipeline(GraphicsPipeline* pipeline) const noexcept { if (pipeline->IsBuilt()) { return pipeline; @@ -1126,6 +1180,7 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline() { QueueVulkanPipelineCacheFlush(); return pipeline; } + disk_pipelines.insert(graphics_key.Hash()); serialization_thread.QueueWork([this, key = graphics_key, envs = std::move(environments.envs)] { boost::container::static_vector<const GenericEnvironment*, Maxwell::MaxShaderProgram> env_ptrs; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h index 4d1abf83d5..66fa615ea5 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h @@ -10,6 +10,7 @@ #include <atomic> #include <chrono> #include <cstddef> +#include <deque> #include <filesystem> #include <unordered_map> #include <unordered_set> @@ -116,6 +117,8 @@ public: [[nodiscard]] GraphicsPipeline* CurrentGraphicsPipeline(); + void TickFrame(); + [[nodiscard]] ComputePipeline* CurrentComputePipeline(); void LoadDiskResources(u64 title_id, std::stop_token stop_loading, @@ -165,6 +168,12 @@ private: GraphicsPipelineCacheKey graphics_key{}; GraphicsPipeline* current_pipeline{}; + /// Destroys graphics pipelines unused for a while, once the GPU is done with them. + void EvictUnusedPipelines(); + u64 frame_count{}; + std::deque<std::pair<u64, std::unique_ptr<GraphicsPipeline>>> evicted_pipelines; ///< (tick, pipeline) + std::unordered_set<u64> used_and_evicted; ///< Key hashes of pipelines used this session, evicted since + ::Common::unordered_map<ComputePipelineCacheKey, std::unique_ptr<ComputePipeline>> compute_cache; ::Common::unordered_map<GraphicsPipelineCacheKey, std::unique_ptr<GraphicsPipeline>> graphics_cache; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 0826820714..d659e44fe2 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -1193,6 +1193,7 @@ void RasterizerVulkan::FlushCommands() { void RasterizerVulkan::TickFrame() { draw_counter = 0; + pipeline_cache.TickFrame(); guest_descriptor_queue.TickFrame(); compute_pass_descriptor_queue.TickFrame(); descriptor_buffer_ring.TickFrame(); |
