#ifndef CORE_SHADER_RECOMPILER_SHADERCACHEDIRECTORY_HPP #define CORE_SHADER_RECOMPILER_SHADERCACHEDIRECTORY_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #ifndef NOMINMAX #define NOMINMAX #endif #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #endif namespace ShaderRecompiler { std::filesystem::path ShaderCacheDirectory(); inline bool WriteFileAtomically(const std::filesystem::path& path, std::span bytes) { std::error_code error; std::filesystem::create_directories(path.parent_path(), error); static std::atomic serial{0}; const auto unique = std::hash{}(std::this_thread::get_id()) ^ static_cast(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)); #ifdef _WIN32 struct RenameInfo { DWORD flags; HANDLE root; DWORD nameBytes; WCHAR name[1]; }; static_assert(sizeof(RenameInfo) == sizeof(FILE_RENAME_INFO) && offsetof(RenameInfo, name) == offsetof(FILE_RENAME_INFO, FileName)); const auto destination = std::filesystem::absolute(path, error); if (error || destination.native().size() > (MAXDWORD - sizeof(RenameInfo)) / sizeof(wchar_t)) return false; const auto nameBytes = destination.native().size() * sizeof(wchar_t); std::vector renameBuffer(sizeof(RenameInfo) + nameBytes); auto* rename = reinterpret_cast(renameBuffer.data()); constexpr DWORD replaceExisting = 0x00000001, posixSemantics = 0x00000002; rename->flags = replaceExisting | posixSemantics; rename->nameBytes = static_cast(nameBytes); std::memcpy(rename->name, destination.c_str(), nameBytes); #endif bool written = false; { std::ofstream file(temporary, std::ios::binary | std::ios::trunc); if (!file) return false; file.write(reinterpret_cast(bytes.data()), static_cast(bytes.size())); file.close(); written = !file.fail(); } if (!written) { std::filesystem::remove(temporary, error); return false; } #ifdef _WIN32 DWORD renameError = ERROR_SUCCESS; { const auto handle = CreateFileW(temporary.c_str(), DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); if (handle == INVALID_HANDLE_VALUE) { std::filesystem::remove(temporary, error); return false; } struct Close { HANDLE handle; ~Close() { CloseHandle(handle); } } close{handle}; constexpr auto renameInfoEx = static_cast(22); if (!SetFileInformationByHandle(handle, renameInfoEx, rename, static_cast(renameBuffer.size()))) renameError = GetLastError(); } if (renameError == ERROR_SUCCESS) return true; if (renameError != ERROR_INVALID_PARAMETER && renameError != ERROR_INVALID_FUNCTION && renameError != ERROR_NOT_SUPPORTED) { std::filesystem::remove(temporary, error); return false; } #endif 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& bytes) { bytes.clear(); try { std::ifstream file(path, std::ios::binary | std::ios::ate); if (!file) return false; const auto size = static_cast(file.tellg()); if (size < 0 || static_cast(size) > bytes.max_size() || static_cast(size) > static_cast(std::numeric_limits::max())) return false; if (!file.seekg(0, std::ios::beg)) return false; bytes.resize(static_cast(size)); if (!file.read(reinterpret_cast(bytes.data()), static_cast(bytes.size()))) { bytes.clear(); return false; } return true; } catch (const std::bad_alloc&) { bytes.clear(); return false; } catch (const std::length_error&) { bytes.clear(); return false; } } inline std::uint64_t HashBytes(std::span bytes, std::uint64_t seed = 0) { constexpr std::uint64_t multiplier = 0x9e3779b97f4a7c15ull; std::uint64_t hash = seed ^ (static_cast(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