mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-05 13:20:11 +00:00
Every run recompiled every shader: the recompiler's variant cache and the device's VkPipelineCache only lived in memory, and the first frames of a title spent hundreds of milliseconds per large program in the front end, the emitter and the optimizer. Compiled variants (the SPIR-V plus the compiled shader info and binding allocation that per-snapshot binding population needs) are now stored on disk, one file per key, written atomically by a background thread, and a later run loads them instead of compiling. The key holds every input the in-memory variant cache matches on: the in-memory recompile key (stage, code hash, stage registers, user data count, target), every code word, the host subgroup size, the binding layout, the resource specialization, and the values of the APS5_* switches the recompiler sources read (found at build time; switches that only report are left out). Entries live under a directory per source version, a build-time hash of the recompiler and AGC driver sources and the build configuration, and carry their key and checksums: an entry whose format, version, key, size or checksum does not match, or whose payload does not decode exactly, is ignored and recompiled. Directories no build used for two weeks and stale temporary files are removed. Requests under the debug probe bypass the disk cache. The device's VkPipelineCache is loaded at creation when the file's checksum and Vulkan header match the device (vendor, device and pipelineCacheUUID; the driver version is in the file name), saved every 10 s while it grew, and saved at teardown. Location: a shader_cache folder beside the running executable (not the working directory), so the relinked game stays a self-contained folder; ANYPS5_SHADER_CACHE_DIR overrides it. ANYPS5_NO_SHADER_CACHE=1 turns both caches off; the AGC tests run with it set so they never write to the user's cache. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01ButHENCbXXt8h4EZuWUabJ
95 lines
3.1 KiB
C++
95 lines
3.1 KiB
C++
#ifndef CORE_SHADER_RECOMPILER_SHADERCACHEDIRECTORY_HPP
|
|
#define CORE_SHADER_RECOMPILER_SHADERCACHEDIRECTORY_HPP
|
|
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <cstddef>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <functional>
|
|
#include <span>
|
|
#include <string>
|
|
#include <system_error>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
namespace ShaderRecompiler {
|
|
|
|
std::filesystem::path ShaderCacheDirectory();
|
|
|
|
inline bool WriteFileAtomically(const std::filesystem::path& path, std::span<const std::byte> bytes) {
|
|
std::error_code error;
|
|
std::filesystem::create_directories(path.parent_path(), error);
|
|
static std::atomic<std::uint64_t> serial{0};
|
|
const auto unique = std::hash<std::thread::id>{}(std::this_thread::get_id()) ^ static_cast<std::size_t>(std::chrono::steady_clock::now().time_since_epoch().count());
|
|
auto temporary = path;
|
|
temporary += ".tmp." + std::to_string(unique) + "." + std::to_string(serial.fetch_add(1, std::memory_order_relaxed));
|
|
bool written = false;
|
|
{
|
|
std::ofstream file(temporary, std::ios::binary | std::ios::trunc);
|
|
if (!file) return false;
|
|
file.write(reinterpret_cast<const char*>(bytes.data()), static_cast<std::streamsize>(bytes.size()));
|
|
file.close();
|
|
written = !file.fail();
|
|
}
|
|
if (!written) {
|
|
std::filesystem::remove(temporary, error);
|
|
return false;
|
|
}
|
|
std::filesystem::rename(temporary, path, error);
|
|
if (error) {
|
|
std::filesystem::remove(temporary, error);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
inline bool ReadWholeFile(const std::filesystem::path& path, std::vector<std::byte>& bytes) {
|
|
bytes.clear();
|
|
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
|
if (!file) return false;
|
|
const auto size = static_cast<std::streamoff>(file.tellg());
|
|
if (size < 0) return false;
|
|
file.seekg(0, std::ios::beg);
|
|
bytes.resize(static_cast<std::size_t>(size));
|
|
if (!file.read(reinterpret_cast<char*>(bytes.data()), static_cast<std::streamsize>(bytes.size()))) {
|
|
bytes.clear();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
inline std::uint64_t HashBytes(std::span<const std::byte> bytes, std::uint64_t seed = 0) {
|
|
constexpr std::uint64_t multiplier = 0x9e3779b97f4a7c15ull;
|
|
std::uint64_t hash = seed ^ (static_cast<std::uint64_t>(bytes.size()) * multiplier);
|
|
const auto mix = [&](std::uint64_t chunk) {
|
|
chunk *= 0xff51afd7ed558ccdull;
|
|
chunk ^= chunk >> 32u;
|
|
hash = (hash ^ chunk) * multiplier;
|
|
hash = (hash << 27u) | (hash >> 37u);
|
|
};
|
|
std::size_t index = 0;
|
|
for (; index + 8 <= bytes.size(); index += 8) {
|
|
std::uint64_t chunk;
|
|
std::memcpy(&chunk, bytes.data() + index, sizeof(chunk));
|
|
mix(chunk);
|
|
}
|
|
if (index < bytes.size()) {
|
|
std::uint64_t chunk = 0;
|
|
std::memcpy(&chunk, bytes.data() + index, bytes.size() - index);
|
|
mix(chunk ^ 0x8000000000000000ull);
|
|
}
|
|
hash ^= hash >> 33u;
|
|
hash *= 0xff51afd7ed558ccdull;
|
|
hash ^= hash >> 33u;
|
|
hash *= 0xc4ceb9fe1a85ec53ull;
|
|
hash ^= hash >> 33u;
|
|
return hash;
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|