diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index c6141390a..c338f403c 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -27,6 +27,7 @@ add_library(common gl/stream_buffer.h gl/texture.cpp gl/texture.h + hash_combine.h heap_array.h iso_reader.cpp iso_reader.h @@ -58,6 +59,8 @@ target_link_libraries(common PRIVATE glad libcue Threads::Threads cubeb) if(WIN32) target_sources(common PRIVATE + d3d11/shader_cache.cpp + d3d11/shader_cache.h d3d11/shader_compiler.cpp d3d11/shader_compiler.h d3d11/staging_texture.cpp diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj index f29ab73f5..5836429b3 100644 --- a/src/common/common.vcxproj +++ b/src/common/common.vcxproj @@ -43,6 +43,7 @@ + @@ -52,6 +53,7 @@ + @@ -76,6 +78,7 @@ + diff --git a/src/common/common.vcxproj.filters b/src/common/common.vcxproj.filters index 5e9a2e99b..bb91af0fc 100644 --- a/src/common/common.vcxproj.filters +++ b/src/common/common.vcxproj.filters @@ -47,6 +47,10 @@ + + d3d11 + + @@ -90,6 +94,9 @@ + + d3d11 + diff --git a/src/common/d3d11/shader_cache.cpp b/src/common/d3d11/shader_cache.cpp new file mode 100644 index 000000000..2464b64b2 --- /dev/null +++ b/src/common/d3d11/shader_cache.cpp @@ -0,0 +1,336 @@ +#include "shader_cache.h" +#include "../file_system.h" +#include "../log.h" +#include "../md5_digest.h" +#include +Log_SetChannel(D3D11::ShaderCache); + +namespace D3D11 { + +#pragma pack(push, 1) +struct CacheIndexEntry +{ + u64 source_hash_low; + u64 source_hash_high; + u32 source_length; + u32 shader_type; + u32 file_offset; + u32 blob_size; +}; +#pragma pack(pop) + +ShaderCache::ShaderCache() = default; + +ShaderCache::~ShaderCache() +{ + if (m_index_file) + std::fclose(m_index_file); + if (m_blob_file) + std::fclose(m_blob_file); +} + +bool ShaderCache::CacheIndexKey::operator==(const CacheIndexKey& key) const +{ + return (source_hash_low == key.source_hash_low && source_hash_high == key.source_hash_high && + source_length == key.source_length && shader_type == key.shader_type); +} + +bool ShaderCache::CacheIndexKey::operator!=(const CacheIndexKey& key) const +{ + return (source_hash_low != key.source_hash_low || source_hash_high != key.source_hash_high || + source_length != key.source_length || shader_type != key.shader_type); +} + +void ShaderCache::Open(std::string_view base_path, D3D_FEATURE_LEVEL feature_level, bool debug) +{ + m_feature_level = feature_level; + m_debug = debug; + + const std::string base_filename = GetCacheBaseFileName(base_path, feature_level, debug); + const std::string index_filename = base_filename + ".idx"; + const std::string blob_filename = base_filename + ".bin"; + + if (!ReadExisting(index_filename, blob_filename)) + CreateNew(index_filename, blob_filename); +} + +bool ShaderCache::CreateNew(const std::string& index_filename, const std::string& blob_filename) +{ + if (FileSystem::FileExists(index_filename.c_str())) + { + Log_WarningPrintf("Removing existing index file '%s'", index_filename.c_str()); + FileSystem::DeleteFile(index_filename.c_str()); + } + if (FileSystem::FileExists(blob_filename.c_str())) + { + Log_WarningPrintf("Removing existing blob file '%s'", blob_filename.c_str()); + FileSystem::DeleteFile(blob_filename.c_str()); + } + + m_index_file = FileSystem::OpenCFile(index_filename.c_str(), "wb"); + if (!m_index_file) + { + Log_ErrorPrintf("Failed to open index file '%s' for writing", index_filename.c_str()); + return false; + } + + const u32 index_version = FILE_VERSION; + if (std::fwrite(&index_version, sizeof(index_version), 1, m_index_file) != 1) + { + Log_ErrorPrintf("Failed to write version to index file '%s'", index_filename.c_str()); + std::fclose(m_index_file); + m_index_file = nullptr; + FileSystem::DeleteFile(index_filename.c_str()); + return false; + } + + m_blob_file = FileSystem::OpenCFile(blob_filename.c_str(), "w+b"); + if (!m_blob_file) + { + Log_ErrorPrintf("Failed to open blob file '%s' for writing", blob_filename.c_str()); + std::fclose(m_index_file); + m_index_file = nullptr; + FileSystem::DeleteFile(index_filename.c_str()); + return false; + } + + return true; +} + +bool ShaderCache::ReadExisting(const std::string& index_filename, const std::string& blob_filename) +{ + m_index_file = FileSystem::OpenCFile(index_filename.c_str(), "r+b"); + if (!m_index_file) + return false; + + u32 file_version; + if (std::fread(&file_version, sizeof(file_version), 1, m_index_file) != 1 || file_version != FILE_VERSION) + { + Log_ErrorPrintf("Bad file version in '%s'", index_filename.c_str()); + std::fclose(m_index_file); + m_index_file = nullptr; + return false; + } + + m_blob_file = FileSystem::OpenCFile(blob_filename.c_str(), "a+b"); + if (!m_blob_file) + { + Log_ErrorPrintf("Blob file '%s' is missing", blob_filename.c_str()); + std::fclose(m_index_file); + m_index_file = nullptr; + return false; + } + + std::fseek(m_blob_file, 0, SEEK_END); + const u32 blob_file_size = static_cast(std::ftell(m_blob_file)); + + for (;;) + { + CacheIndexEntry entry; + if (std::fread(&entry, sizeof(entry), 1, m_index_file) != 1 || + (entry.file_offset + entry.blob_size) > blob_file_size) + { + if (std::feof(m_index_file)) + break; + + Log_ErrorPrintf("Failed to read entry from '%s', corrupt file?", index_filename.c_str()); + m_index.clear(); + std::fclose(m_blob_file); + m_blob_file = nullptr; + std::fclose(m_index_file); + m_index_file = nullptr; + return false; + } + + const CacheIndexKey key{entry.source_hash_low, entry.source_hash_high, entry.source_length, + static_cast(entry.shader_type)}; + const CacheIndexData data{entry.file_offset, entry.blob_size}; + m_index.emplace(key, data); + } + + Log_InfoPrintf("Read %zu entries from '%s'", m_index.size(), index_filename.c_str()); + return true; +} + +std::string ShaderCache::GetCacheBaseFileName(const std::string_view& base_path, D3D_FEATURE_LEVEL feature_level, + bool debug) +{ + std::string base_filename(base_path); + base_filename += FS_OSPATH_SEPERATOR_CHARACTER; + base_filename += "d3d_shaders_"; + + switch (feature_level) + { + case D3D_FEATURE_LEVEL_10_0: + base_filename += "sm40"; + break; + case D3D_FEATURE_LEVEL_10_1: + base_filename += "sm41"; + break; + case D3D_FEATURE_LEVEL_11_0: + base_filename += "sm50"; + break; + case D3D_FEATURE_LEVEL_11_1: + base_filename += "sm51"; + break; + case D3D_FEATURE_LEVEL_12_0: + base_filename += "sm60"; + break; + case D3D_FEATURE_LEVEL_12_1: + base_filename += "sm61"; + break; + default: + base_filename += "unk"; + break; + } + + if (debug) + base_filename += "_debug"; + + return base_filename; +} + +ShaderCache::CacheIndexKey ShaderCache::GetCacheKey(ShaderCompiler::Type type, const std::string_view& shader_code) +{ + union + { + struct + { + u64 hash_low; + u64 hash_high; + }; + u8 hash[16]; + }; + + MD5Digest digest; + digest.Update(shader_code.data(), static_cast(shader_code.length())); + digest.Final(hash); + + return CacheIndexKey{hash_low, hash_high, static_cast(shader_code.length()), type}; +} + +ShaderCache::ComPtr ShaderCache::GetShaderBlob(ShaderCompiler::Type type, std::string_view shader_code) +{ + const auto key = GetCacheKey(type, shader_code); + auto iter = m_index.find(key); + if (iter == m_index.end()) + return CompileAndAddShaderBlob(key, shader_code); + + ComPtr blob; + HRESULT hr = D3DCreateBlob(iter->second.blob_size, blob.GetAddressOf()); + if (FAILED(hr) || std::fseek(m_blob_file, iter->second.file_offset, SEEK_SET) != 0 || + std::fread(blob->GetBufferPointer(), 1, iter->second.blob_size, m_blob_file) != iter->second.blob_size) + { + Log_ErrorPrintf("Read blob from file failed"); + return {}; + } + + return blob; +} + +ShaderCache::ComPtr ShaderCache::GetVertexShader(ID3D11Device* device, std::string_view shader_code) +{ + ComPtr blob = GetShaderBlob(ShaderCompiler::Type::Vertex, std::move(shader_code)); + if (!blob) + return {}; + + ComPtr vs; + HRESULT hr = device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, vs.GetAddressOf()); + if (FAILED(hr)) + { + Log_ErrorPrintf("Failed to create vertex shader from blob: 0x%08X", hr); + return {}; + } + + return vs; +} + +ShaderCache::ComPtr ShaderCache::GetGeometryShader(ID3D11Device* device, + std::string_view shader_code) +{ + ComPtr blob = GetShaderBlob(ShaderCompiler::Type::Geometry, std::move(shader_code)); + if (!blob) + return {}; + + ComPtr gs; + HRESULT hr = + device->CreateGeometryShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, gs.GetAddressOf()); + if (FAILED(hr)) + { + Log_ErrorPrintf("Failed to create geometry shader from blob: 0x%08X", hr); + return {}; + } + + return gs; +} + +ShaderCache::ComPtr ShaderCache::GetPixelShader(ID3D11Device* device, std::string_view shader_code) +{ + ComPtr blob = GetShaderBlob(ShaderCompiler::Type::Pixel, std::move(shader_code)); + if (!blob) + return {}; + + ComPtr ps; + HRESULT hr = device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, ps.GetAddressOf()); + if (FAILED(hr)) + { + Log_ErrorPrintf("Failed to create pixel shader from blob: 0x%08X", hr); + return {}; + } + + return ps; +} + +ShaderCache::ComPtr ShaderCache::GetComputeShader(ID3D11Device* device, + std::string_view shader_code) +{ + ComPtr blob = GetShaderBlob(ShaderCompiler::Type::Compute, std::move(shader_code)); + if (!blob) + return {}; + + ComPtr cs; + HRESULT hr = device->CreateComputeShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, cs.GetAddressOf()); + if (FAILED(hr)) + { + Log_ErrorPrintf("Failed to create compute shader from blob: 0x%08X", hr); + return {}; + } + + return cs; +} + +ShaderCache::ComPtr ShaderCache::CompileAndAddShaderBlob(const CacheIndexKey& key, + std::string_view shader_code) +{ + ComPtr blob = ShaderCompiler::CompileShader(key.shader_type, m_feature_level, shader_code, m_debug); + if (!blob) + return {}; + + if (!m_blob_file || std::fseek(m_blob_file, 0, SEEK_END) != 0) + return blob; + + CacheIndexData data; + data.file_offset = static_cast(std::ftell(m_blob_file)); + data.blob_size = static_cast(blob->GetBufferSize()); + + CacheIndexEntry entry = {}; + entry.source_hash_low = key.source_hash_low; + entry.source_hash_high = key.source_hash_high; + entry.source_length = key.source_length; + entry.shader_type = static_cast(key.shader_type); + entry.blob_size = data.blob_size; + entry.file_offset = data.file_offset; + + if (std::fwrite(blob->GetBufferPointer(), 1, entry.blob_size, m_blob_file) != entry.blob_size || + std::fflush(m_blob_file) != 0 || std::fwrite(&entry, sizeof(entry), 1, m_index_file) != 1 || + std::fflush(m_index_file) != 0) + { + Log_ErrorPrintf("Failed to write shader blob to file"); + return blob; + } + + m_index.emplace(key, data); + return blob; +} + +} // namespace D3D11 \ No newline at end of file diff --git a/src/common/d3d11/shader_cache.h b/src/common/d3d11/shader_cache.h new file mode 100644 index 000000000..d658fc91b --- /dev/null +++ b/src/common/d3d11/shader_cache.h @@ -0,0 +1,83 @@ +#pragma once +#include "../hash_combine.h" +#include "../types.h" +#include "../windows_headers.h" +#include "shader_compiler.h" +#include +#include +#include +#include +#include +#include + +namespace D3D11 { + +class ShaderCache +{ +public: + template + using ComPtr = Microsoft::WRL::ComPtr; + + ShaderCache(); + ~ShaderCache(); + + void Open(std::string_view base_path, D3D_FEATURE_LEVEL feature_level, bool debug); + + ComPtr GetShaderBlob(ShaderCompiler::Type type, std::string_view shader_code); + + ComPtr GetVertexShader(ID3D11Device* device, std::string_view shader_code); + ComPtr GetGeometryShader(ID3D11Device* device, std::string_view shader_code); + ComPtr GetPixelShader(ID3D11Device* device, std::string_view shader_code); + ComPtr GetComputeShader(ID3D11Device* device, std::string_view shader_code); + +private: + static constexpr u32 FILE_VERSION = 1; + + struct CacheIndexKey + { + u64 source_hash_low; + u64 source_hash_high; + u32 source_length; + ShaderCompiler::Type shader_type; + + bool operator==(const CacheIndexKey& key) const; + bool operator!=(const CacheIndexKey& key) const; + }; + + struct CacheIndexEntryHasher + { + std::size_t operator()(const CacheIndexKey& e) const noexcept + { + std::size_t h = 0; + hash_combine(h, e.source_hash_low, e.source_hash_high, e.source_length, e.shader_type); + return h; + } + }; + + struct CacheIndexData + { + u32 file_offset; + u32 blob_size; + }; + + using CacheIndex = std::unordered_map; + + static std::string GetCacheBaseFileName(const std::string_view& base_path, D3D_FEATURE_LEVEL feature_level, bool debug); + static CacheIndexKey GetCacheKey(ShaderCompiler::Type type, const std::string_view& shader_code); + + bool CreateNew(const std::string& index_filename, const std::string& blob_filename); + bool ReadExisting(const std::string& index_filename, const std::string& blob_filename); + void Close(); + + ComPtr CompileAndAddShaderBlob(const CacheIndexKey& key, std::string_view shader_code); + + std::FILE* m_index_file = nullptr; + std::FILE* m_blob_file = nullptr; + + CacheIndex m_index; + + D3D_FEATURE_LEVEL m_feature_level = D3D_FEATURE_LEVEL_11_0; + bool m_debug = false; +}; + +} // namespace D3D11 diff --git a/src/common/hash_combine.h b/src/common/hash_combine.h new file mode 100644 index 000000000..52188ba88 --- /dev/null +++ b/src/common/hash_combine.h @@ -0,0 +1,10 @@ +// https://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x +#pragma once +#include + +template +void hash_combine(std::size_t& seed, const T& v, const Rest&... rest) +{ + seed ^= std::hash{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + (hash_combine(seed, rest), ...); +}