summaryrefslogtreecommitdiff
path: root/src/video_core/shader_environment.cpp
diff options
context:
space:
mode:
authorGravatar bunnei2021-07-25 11:39:04 -0700
committerGravatar GitHub2021-07-25 11:39:04 -0700
commit98b26b6e126d4775fdf3f773fe8a8ac808a8ff8f (patch)
tree816faa96c2c4d291825063433331a8ea4b3d08f1 /src/video_core/shader_environment.cpp
parentMerge pull request #6699 from lat9nq/common-threads (diff)
parentshader: Support out of bound local memory reads and immediate writes (diff)
downloadyuzu-98b26b6e126d4775fdf3f773fe8a8ac808a8ff8f.tar.gz
yuzu-98b26b6e126d4775fdf3f773fe8a8ac808a8ff8f.tar.xz
yuzu-98b26b6e126d4775fdf3f773fe8a8ac808a8ff8f.zip
Merge pull request #6585 from ameerj/hades
Shader Decompiler Rewrite
Diffstat (limited to 'src/video_core/shader_environment.cpp')
-rw-r--r--src/video_core/shader_environment.cpp460
1 files changed, 460 insertions, 0 deletions
diff --git a/src/video_core/shader_environment.cpp b/src/video_core/shader_environment.cpp
new file mode 100644
index 000000000..8a4581c19
--- /dev/null
+++ b/src/video_core/shader_environment.cpp
@@ -0,0 +1,460 @@
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 <filesystem>
6#include <fstream>
7#include <memory>
8#include <optional>
9#include <utility>
10
11#include "common/assert.h"
12#include "common/cityhash.h"
13#include "common/common_types.h"
14#include "common/div_ceil.h"
15#include "common/fs/fs.h"
16#include "common/logging/log.h"
17#include "shader_recompiler/environment.h"
18#include "video_core/memory_manager.h"
19#include "video_core/shader_environment.h"
20#include "video_core/textures/texture.h"
21
22namespace VideoCommon {
23
24constexpr std::array<char, 8> MAGIC_NUMBER{'y', 'u', 'z', 'u', 'c', 'a', 'c', 'h'};
25
26constexpr size_t INST_SIZE = sizeof(u64);
27
28using Maxwell = Tegra::Engines::Maxwell3D::Regs;
29
30static u64 MakeCbufKey(u32 index, u32 offset) {
31 return (static_cast<u64>(index) << 32) | offset;
32}
33
34static Shader::TextureType ConvertType(const Tegra::Texture::TICEntry& entry) {
35 switch (entry.texture_type) {
36 case Tegra::Texture::TextureType::Texture1D:
37 return Shader::TextureType::Color1D;
38 case Tegra::Texture::TextureType::Texture2D:
39 case Tegra::Texture::TextureType::Texture2DNoMipmap:
40 return Shader::TextureType::Color2D;
41 case Tegra::Texture::TextureType::Texture3D:
42 return Shader::TextureType::Color3D;
43 case Tegra::Texture::TextureType::TextureCubemap:
44 return Shader::TextureType::ColorCube;
45 case Tegra::Texture::TextureType::Texture1DArray:
46 return Shader::TextureType::ColorArray1D;
47 case Tegra::Texture::TextureType::Texture2DArray:
48 return Shader::TextureType::ColorArray2D;
49 case Tegra::Texture::TextureType::Texture1DBuffer:
50 return Shader::TextureType::Buffer;
51 case Tegra::Texture::TextureType::TextureCubeArray:
52 return Shader::TextureType::ColorArrayCube;
53 default:
54 throw Shader::NotImplementedException("Unknown texture type");
55 }
56}
57
58GenericEnvironment::GenericEnvironment(Tegra::MemoryManager& gpu_memory_, GPUVAddr program_base_,
59 u32 start_address_)
60 : gpu_memory{&gpu_memory_}, program_base{program_base_} {
61 start_address = start_address_;
62}
63
64GenericEnvironment::~GenericEnvironment() = default;
65
66u32 GenericEnvironment::TextureBoundBuffer() const {
67 return texture_bound;
68}
69
70u32 GenericEnvironment::LocalMemorySize() const {
71 return local_memory_size;
72}
73
74u32 GenericEnvironment::SharedMemorySize() const {
75 return shared_memory_size;
76}
77
78std::array<u32, 3> GenericEnvironment::WorkgroupSize() const {
79 return workgroup_size;
80}
81
82u64 GenericEnvironment::ReadInstruction(u32 address) {
83 read_lowest = std::min(read_lowest, address);
84 read_highest = std::max(read_highest, address);
85
86 if (address >= cached_lowest && address < cached_highest) {
87 return code[(address - cached_lowest) / INST_SIZE];
88 }
89 has_unbound_instructions = true;
90 return gpu_memory->Read<u64>(program_base + address);
91}
92
93std::optional<u64> GenericEnvironment::Analyze() {
94 const std::optional<u64> size{TryFindSize()};
95 if (!size) {
96 return std::nullopt;
97 }
98 cached_lowest = start_address;
99 cached_highest = start_address + static_cast<u32>(*size);
100 return Common::CityHash64(reinterpret_cast<const char*>(code.data()), *size);
101}
102
103void GenericEnvironment::SetCachedSize(size_t size_bytes) {
104 cached_lowest = start_address;
105 cached_highest = start_address + static_cast<u32>(size_bytes);
106 code.resize(CachedSize());
107 gpu_memory->ReadBlock(program_base + cached_lowest, code.data(), code.size() * sizeof(u64));
108}
109
110size_t GenericEnvironment::CachedSize() const noexcept {
111 return cached_highest - cached_lowest + INST_SIZE;
112}
113
114size_t GenericEnvironment::ReadSize() const noexcept {
115 return read_highest - read_lowest + INST_SIZE;
116}
117
118bool GenericEnvironment::CanBeSerialized() const noexcept {
119 return !has_unbound_instructions;
120}
121
122u64 GenericEnvironment::CalculateHash() const {
123 const size_t size{ReadSize()};
124 const auto data{std::make_unique<char[]>(size)};
125 gpu_memory->ReadBlock(program_base + read_lowest, data.get(), size);
126 return Common::CityHash64(data.get(), size);
127}
128
129void GenericEnvironment::Serialize(std::ofstream& file) const {
130 const u64 code_size{static_cast<u64>(CachedSize())};
131 const u64 num_texture_types{static_cast<u64>(texture_types.size())};
132 const u64 num_cbuf_values{static_cast<u64>(cbuf_values.size())};
133
134 file.write(reinterpret_cast<const char*>(&code_size), sizeof(code_size))
135 .write(reinterpret_cast<const char*>(&num_texture_types), sizeof(num_texture_types))
136 .write(reinterpret_cast<const char*>(&num_cbuf_values), sizeof(num_cbuf_values))
137 .write(reinterpret_cast<const char*>(&local_memory_size), sizeof(local_memory_size))
138 .write(reinterpret_cast<const char*>(&texture_bound), sizeof(texture_bound))
139 .write(reinterpret_cast<const char*>(&start_address), sizeof(start_address))
140 .write(reinterpret_cast<const char*>(&cached_lowest), sizeof(cached_lowest))
141 .write(reinterpret_cast<const char*>(&cached_highest), sizeof(cached_highest))
142 .write(reinterpret_cast<const char*>(&stage), sizeof(stage))
143 .write(reinterpret_cast<const char*>(code.data()), code_size);
144 for (const auto [key, type] : texture_types) {
145 file.write(reinterpret_cast<const char*>(&key), sizeof(key))
146 .write(reinterpret_cast<const char*>(&type), sizeof(type));
147 }
148 for (const auto [key, type] : cbuf_values) {
149 file.write(reinterpret_cast<const char*>(&key), sizeof(key))
150 .write(reinterpret_cast<const char*>(&type), sizeof(type));
151 }
152 if (stage == Shader::Stage::Compute) {
153 file.write(reinterpret_cast<const char*>(&workgroup_size), sizeof(workgroup_size))
154 .write(reinterpret_cast<const char*>(&shared_memory_size), sizeof(shared_memory_size));
155 } else {
156 file.write(reinterpret_cast<const char*>(&sph), sizeof(sph));
157 if (stage == Shader::Stage::Geometry) {
158 file.write(reinterpret_cast<const char*>(&gp_passthrough_mask),
159 sizeof(gp_passthrough_mask));
160 }
161 }
162}
163
164std::optional<u64> GenericEnvironment::TryFindSize() {
165 static constexpr size_t BLOCK_SIZE = 0x1000;
166 static constexpr size_t MAXIMUM_SIZE = 0x100000;
167
168 static constexpr u64 SELF_BRANCH_A = 0xE2400FFFFF87000FULL;
169 static constexpr u64 SELF_BRANCH_B = 0xE2400FFFFF07000FULL;
170
171 GPUVAddr guest_addr{program_base + start_address};
172 size_t offset{0};
173 size_t size{BLOCK_SIZE};
174 while (size <= MAXIMUM_SIZE) {
175 code.resize(size / INST_SIZE);
176 u64* const data = code.data() + offset / INST_SIZE;
177 gpu_memory->ReadBlock(guest_addr, data, BLOCK_SIZE);
178 for (size_t index = 0; index < BLOCK_SIZE; index += INST_SIZE) {
179 const u64 inst = data[index / INST_SIZE];
180 if (inst == SELF_BRANCH_A || inst == SELF_BRANCH_B) {
181 return offset + index;
182 }
183 }
184 guest_addr += BLOCK_SIZE;
185 size += BLOCK_SIZE;
186 offset += BLOCK_SIZE;
187 }
188 return std::nullopt;
189}
190
191Shader::TextureType GenericEnvironment::ReadTextureTypeImpl(GPUVAddr tic_addr, u32 tic_limit,
192 bool via_header_index, u32 raw) {
193 const auto handle{Tegra::Texture::TexturePair(raw, via_header_index)};
194 const GPUVAddr descriptor_addr{tic_addr + handle.first * sizeof(Tegra::Texture::TICEntry)};
195 Tegra::Texture::TICEntry entry;
196 gpu_memory->ReadBlock(descriptor_addr, &entry, sizeof(entry));
197 const Shader::TextureType result{ConvertType(entry)};
198 texture_types.emplace(raw, result);
199 return result;
200}
201
202GraphicsEnvironment::GraphicsEnvironment(Tegra::Engines::Maxwell3D& maxwell3d_,
203 Tegra::MemoryManager& gpu_memory_,
204 Maxwell::ShaderProgram program, GPUVAddr program_base_,
205 u32 start_address_)
206 : GenericEnvironment{gpu_memory_, program_base_, start_address_}, maxwell3d{&maxwell3d_} {
207 gpu_memory->ReadBlock(program_base + start_address, &sph, sizeof(sph));
208 gp_passthrough_mask = maxwell3d->regs.gp_passthrough_mask;
209 switch (program) {
210 case Maxwell::ShaderProgram::VertexA:
211 stage = Shader::Stage::VertexA;
212 stage_index = 0;
213 break;
214 case Maxwell::ShaderProgram::VertexB:
215 stage = Shader::Stage::VertexB;
216 stage_index = 0;
217 break;
218 case Maxwell::ShaderProgram::TesselationControl:
219 stage = Shader::Stage::TessellationControl;
220 stage_index = 1;
221 break;
222 case Maxwell::ShaderProgram::TesselationEval:
223 stage = Shader::Stage::TessellationEval;
224 stage_index = 2;
225 break;
226 case Maxwell::ShaderProgram::Geometry:
227 stage = Shader::Stage::Geometry;
228 stage_index = 3;
229 break;
230 case Maxwell::ShaderProgram::Fragment:
231 stage = Shader::Stage::Fragment;
232 stage_index = 4;
233 break;
234 default:
235 UNREACHABLE_MSG("Invalid program={}", program);
236 break;
237 }
238 const u64 local_size{sph.LocalMemorySize()};
239 ASSERT(local_size <= std::numeric_limits<u32>::max());
240 local_memory_size = static_cast<u32>(local_size) + sph.common3.shader_local_memory_crs_size;
241 texture_bound = maxwell3d->regs.tex_cb_index;
242}
243
244u32 GraphicsEnvironment::ReadCbufValue(u32 cbuf_index, u32 cbuf_offset) {
245 const auto& cbuf{maxwell3d->state.shader_stages[stage_index].const_buffers[cbuf_index]};
246 ASSERT(cbuf.enabled);
247 u32 value{};
248 if (cbuf_offset < cbuf.size) {
249 value = gpu_memory->Read<u32>(cbuf.address + cbuf_offset);
250 }
251 cbuf_values.emplace(MakeCbufKey(cbuf_index, cbuf_offset), value);
252 return value;
253}
254
255Shader::TextureType GraphicsEnvironment::ReadTextureType(u32 handle) {
256 const auto& regs{maxwell3d->regs};
257 const bool via_header_index{regs.sampler_index == Maxwell::SamplerIndex::ViaHeaderIndex};
258 return ReadTextureTypeImpl(regs.tic.Address(), regs.tic.limit, via_header_index, handle);
259}
260
261ComputeEnvironment::ComputeEnvironment(Tegra::Engines::KeplerCompute& kepler_compute_,
262 Tegra::MemoryManager& gpu_memory_, GPUVAddr program_base_,
263 u32 start_address_)
264 : GenericEnvironment{gpu_memory_, program_base_, start_address_}, kepler_compute{
265 &kepler_compute_} {
266 const auto& qmd{kepler_compute->launch_description};
267 stage = Shader::Stage::Compute;
268 local_memory_size = qmd.local_pos_alloc + qmd.local_crs_alloc;
269 texture_bound = kepler_compute->regs.tex_cb_index;
270 shared_memory_size = qmd.shared_alloc;
271 workgroup_size = {qmd.block_dim_x, qmd.block_dim_y, qmd.block_dim_z};
272}
273
274u32 ComputeEnvironment::ReadCbufValue(u32 cbuf_index, u32 cbuf_offset) {
275 const auto& qmd{kepler_compute->launch_description};
276 ASSERT(((qmd.const_buffer_enable_mask.Value() >> cbuf_index) & 1) != 0);
277 const auto& cbuf{qmd.const_buffer_config[cbuf_index]};
278 u32 value{};
279 if (cbuf_offset < cbuf.size) {
280 value = gpu_memory->Read<u32>(cbuf.Address() + cbuf_offset);
281 }
282 cbuf_values.emplace(MakeCbufKey(cbuf_index, cbuf_offset), value);
283 return value;
284}
285
286Shader::TextureType ComputeEnvironment::ReadTextureType(u32 handle) {
287 const auto& regs{kepler_compute->regs};
288 const auto& qmd{kepler_compute->launch_description};
289 return ReadTextureTypeImpl(regs.tic.Address(), regs.tic.limit, qmd.linked_tsc != 0, handle);
290}
291
292void FileEnvironment::Deserialize(std::ifstream& file) {
293 u64 code_size{};
294 u64 num_texture_types{};
295 u64 num_cbuf_values{};
296 file.read(reinterpret_cast<char*>(&code_size), sizeof(code_size))
297 .read(reinterpret_cast<char*>(&num_texture_types), sizeof(num_texture_types))
298 .read(reinterpret_cast<char*>(&num_cbuf_values), sizeof(num_cbuf_values))
299 .read(reinterpret_cast<char*>(&local_memory_size), sizeof(local_memory_size))
300 .read(reinterpret_cast<char*>(&texture_bound), sizeof(texture_bound))
301 .read(reinterpret_cast<char*>(&start_address), sizeof(start_address))
302 .read(reinterpret_cast<char*>(&read_lowest), sizeof(read_lowest))
303 .read(reinterpret_cast<char*>(&read_highest), sizeof(read_highest))
304 .read(reinterpret_cast<char*>(&stage), sizeof(stage));
305 code = std::make_unique<u64[]>(Common::DivCeil(code_size, sizeof(u64)));
306 file.read(reinterpret_cast<char*>(code.get()), code_size);
307 for (size_t i = 0; i < num_texture_types; ++i) {
308 u32 key;
309 Shader::TextureType type;
310 file.read(reinterpret_cast<char*>(&key), sizeof(key))
311 .read(reinterpret_cast<char*>(&type), sizeof(type));
312 texture_types.emplace(key, type);
313 }
314 for (size_t i = 0; i < num_cbuf_values; ++i) {
315 u64 key;
316 u32 value;
317 file.read(reinterpret_cast<char*>(&key), sizeof(key))
318 .read(reinterpret_cast<char*>(&value), sizeof(value));
319 cbuf_values.emplace(key, value);
320 }
321 if (stage == Shader::Stage::Compute) {
322 file.read(reinterpret_cast<char*>(&workgroup_size), sizeof(workgroup_size))
323 .read(reinterpret_cast<char*>(&shared_memory_size), sizeof(shared_memory_size));
324 } else {
325 file.read(reinterpret_cast<char*>(&sph), sizeof(sph));
326 if (stage == Shader::Stage::Geometry) {
327 file.read(reinterpret_cast<char*>(&gp_passthrough_mask), sizeof(gp_passthrough_mask));
328 }
329 }
330}
331
332u64 FileEnvironment::ReadInstruction(u32 address) {
333 if (address < read_lowest || address > read_highest) {
334 throw Shader::LogicError("Out of bounds address {}", address);
335 }
336 return code[(address - read_lowest) / sizeof(u64)];
337}
338
339u32 FileEnvironment::ReadCbufValue(u32 cbuf_index, u32 cbuf_offset) {
340 const auto it{cbuf_values.find(MakeCbufKey(cbuf_index, cbuf_offset))};
341 if (it == cbuf_values.end()) {
342 throw Shader::LogicError("Uncached read texture type");
343 }
344 return it->second;
345}
346
347Shader::TextureType FileEnvironment::ReadTextureType(u32 handle) {
348 const auto it{texture_types.find(handle)};
349 if (it == texture_types.end()) {
350 throw Shader::LogicError("Uncached read texture type");
351 }
352 return it->second;
353}
354
355u32 FileEnvironment::LocalMemorySize() const {
356 return local_memory_size;
357}
358
359u32 FileEnvironment::SharedMemorySize() const {
360 return shared_memory_size;
361}
362
363u32 FileEnvironment::TextureBoundBuffer() const {
364 return texture_bound;
365}
366
367std::array<u32, 3> FileEnvironment::WorkgroupSize() const {
368 return workgroup_size;
369}
370
371void SerializePipeline(std::span<const char> key, std::span<const GenericEnvironment* const> envs,
372 const std::filesystem::path& filename, u32 cache_version) try {
373 std::ofstream file(filename, std::ios::binary | std::ios::ate | std::ios::app);
374 file.exceptions(std::ifstream::failbit);
375 if (!file.is_open()) {
376 LOG_ERROR(Common_Filesystem, "Failed to open pipeline cache file {}",
377 Common::FS::PathToUTF8String(filename));
378 return;
379 }
380 if (file.tellp() == 0) {
381 // Write header
382 file.write(MAGIC_NUMBER.data(), MAGIC_NUMBER.size())
383 .write(reinterpret_cast<const char*>(&cache_version), sizeof(cache_version));
384 }
385 if (!std::ranges::all_of(envs, &GenericEnvironment::CanBeSerialized)) {
386 return;
387 }
388 const u32 num_envs{static_cast<u32>(envs.size())};
389 file.write(reinterpret_cast<const char*>(&num_envs), sizeof(num_envs));
390 for (const GenericEnvironment* const env : envs) {
391 env->Serialize(file);
392 }
393 file.write(key.data(), key.size_bytes());
394
395} catch (const std::ios_base::failure& e) {
396 LOG_ERROR(Common_Filesystem, "{}", e.what());
397 if (!Common::FS::RemoveFile(filename)) {
398 LOG_ERROR(Common_Filesystem, "Failed to delete pipeline cache file {}",
399 Common::FS::PathToUTF8String(filename));
400 }
401}
402
403void LoadPipelines(
404 std::stop_token stop_loading, const std::filesystem::path& filename, u32 expected_cache_version,
405 Common::UniqueFunction<void, std::ifstream&, FileEnvironment> load_compute,
406 Common::UniqueFunction<void, std::ifstream&, std::vector<FileEnvironment>> load_graphics) try {
407 std::ifstream file(filename, std::ios::binary | std::ios::ate);
408 if (!file.is_open()) {
409 return;
410 }
411 file.exceptions(std::ifstream::failbit);
412 const auto end{file.tellg()};
413 file.seekg(0, std::ios::beg);
414
415 std::array<char, 8> magic_number;
416 u32 cache_version;
417 file.read(magic_number.data(), magic_number.size())
418 .read(reinterpret_cast<char*>(&cache_version), sizeof(cache_version));
419 if (magic_number != MAGIC_NUMBER || cache_version != expected_cache_version) {
420 file.close();
421 if (Common::FS::RemoveFile(filename)) {
422 if (magic_number != MAGIC_NUMBER) {
423 LOG_ERROR(Common_Filesystem, "Invalid pipeline cache file");
424 }
425 if (cache_version != expected_cache_version) {
426 LOG_INFO(Common_Filesystem, "Deleting old pipeline cache");
427 }
428 } else {
429 LOG_ERROR(Common_Filesystem,
430 "Invalid pipeline cache file and failed to delete it in \"{}\"",
431 Common::FS::PathToUTF8String(filename));
432 }
433 return;
434 }
435 while (file.tellg() != end) {
436 if (stop_loading.stop_requested()) {
437 return;
438 }
439 u32 num_envs{};
440 file.read(reinterpret_cast<char*>(&num_envs), sizeof(num_envs));
441 std::vector<FileEnvironment> envs(num_envs);
442 for (FileEnvironment& env : envs) {
443 env.Deserialize(file);
444 }
445 if (envs.front().ShaderStage() == Shader::Stage::Compute) {
446 load_compute(file, std::move(envs.front()));
447 } else {
448 load_graphics(file, std::move(envs));
449 }
450 }
451
452} catch (const std::ios_base::failure& e) {
453 LOG_ERROR(Common_Filesystem, "{}", e.what());
454 if (!Common::FS::RemoveFile(filename)) {
455 LOG_ERROR(Common_Filesystem, "Failed to delete pipeline cache file {}",
456 Common::FS::PathToUTF8String(filename));
457 }
458}
459
460} // namespace VideoCommon