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.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