summaryrefslogtreecommitdiff
path: root/src/video_core/shader_cache.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/video_core/shader_cache.cpp')
-rw-r--r--src/video_core/shader_cache.cpp250
1 files changed, 250 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..78bf90c48
--- /dev/null
+++ b/src/video_core/shader_cache.cpp
@@ -0,0 +1,250 @@
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
94void ShaderCache::GetGraphicsEnvironments(GraphicsEnvironments& result,
95 const std::array<u64, NUM_PROGRAMS>& unique_hashes) {
96 size_t env_index{};
97 const GPUVAddr base_addr{maxwell3d.regs.code_address.CodeAddress()};
98 for (size_t index = 0; index < NUM_PROGRAMS; ++index) {
99 if (unique_hashes[index] == 0) {
100 continue;
101 }
102 const auto program{static_cast<Tegra::Engines::Maxwell3D::Regs::ShaderProgram>(index)};
103 auto& env{result.envs[index]};
104 const u32 start_address{maxwell3d.regs.shader_config[index].offset};
105 env = GraphicsEnvironment{maxwell3d, gpu_memory, program, base_addr, start_address};
106 env.SetCachedSize(shader_infos[index]->size_bytes);
107 result.env_ptrs[env_index++] = &env;
108 }
109}
110
111ShaderInfo* ShaderCache::TryGet(VAddr addr) const {
112 std::scoped_lock lock{lookup_mutex};
113
114 const auto it = lookup_cache.find(addr);
115 if (it == lookup_cache.end()) {
116 return nullptr;
117 }
118 return it->second->data;
119}
120
121void ShaderCache::Register(std::unique_ptr<ShaderInfo> data, VAddr addr, size_t size) {
122 std::scoped_lock lock{invalidation_mutex, lookup_mutex};
123
124 const VAddr addr_end = addr + size;
125 Entry* const entry = NewEntry(addr, addr_end, data.get());
126
127 const u64 page_end = (addr_end + PAGE_SIZE - 1) >> PAGE_BITS;
128 for (u64 page = addr >> PAGE_BITS; page < page_end; ++page) {
129 invalidation_cache[page].push_back(entry);
130 }
131
132 storage.push_back(std::move(data));
133
134 rasterizer.UpdatePagesCachedCount(addr, size, 1);
135}
136
137void ShaderCache::InvalidatePagesInRegion(VAddr addr, size_t size) {
138 const VAddr addr_end = addr + size;
139 const u64 page_end = (addr_end + PAGE_SIZE - 1) >> PAGE_BITS;
140 for (u64 page = addr >> PAGE_BITS; page < page_end; ++page) {
141 auto it = invalidation_cache.find(page);
142 if (it == invalidation_cache.end()) {
143 continue;
144 }
145 InvalidatePageEntries(it->second, addr, addr_end);
146 }
147}
148
149void ShaderCache::RemovePendingShaders() {
150 if (marked_for_removal.empty()) {
151 return;
152 }
153 // Remove duplicates
154 std::ranges::sort(marked_for_removal);
155 marked_for_removal.erase(std::unique(marked_for_removal.begin(), marked_for_removal.end()),
156 marked_for_removal.end());
157
158 std::vector<ShaderInfo*> removed_shaders;
159 removed_shaders.reserve(marked_for_removal.size());
160
161 std::scoped_lock lock{lookup_mutex};
162
163 for (Entry* const entry : marked_for_removal) {
164 removed_shaders.push_back(entry->data);
165
166 const auto it = lookup_cache.find(entry->addr_start);
167 ASSERT(it != lookup_cache.end());
168 lookup_cache.erase(it);
169 }
170 marked_for_removal.clear();
171
172 if (!removed_shaders.empty()) {
173 RemoveShadersFromStorage(std::move(removed_shaders));
174 }
175}
176
177void ShaderCache::InvalidatePageEntries(std::vector<Entry*>& entries, VAddr addr, VAddr addr_end) {
178 size_t index = 0;
179 while (index < entries.size()) {
180 Entry* const entry = entries[index];
181 if (!entry->Overlaps(addr, addr_end)) {
182 ++index;
183 continue;
184 }
185
186 UnmarkMemory(entry);
187 RemoveEntryFromInvalidationCache(entry);
188 marked_for_removal.push_back(entry);
189 }
190}
191
192void ShaderCache::RemoveEntryFromInvalidationCache(const Entry* entry) {
193 const u64 page_end = (entry->addr_end + PAGE_SIZE - 1) >> PAGE_BITS;
194 for (u64 page = entry->addr_start >> PAGE_BITS; page < page_end; ++page) {
195 const auto entries_it = invalidation_cache.find(page);
196 ASSERT(entries_it != invalidation_cache.end());
197 std::vector<Entry*>& entries = entries_it->second;
198
199 const auto entry_it = std::ranges::find(entries, entry);
200 ASSERT(entry_it != entries.end());
201 entries.erase(entry_it);
202 }
203}
204
205void ShaderCache::UnmarkMemory(Entry* entry) {
206 if (!entry->is_memory_marked) {
207 return;
208 }
209 entry->is_memory_marked = false;
210
211 const VAddr addr = entry->addr_start;
212 const size_t size = entry->addr_end - addr;
213 rasterizer.UpdatePagesCachedCount(addr, size, -1);
214}
215
216void ShaderCache::RemoveShadersFromStorage(std::vector<ShaderInfo*> removed_shaders) {
217 // Remove them from the cache
218 std::erase_if(storage, [&removed_shaders](const std::unique_ptr<ShaderInfo>& shader) {
219 return std::ranges::find(removed_shaders, shader.get()) != removed_shaders.end();
220 });
221}
222
223ShaderCache::Entry* ShaderCache::NewEntry(VAddr addr, VAddr addr_end, ShaderInfo* data) {
224 auto entry = std::make_unique<Entry>(Entry{addr, addr_end, data});
225 Entry* const entry_pointer = entry.get();
226
227 lookup_cache.emplace(addr, std::move(entry));
228 return entry_pointer;
229}
230
231const ShaderInfo* ShaderCache::MakeShaderInfo(GenericEnvironment& env, VAddr cpu_addr) {
232 auto info = std::make_unique<ShaderInfo>();
233 if (const std::optional<u64> cached_hash{env.Analyze()}) {
234 info->unique_hash = *cached_hash;
235 info->size_bytes = env.CachedSize();
236 } else {
237 // Slow path, not really hit on commercial games
238 // Build a control flow graph to get the real shader size
239 Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block;
240 Shader::Maxwell::Flow::CFG cfg{env, flow_block, env.StartAddress()};
241 info->unique_hash = env.CalculateHash();
242 info->size_bytes = env.ReadSize();
243 }
244 const size_t size_bytes{info->size_bytes};
245 const ShaderInfo* const result{info.get()};
246 Register(std::move(info), cpu_addr, size_bytes);
247 return result;
248}
249
250} // namespace VideoCommon