summaryrefslogtreecommitdiff
path: root/src/video_core/shader_cache.cpp
diff options
context:
space:
mode:
authorGravatar ReinUsesLisp2021-04-26 03:53:26 -0300
committerGravatar ameerj2021-07-22 21:51:29 -0400
commit025b20f96ae588777e3ff11083cc4184bf418af6 (patch)
tree7cda9932a219409196adfc8a8d7d5793840657c1 /src/video_core/shader_cache.cpp
parentvulkan: Defer descriptor set work to the Vulkan thread (diff)
downloadyuzu-025b20f96ae588777e3ff11083cc4184bf418af6.tar.gz
yuzu-025b20f96ae588777e3ff11083cc4184bf418af6.tar.xz
yuzu-025b20f96ae588777e3ff11083cc4184bf418af6.zip
shader: Move pipeline cache logic to separate files
Move code to separate files to be able to reuse it from OpenGL. This greatly simplifies the pipeline cache logic on Vulkan. Transform feedback state is not yet abstracted and it's still intrusively stored inside vk_pipeline_cache. It will be moved when needed on OpenGL.
Diffstat (limited to 'src/video_core/shader_cache.cpp')
-rw-r--r--src/video_core/shader_cache.cpp233
1 files changed, 233 insertions, 0 deletions
diff --git a/src/video_core/shader_cache.cpp b/src/video_core/shader_cache.cpp
new file mode 100644
index 000000000..b8b8eace5
--- /dev/null
+++ b/src/video_core/shader_cache.cpp
@@ -0,0 +1,233 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <array>
7#include <vector>
8
9#include "common/assert.h"
10#include "shader_recompiler/frontend/maxwell/control_flow.h"
11#include "shader_recompiler/object_pool.h"
12#include "video_core/dirty_flags.h"
13#include "video_core/engines/kepler_compute.h"
14#include "video_core/engines/maxwell_3d.h"
15#include "video_core/memory_manager.h"
16#include "video_core/shader_cache.h"
17#include "video_core/shader_environment.h"
18
19namespace VideoCommon {
20
21void ShaderCache::InvalidateRegion(VAddr addr, size_t size) {
22 std::scoped_lock lock{invalidation_mutex};
23 InvalidatePagesInRegion(addr, size);
24 RemovePendingShaders();
25}
26
27void ShaderCache::OnCPUWrite(VAddr addr, size_t size) {
28 std::lock_guard lock{invalidation_mutex};
29 InvalidatePagesInRegion(addr, size);
30}
31
32void ShaderCache::SyncGuestHost() {
33 std::scoped_lock lock{invalidation_mutex};
34 RemovePendingShaders();
35}
36
37ShaderCache::ShaderCache(VideoCore::RasterizerInterface& rasterizer_,
38 Tegra::MemoryManager& gpu_memory_, Tegra::Engines::Maxwell3D& maxwell3d_,
39 Tegra::Engines::KeplerCompute& kepler_compute_)
40 : gpu_memory{gpu_memory_}, maxwell3d{maxwell3d_}, kepler_compute{kepler_compute_},
41 rasterizer{rasterizer_} {}
42
43bool ShaderCache::RefreshStages(std::array<u64, 6>& unique_hashes) {
44 auto& dirty{maxwell3d.dirty.flags};
45 if (!dirty[VideoCommon::Dirty::Shaders]) {
46 return last_shaders_valid;
47 }
48 dirty[VideoCommon::Dirty::Shaders] = false;
49
50 const GPUVAddr base_addr{maxwell3d.regs.code_address.CodeAddress()};
51 for (size_t index = 0; index < Tegra::Engines::Maxwell3D::Regs::MaxShaderProgram; ++index) {
52 if (!maxwell3d.regs.IsShaderConfigEnabled(index)) {
53 unique_hashes[index] = 0;
54 continue;
55 }
56 const auto& shader_config{maxwell3d.regs.shader_config[index]};
57 const auto program{static_cast<Tegra::Engines::Maxwell3D::Regs::ShaderProgram>(index)};
58 const GPUVAddr shader_addr{base_addr + shader_config.offset};
59 const std::optional<VAddr> cpu_shader_addr{gpu_memory.GpuToCpuAddress(shader_addr)};
60 if (!cpu_shader_addr) {
61 LOG_ERROR(HW_GPU, "Invalid GPU address for shader 0x{:016x}", shader_addr);
62 last_shaders_valid = false;
63 return false;
64 }
65 const ShaderInfo* shader_info{TryGet(*cpu_shader_addr)};
66 if (!shader_info) {
67 const u32 start_address{shader_config.offset};
68 GraphicsEnvironment env{maxwell3d, gpu_memory, program, base_addr, start_address};
69 shader_info = MakeShaderInfo(env, *cpu_shader_addr);
70 }
71 shader_infos[index] = shader_info;
72 unique_hashes[index] = shader_info->unique_hash;
73 }
74 last_shaders_valid = true;
75 return true;
76}
77
78const ShaderInfo* ShaderCache::ComputeShader() {
79 const GPUVAddr program_base{kepler_compute.regs.code_loc.Address()};
80 const auto& qmd{kepler_compute.launch_description};
81 const GPUVAddr shader_addr{program_base + qmd.program_start};
82 const std::optional<VAddr> cpu_shader_addr{gpu_memory.GpuToCpuAddress(shader_addr)};
83 if (!cpu_shader_addr) {
84 LOG_ERROR(HW_GPU, "Invalid GPU address for shader 0x{:016x}", shader_addr);
85 return nullptr;
86 }
87 if (const ShaderInfo* const shader = TryGet(*cpu_shader_addr)) {
88 return shader;
89 }
90 ComputeEnvironment env{kepler_compute, gpu_memory, program_base, qmd.program_start};
91 return MakeShaderInfo(env, *cpu_shader_addr);
92}
93
94ShaderInfo* ShaderCache::TryGet(VAddr addr) const {
95 std::scoped_lock lock{lookup_mutex};
96
97 const auto it = lookup_cache.find(addr);
98 if (it == lookup_cache.end()) {
99 return nullptr;
100 }
101 return it->second->data;
102}
103
104void ShaderCache::Register(std::unique_ptr<ShaderInfo> data, VAddr addr, size_t size) {
105 std::scoped_lock lock{invalidation_mutex, lookup_mutex};
106
107 const VAddr addr_end = addr + size;
108 Entry* const entry = NewEntry(addr, addr_end, data.get());
109
110 const u64 page_end = (addr_end + PAGE_SIZE - 1) >> PAGE_BITS;
111 for (u64 page = addr >> PAGE_BITS; page < page_end; ++page) {
112 invalidation_cache[page].push_back(entry);
113 }
114
115 storage.push_back(std::move(data));
116
117 rasterizer.UpdatePagesCachedCount(addr, size, 1);
118}
119
120void ShaderCache::InvalidatePagesInRegion(VAddr addr, size_t size) {
121 const VAddr addr_end = addr + size;
122 const u64 page_end = (addr_end + PAGE_SIZE - 1) >> PAGE_BITS;
123 for (u64 page = addr >> PAGE_BITS; page < page_end; ++page) {
124 auto it = invalidation_cache.find(page);
125 if (it == invalidation_cache.end()) {
126 continue;
127 }
128 InvalidatePageEntries(it->second, addr, addr_end);
129 }
130}
131
132void ShaderCache::RemovePendingShaders() {
133 if (marked_for_removal.empty()) {
134 return;
135 }
136 // Remove duplicates
137 std::ranges::sort(marked_for_removal);
138 marked_for_removal.erase(std::unique(marked_for_removal.begin(), marked_for_removal.end()),
139 marked_for_removal.end());
140
141 std::vector<ShaderInfo*> removed_shaders;
142 removed_shaders.reserve(marked_for_removal.size());
143
144 std::scoped_lock lock{lookup_mutex};
145
146 for (Entry* const entry : marked_for_removal) {
147 removed_shaders.push_back(entry->data);
148
149 const auto it = lookup_cache.find(entry->addr_start);
150 ASSERT(it != lookup_cache.end());
151 lookup_cache.erase(it);
152 }
153 marked_for_removal.clear();
154
155 if (!removed_shaders.empty()) {
156 RemoveShadersFromStorage(std::move(removed_shaders));
157 }
158}
159
160void ShaderCache::InvalidatePageEntries(std::vector<Entry*>& entries, VAddr addr, VAddr addr_end) {
161 size_t index = 0;
162 while (index < entries.size()) {
163 Entry* const entry = entries[index];
164 if (!entry->Overlaps(addr, addr_end)) {
165 ++index;
166 continue;
167 }
168
169 UnmarkMemory(entry);
170 RemoveEntryFromInvalidationCache(entry);
171 marked_for_removal.push_back(entry);
172 }
173}
174
175void ShaderCache::RemoveEntryFromInvalidationCache(const Entry* entry) {
176 const u64 page_end = (entry->addr_end + PAGE_SIZE - 1) >> PAGE_BITS;
177 for (u64 page = entry->addr_start >> PAGE_BITS; page < page_end; ++page) {
178 const auto entries_it = invalidation_cache.find(page);
179 ASSERT(entries_it != invalidation_cache.end());
180 std::vector<Entry*>& entries = entries_it->second;
181
182 const auto entry_it = std::ranges::find(entries, entry);
183 ASSERT(entry_it != entries.end());
184 entries.erase(entry_it);
185 }
186}
187
188void ShaderCache::UnmarkMemory(Entry* entry) {
189 if (!entry->is_memory_marked) {
190 return;
191 }
192 entry->is_memory_marked = false;
193
194 const VAddr addr = entry->addr_start;
195 const size_t size = entry->addr_end - addr;
196 rasterizer.UpdatePagesCachedCount(addr, size, -1);
197}
198
199void ShaderCache::RemoveShadersFromStorage(std::vector<ShaderInfo*> removed_shaders) {
200 // Remove them from the cache
201 std::erase_if(storage, [&removed_shaders](const std::unique_ptr<ShaderInfo>& shader) {
202 return std::ranges::find(removed_shaders, shader.get()) != removed_shaders.end();
203 });
204}
205
206ShaderCache::Entry* ShaderCache::NewEntry(VAddr addr, VAddr addr_end, ShaderInfo* data) {
207 auto entry = std::make_unique<Entry>(Entry{addr, addr_end, data});
208 Entry* const entry_pointer = entry.get();
209
210 lookup_cache.emplace(addr, std::move(entry));
211 return entry_pointer;
212}
213
214const ShaderInfo* ShaderCache::MakeShaderInfo(GenericEnvironment& env, VAddr cpu_addr) {
215 auto info = std::make_unique<ShaderInfo>();
216 if (const std::optional<u64> cached_hash{env.Analyze()}) {
217 info->unique_hash = *cached_hash;
218 info->size_bytes = env.CachedSize();
219 } else {
220 // Slow path, not really hit on commercial games
221 // Build a control flow graph to get the real shader size
222 Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block;
223 Shader::Maxwell::Flow::CFG cfg{env, flow_block, env.StartAddress()};
224 info->unique_hash = env.CalculateHash();
225 info->size_bytes = env.ReadSize();
226 }
227 const size_t size_bytes{info->size_bytes};
228 const ShaderInfo* const result{info.get()};
229 Register(std::move(info), cpu_addr, size_bytes);
230 return result;
231}
232
233} // namespace VideoCommon