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(®_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