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), ...);
+}