/ src / video_core / renderer_opengl / gl_shader_disk_cache.cpp
gl_shader_disk_cache.cpp
  1  // Copyright 2019 yuzu Emulator Project
  2  // Licensed under GPLv2 or any later version
  3  // Refer to the license.txt file included.
  4  
  5  #include <cstring>
  6  #include <fmt/format.h>
  7  
  8  #include "common/common_paths.h"
  9  #include "common/common_types.h"
 10  #include "common/file_util.h"
 11  #include "common/logging/log.h"
 12  #include "common/scm_rev.h"
 13  #include "common/settings.h"
 14  #include "common/zstd_compression.h"
 15  #include "core/core.h"
 16  #include "core/loader/loader.h"
 17  #include "video_core/renderer_opengl/gl_shader_disk_cache.h"
 18  
 19  namespace OpenGL {
 20  
 21  constexpr std::size_t HASH_LENGTH = 64;
 22  using ShaderCacheVersionHash = std::array<u8, HASH_LENGTH>;
 23  
 24  enum class TransferableEntryKind : u32 {
 25      Raw,
 26  };
 27  
 28  enum class PrecompiledEntryKind : u32 {
 29      Decompiled,
 30      Dump,
 31  };
 32  
 33  constexpr u32 NativeVersion = 1;
 34  
 35  // The hash is based on relevant files. The list of files can be found at src/common/CMakeLists.txt
 36  // and CMakeModules/GenerateSCMRev.cmake
 37  ShaderCacheVersionHash GetShaderCacheVersionHash() {
 38      ShaderCacheVersionHash hash{};
 39      const std::size_t length = std::min(std::strlen(Common::g_shader_cache_version), hash.size());
 40      std::memcpy(hash.data(), Common::g_shader_cache_version, length);
 41      return hash;
 42  }
 43  
 44  ShaderDiskCacheRaw::ShaderDiskCacheRaw(u64 unique_identifier, ProgramType program_type,
 45                                         RawShaderConfig config, ProgramCode program_code)
 46      : unique_identifier{unique_identifier}, program_type{program_type}, config{config},
 47        program_code{std::move(program_code)} {}
 48  
 49  bool ShaderDiskCacheRaw::Load(FileUtil::IOFile& file) {
 50      if (file.ReadBytes(&unique_identifier, sizeof(u64)) != sizeof(u64) ||
 51          file.ReadBytes(&program_type, sizeof(u32)) != sizeof(u32)) {
 52          return false;
 53      }
 54  
 55      u64 reg_array_len{};
 56      if (file.ReadBytes(&reg_array_len, sizeof(u64)) != sizeof(u64)) {
 57          return false;
 58      }
 59  
 60      if (file.ReadArray(config.reg_array.data(), reg_array_len) != reg_array_len) {
 61          return false;
 62      }
 63  
 64      // Read in type specific configuration
 65      if (program_type == ProgramType::VS) {
 66          u64 code_len{};
 67          if (file.ReadBytes(&code_len, sizeof(u64)) != sizeof(u64)) {
 68              return false;
 69          }
 70          program_code.resize(code_len);
 71          if (file.ReadArray(program_code.data(), code_len) != code_len) {
 72              return false;
 73          }
 74      }
 75  
 76      return true;
 77  }
 78  
 79  bool ShaderDiskCacheRaw::Save(FileUtil::IOFile& file) const {
 80      if (file.WriteObject(unique_identifier) != 1 ||
 81          file.WriteObject(static_cast<u32>(program_type)) != 1) {
 82          return false;
 83      }
 84  
 85      // Just for future proofing, save the sizes of the array to the file
 86      const std::size_t reg_array_len = Pica::RegsInternal::NUM_REGS;
 87      if (file.WriteObject(static_cast<u64>(reg_array_len)) != 1) {
 88          return false;
 89      }
 90      if (file.WriteArray(config.reg_array.data(), reg_array_len) != reg_array_len) {
 91          return false;
 92      }
 93  
 94      if (program_type == ProgramType::VS) {
 95          const std::size_t code_len = program_code.size();
 96          if (file.WriteObject(static_cast<u64>(code_len)) != 1) {
 97              return false;
 98          }
 99          if (file.WriteArray(program_code.data(), code_len) != code_len) {
100              return false;
101          }
102      }
103      return true;
104  }
105  
106  ShaderDiskCache::ShaderDiskCache(bool separable)
107      : separable{separable}, transferable_file(AppendTransferableFile()),
108        // seperable shaders use the virtual precompile file, that already has a header.
109        precompiled_file(AppendPrecompiledFile(!separable)) {}
110  
111  std::optional<std::vector<ShaderDiskCacheRaw>> ShaderDiskCache::LoadTransferable() {
112      const bool has_title_id = GetProgramID() != 0;
113      if (!Settings::values.use_hw_shader || !Settings::values.use_disk_shader_cache ||
114          !has_title_id) {
115          return std::nullopt;
116      }
117      tried_to_load = true;
118  
119      if (transferable_file.GetSize() == 0) {
120          LOG_INFO(Render_OpenGL, "No transferable shader cache found for game with title id={}",
121                   GetTitleID());
122          return std::nullopt;
123      }
124  
125      u32 version{};
126      if (transferable_file.ReadBytes(&version, sizeof(version)) != sizeof(version)) {
127          LOG_ERROR(Render_OpenGL,
128                    "Failed to get transferable cache version for title id={} - removing",
129                    GetTitleID());
130          InvalidateAll();
131          return std::nullopt;
132      }
133  
134      if (version < NativeVersion) {
135          LOG_INFO(Render_OpenGL, "Transferable shader cache is old - removing");
136          InvalidateAll();
137          return std::nullopt;
138      }
139      if (version > NativeVersion) {
140          LOG_WARNING(Render_OpenGL, "Transferable shader cache was generated with a newer version "
141                                     "of the emulator - skipping");
142          return std::nullopt;
143      }
144  
145      // Version is valid, load the shaders
146      std::vector<ShaderDiskCacheRaw> raws;
147      while (transferable_file.Tell() < transferable_file.GetSize()) {
148          TransferableEntryKind kind{};
149          if (transferable_file.ReadBytes(&kind, sizeof(u32)) != sizeof(u32)) {
150              LOG_ERROR(Render_OpenGL, "Failed to read transferable file - removing");
151              InvalidateAll();
152              return std::nullopt;
153          }
154  
155          switch (kind) {
156          case TransferableEntryKind::Raw: {
157              ShaderDiskCacheRaw entry;
158              if (!entry.Load(transferable_file)) {
159                  LOG_ERROR(Render_OpenGL, "Failed to load transferable raw entry - removing");
160                  InvalidateAll();
161                  return std::nullopt;
162              }
163              transferable.emplace(entry.GetUniqueIdentifier(), ShaderDiskCacheRaw{});
164              raws.push_back(std::move(entry));
165              break;
166          }
167          default:
168              LOG_ERROR(Render_OpenGL, "Unknown transferable shader cache entry kind={} - removing",
169                        kind);
170              InvalidateAll();
171              return std::nullopt;
172          }
173      }
174  
175      LOG_INFO(Render_OpenGL, "Found a transferable disk cache with {} entries", raws.size());
176      return {std::move(raws)};
177  }
178  
179  std::pair<std::unordered_map<u64, ShaderDiskCacheDecompiled>, ShaderDumpsMap>
180  ShaderDiskCache::LoadPrecompiled(bool compressed) {
181      if (!IsUsable())
182          return {};
183  
184      if (precompiled_file.GetSize() == 0) {
185          LOG_INFO(Render_OpenGL, "No precompiled shader cache found for game with title id={}",
186                   GetTitleID());
187          return {};
188      }
189  
190      const auto result = LoadPrecompiledFile(precompiled_file, compressed);
191      if (!result) {
192          LOG_INFO(Render_OpenGL,
193                   "Failed to load precompiled cache for game with title id={} - removing",
194                   GetTitleID());
195          InvalidatePrecompiled();
196          return {};
197      }
198      return *result;
199  }
200  
201  std::optional<std::pair<std::unordered_map<u64, ShaderDiskCacheDecompiled>, ShaderDumpsMap>>
202  ShaderDiskCache::LoadPrecompiledFile(FileUtil::IOFile& file, bool compressed) {
203      // Read compressed file from disk and decompress to virtual precompiled cache file
204      std::vector<u8> precompiled_file(file.GetSize());
205      file.ReadBytes(precompiled_file.data(), precompiled_file.size());
206      if (compressed) {
207          const std::vector<u8> decompressed =
208              Common::Compression::DecompressDataZSTD(precompiled_file);
209          if (decompressed.empty()) {
210              LOG_ERROR(Render_OpenGL, "Could not decompress precompiled shader cache.");
211              return std::nullopt;
212          }
213          SaveArrayToPrecompiled(decompressed.data(), decompressed.size());
214      } else {
215          SaveArrayToPrecompiled(precompiled_file.data(), precompiled_file.size());
216      }
217  
218      decompressed_precompiled_cache_offset = 0;
219  
220      ShaderCacheVersionHash file_hash{};
221      if (!LoadArrayFromPrecompiled(file_hash.data(), file_hash.size())) {
222          return std::nullopt;
223      }
224      if (GetShaderCacheVersionHash() != file_hash) {
225          LOG_INFO(Render_OpenGL, "Precompiled cache is from another version of the emulator");
226          return std::nullopt;
227      }
228  
229      std::unordered_map<u64, ShaderDiskCacheDecompiled> decompiled;
230      ShaderDumpsMap dumps;
231      while (decompressed_precompiled_cache_offset < decompressed_precompiled_cache.size()) {
232          PrecompiledEntryKind kind{};
233          if (!LoadObjectFromPrecompiled(kind)) {
234              return std::nullopt;
235          }
236  
237          switch (kind) {
238          case PrecompiledEntryKind::Decompiled: {
239              u64 unique_identifier{};
240              if (!LoadObjectFromPrecompiled(unique_identifier)) {
241                  return std::nullopt;
242              }
243  
244              auto entry = LoadDecompiledEntry();
245              if (!entry) {
246                  return std::nullopt;
247              }
248              decompiled.insert({unique_identifier, std::move(*entry)});
249              break;
250          }
251          case PrecompiledEntryKind::Dump: {
252              u64 unique_identifier;
253              if (!LoadObjectFromPrecompiled(unique_identifier)) {
254                  return std::nullopt;
255              }
256  
257              ShaderDiskCacheDump dump;
258              if (!LoadObjectFromPrecompiled(dump.binary_format)) {
259                  return std::nullopt;
260              }
261  
262              u32 binary_length{};
263              if (!LoadObjectFromPrecompiled(binary_length)) {
264                  return std::nullopt;
265              }
266  
267              dump.binary.resize(binary_length);
268              if (!LoadArrayFromPrecompiled(dump.binary.data(), dump.binary.size())) {
269                  return std::nullopt;
270              }
271  
272              dumps.insert({unique_identifier, dump});
273              break;
274          }
275          default:
276              return std::nullopt;
277          }
278      }
279  
280      LOG_INFO(Render_OpenGL,
281               "Found a precompiled disk cache with {} decompiled entries and {} binary entries",
282               decompiled.size(), dumps.size());
283      return {{decompiled, dumps}};
284  }
285  
286  std::optional<ShaderDiskCacheDecompiled> ShaderDiskCache::LoadDecompiledEntry() {
287  
288      bool sanitize_mul;
289      if (!LoadObjectFromPrecompiled(sanitize_mul)) {
290          return std::nullopt;
291      }
292  
293      u32 code_size{};
294      if (!LoadObjectFromPrecompiled(code_size)) {
295          return std::nullopt;
296      }
297  
298      std::string code(code_size, '\0');
299      if (!LoadArrayFromPrecompiled(code.data(), code.size())) {
300          return std::nullopt;
301      }
302  
303      ShaderDiskCacheDecompiled entry;
304      entry.code = std::move(code);
305      entry.sanitize_mul = sanitize_mul;
306  
307      return entry;
308  }
309  
310  void ShaderDiskCache::SaveDecompiledToFile(FileUtil::IOFile& file, u64 unique_identifier,
311                                             const std::string& code, bool sanitize_mul) {
312      if (!IsUsable())
313          return;
314  
315      if (file.WriteObject(static_cast<u32>(PrecompiledEntryKind::Decompiled)) != 1 ||
316          file.WriteObject(unique_identifier) != 1 || file.WriteObject(sanitize_mul) != 1 ||
317          file.WriteObject(static_cast<u32>(code.size())) != 1 ||
318          file.WriteArray(code.data(), code.size()) != code.size()) {
319          LOG_ERROR(Render_OpenGL, "Failed to save decompiled cache entry - removing");
320          file.Close();
321          InvalidatePrecompiled();
322      }
323  }
324  
325  bool ShaderDiskCache::SaveDecompiledToCache(u64 unique_identifier, const std::string& code,
326                                              bool sanitize_mul) {
327      if (!SaveObjectToPrecompiled(static_cast<u32>(PrecompiledEntryKind::Decompiled)) ||
328          !SaveObjectToPrecompiled(unique_identifier) || !SaveObjectToPrecompiled(sanitize_mul) ||
329          !SaveObjectToPrecompiled(static_cast<u32>(code.size())) ||
330          !SaveArrayToPrecompiled(code.data(), code.size())) {
331          return false;
332      }
333  
334      return true;
335  }
336  
337  void ShaderDiskCache::InvalidateAll() {
338      transferable_file.Close();
339      if (!FileUtil::Delete(GetTransferablePath())) {
340          LOG_ERROR(Render_OpenGL, "Failed to invalidate transferable file={}",
341                    GetTransferablePath());
342      }
343      transferable_file = AppendTransferableFile();
344  
345      InvalidatePrecompiled();
346  }
347  
348  void ShaderDiskCache::InvalidatePrecompiled() {
349      // Clear virtual precompiled cache file
350      decompressed_precompiled_cache.resize(0);
351  
352      precompiled_file.Close();
353      if (!FileUtil::Delete(GetPrecompiledPath())) {
354          LOG_ERROR(Render_OpenGL, "Failed to invalidate precompiled file={}", GetPrecompiledPath());
355      }
356      precompiled_file = AppendPrecompiledFile(!separable);
357  }
358  
359  void ShaderDiskCache::SaveRaw(const ShaderDiskCacheRaw& entry) {
360      if (!IsUsable())
361          return;
362  
363      const u64 id = entry.GetUniqueIdentifier();
364      if (transferable.find(id) != transferable.end()) {
365          // The shader already exists
366          return;
367      }
368  
369      if (transferable_file.WriteObject(TransferableEntryKind::Raw) != 1 ||
370          !entry.Save(transferable_file)) {
371          LOG_ERROR(Render_OpenGL, "Failed to save raw transferable cache entry - removing");
372          InvalidateAll();
373          return;
374      }
375      transferable.insert({id, entry});
376      transferable_file.Flush();
377  }
378  
379  void ShaderDiskCache::SaveDecompiled(u64 unique_identifier, const std::string& code,
380                                       bool sanitize_mul) {
381      if (!IsUsable())
382          return;
383  
384      if (decompressed_precompiled_cache.empty()) {
385          SavePrecompiledHeaderToVirtualPrecompiledCache();
386      }
387  
388      if (!SaveDecompiledToCache(unique_identifier, code, sanitize_mul)) {
389          LOG_ERROR(Render_OpenGL,
390                    "Failed to save decompiled entry to the precompiled file - removing");
391          InvalidatePrecompiled();
392      }
393  }
394  
395  void ShaderDiskCache::SaveDump(u64 unique_identifier, GLuint program) {
396      if (!IsUsable())
397          return;
398  
399      GLint binary_length{};
400      glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &binary_length);
401  
402      GLenum binary_format{};
403      std::vector<u8> binary(binary_length);
404      glGetProgramBinary(program, binary_length, nullptr, &binary_format, binary.data());
405  
406      if (!SaveObjectToPrecompiled(static_cast<u32>(PrecompiledEntryKind::Dump)) ||
407          !SaveObjectToPrecompiled(unique_identifier) ||
408          !SaveObjectToPrecompiled(static_cast<u32>(binary_format)) ||
409          !SaveObjectToPrecompiled(static_cast<u32>(binary_length)) ||
410          !SaveArrayToPrecompiled(binary.data(), binary.size())) {
411          LOG_ERROR(Render_OpenGL, "Failed to save binary program file in shader={:016x} - removing",
412                    unique_identifier);
413          InvalidatePrecompiled();
414          return;
415      }
416  }
417  
418  void ShaderDiskCache::SaveDumpToFile(u64 unique_identifier, GLuint program, bool sanitize_mul) {
419      if (!IsUsable())
420          return;
421  
422      GLint binary_length{};
423      glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &binary_length);
424  
425      GLenum binary_format{};
426      std::vector<u8> binary(binary_length);
427      glGetProgramBinary(program, binary_length, nullptr, &binary_format, binary.data());
428  
429      if (precompiled_file.WriteObject(static_cast<u32>(PrecompiledEntryKind::Dump)) != 1 ||
430          precompiled_file.WriteObject(unique_identifier) != 1 ||
431          precompiled_file.WriteObject(static_cast<u32>(binary_format)) != 1 ||
432          precompiled_file.WriteObject(static_cast<u32>(binary_length)) != 1 ||
433          precompiled_file.WriteArray(binary.data(), binary.size()) != binary.size()) {
434          LOG_ERROR(Render_OpenGL, "Failed to save binary program file in shader={:016x} - removing",
435                    unique_identifier);
436          InvalidatePrecompiled();
437          return;
438      }
439  
440      // SaveDecompiled is used only to store the accurate multiplication setting, a better way is to
441      // probably change the header in SaveDump
442      SaveDecompiledToFile(precompiled_file, unique_identifier, {}, sanitize_mul);
443  
444      precompiled_file.Flush();
445  }
446  
447  bool ShaderDiskCache::IsUsable() const {
448      return tried_to_load && Settings::values.use_disk_shader_cache;
449  }
450  
451  FileUtil::IOFile ShaderDiskCache::AppendTransferableFile() {
452      if (!EnsureDirectories())
453          return {};
454  
455      const auto transferable_path{GetTransferablePath()};
456      const bool existed = FileUtil::Exists(transferable_path);
457  
458      FileUtil::IOFile file(transferable_path, "ab+");
459      if (!file.IsOpen()) {
460          LOG_ERROR(Render_OpenGL, "Failed to open transferable cache in path={}", transferable_path);
461          return {};
462      }
463      if (!existed || file.GetSize() == 0) {
464          // If the file didn't exist, write its version
465          if (file.WriteObject(NativeVersion) != 1) {
466              LOG_ERROR(Render_OpenGL, "Failed to write transferable cache version in path={}",
467                        transferable_path);
468              return {};
469          }
470      }
471      return file;
472  }
473  
474  FileUtil::IOFile ShaderDiskCache::AppendPrecompiledFile(bool write_header) {
475      if (!EnsureDirectories())
476          return {};
477  
478      const auto precompiled_path{GetPrecompiledPath()};
479      const bool existed = FileUtil::Exists(precompiled_path);
480  
481      FileUtil::IOFile file(precompiled_path, "ab+");
482      if (!file.IsOpen()) {
483          LOG_ERROR(Render_OpenGL, "Failed to open precompiled cache in path={}", precompiled_path);
484          return {};
485      }
486  
487      // If the file didn't exist, write its version
488      if (write_header && (!existed || file.GetSize() == 0)) {
489          const auto hash{GetShaderCacheVersionHash()};
490          if (file.WriteArray(hash.data(), hash.size()) != hash.size()) {
491              LOG_ERROR(Render_OpenGL, "Failed to write precompiled cache version in path={}",
492                        precompiled_path);
493              return {};
494          }
495      }
496      return file;
497  }
498  
499  void ShaderDiskCache::SavePrecompiledHeaderToVirtualPrecompiledCache() {
500      const auto hash{GetShaderCacheVersionHash()};
501      if (!SaveArrayToPrecompiled(hash.data(), hash.size())) {
502          LOG_ERROR(
503              Render_OpenGL,
504              "Failed to write precompiled cache version hash to virtual precompiled cache file");
505      }
506  }
507  
508  void ShaderDiskCache::SaveVirtualPrecompiledFile() {
509      decompressed_precompiled_cache_offset = 0;
510      const auto compressed =
511          Common::Compression::CompressDataZSTDDefault(decompressed_precompiled_cache);
512  
513      const auto precompiled_path{GetPrecompiledPath()};
514  
515      precompiled_file.Close();
516      if (!FileUtil::Delete(GetPrecompiledPath())) {
517          LOG_ERROR(Render_OpenGL, "Failed to invalidate precompiled file={}", GetPrecompiledPath());
518      }
519      precompiled_file = AppendPrecompiledFile(!separable);
520  
521      if (precompiled_file.WriteBytes(compressed.data(), compressed.size()) != compressed.size()) {
522          LOG_ERROR(Render_OpenGL, "Failed to write precompiled cache version in path={}",
523                    precompiled_path);
524          return;
525      }
526  
527      precompiled_file.Flush();
528  }
529  
530  bool ShaderDiskCache::EnsureDirectories() const {
531      const auto CreateDir = [](const std::string& dir) {
532          if (!FileUtil::CreateDir(dir)) {
533              LOG_ERROR(Render_OpenGL, "Failed to create directory={}", dir);
534              return false;
535          }
536          return true;
537      };
538  
539      return CreateDir(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir)) &&
540             CreateDir(GetBaseDir()) && CreateDir(GetTransferableDir()) &&
541             CreateDir(GetPrecompiledDir()) && CreateDir(GetPrecompiledShaderDir());
542  }
543  
544  std::string ShaderDiskCache::GetTransferablePath() {
545      return FileUtil::SanitizePath(GetTransferableDir() + DIR_SEP_CHR + GetTitleID() + ".bin");
546  }
547  
548  std::string ShaderDiskCache::GetPrecompiledPath() {
549      return FileUtil::SanitizePath(GetPrecompiledShaderDir() + DIR_SEP_CHR + GetTitleID() + ".bin");
550  }
551  
552  std::string ShaderDiskCache::GetTransferableDir() const {
553      return GetBaseDir() + DIR_SEP "transferable";
554  }
555  
556  std::string ShaderDiskCache::GetPrecompiledDir() const {
557      return GetBaseDir() + DIR_SEP "precompiled";
558  }
559  
560  std::string ShaderDiskCache::GetPrecompiledShaderDir() const {
561      if (separable) {
562          return GetPrecompiledDir() + DIR_SEP "separable";
563      }
564      return GetPrecompiledDir() + DIR_SEP "conventional";
565  }
566  
567  std::string ShaderDiskCache::GetBaseDir() const {
568      return FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir) + DIR_SEP "opengl";
569  }
570  
571  u64 ShaderDiskCache::GetProgramID() {
572      // Skip games without title id
573      if (program_id != 0) {
574          return program_id;
575      }
576      if (Core::System::GetInstance().GetAppLoader().ReadProgramId(program_id) !=
577          Loader::ResultStatus::Success) {
578          return 0;
579      }
580      return program_id;
581  }
582  
583  std::string ShaderDiskCache::GetTitleID() {
584      if (!title_id.empty()) {
585          return title_id;
586      }
587      title_id = fmt::format("{:016X}", GetProgramID());
588      return title_id;
589  }
590  
591  } // namespace OpenGL