From 00a452ed895728bd58a62b789f95f6809af22a3e Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Fri, 6 Apr 2018 17:54:02 +0200 Subject: [PATCH] [d3d11] Implement DXBC shader module cache If an application compiles the same shader multiple times, we should reuse an already existing DxvkShaderModule instead of creating a new one. This helps keep the number of DxvkGraphicsPipeline objects low in games such as Rise of the Tomb Raider. --- src/d3d11/d3d11_device.cpp | 25 +++++---- src/d3d11/d3d11_device.h | 5 +- src/d3d11/d3d11_shader.cpp | 104 ++++++++++++++++++++++++++----------- src/d3d11/d3d11_shader.h | 103 ++++++++++++++++++++++++++++++------ src/util/sha1/sha1_util.h | 11 ++++ 5 files changed, 190 insertions(+), 58 deletions(-) diff --git a/src/d3d11/d3d11_device.cpp b/src/d3d11/d3d11_device.cpp index 2c4ed88d..abffdf31 100644 --- a/src/d3d11/d3d11_device.cpp +++ b/src/d3d11/d3d11_device.cpp @@ -1007,7 +1007,8 @@ namespace dxvk { D3D11ShaderModule module; if (FAILED(this->CreateShaderModule(&module, - pShaderBytecode, BytecodeLength, pClassLinkage))) + pShaderBytecode, BytecodeLength, pClassLinkage, + DxbcProgramType::VertexShader))) return E_INVALIDARG; if (ppVertexShader == nullptr) @@ -1028,7 +1029,8 @@ namespace dxvk { D3D11ShaderModule module; if (FAILED(this->CreateShaderModule(&module, - pShaderBytecode, BytecodeLength, pClassLinkage))) + pShaderBytecode, BytecodeLength, pClassLinkage, + DxbcProgramType::GeometryShader))) return E_INVALIDARG; if (ppGeometryShader == nullptr) @@ -1065,7 +1067,8 @@ namespace dxvk { D3D11ShaderModule module; if (FAILED(this->CreateShaderModule(&module, - pShaderBytecode, BytecodeLength, pClassLinkage))) + pShaderBytecode, BytecodeLength, pClassLinkage, + DxbcProgramType::PixelShader))) return E_INVALIDARG; if (ppPixelShader == nullptr) @@ -1086,7 +1089,8 @@ namespace dxvk { D3D11ShaderModule module; if (FAILED(this->CreateShaderModule(&module, - pShaderBytecode, BytecodeLength, pClassLinkage))) + pShaderBytecode, BytecodeLength, pClassLinkage, + DxbcProgramType::HullShader))) return E_INVALIDARG; if (ppHullShader == nullptr) @@ -1107,7 +1111,8 @@ namespace dxvk { D3D11ShaderModule module; if (FAILED(this->CreateShaderModule(&module, - pShaderBytecode, BytecodeLength, pClassLinkage))) + pShaderBytecode, BytecodeLength, pClassLinkage, + DxbcProgramType::DomainShader))) return E_INVALIDARG; if (ppDomainShader == nullptr) @@ -1128,7 +1133,8 @@ namespace dxvk { D3D11ShaderModule module; if (FAILED(this->CreateShaderModule(&module, - pShaderBytecode, BytecodeLength, pClassLinkage))) + pShaderBytecode, BytecodeLength, pClassLinkage, + DxbcProgramType::ComputeShader))) return E_INVALIDARG; if (ppComputeShader == nullptr) @@ -1754,13 +1760,14 @@ namespace dxvk { D3D11ShaderModule* pShaderModule, const void* pShaderBytecode, size_t BytecodeLength, - ID3D11ClassLinkage* pClassLinkage) { + ID3D11ClassLinkage* pClassLinkage, + DxbcProgramType ProgramType) { if (pClassLinkage != nullptr) Logger::warn("D3D11Device::CreateShaderModule: Class linkage not supported"); try { - *pShaderModule = D3D11ShaderModule( - &m_dxbcOptions, this, pShaderBytecode, BytecodeLength); + *pShaderModule = m_shaderModules.GetShaderModule( + &m_dxbcOptions, pShaderBytecode, BytecodeLength, ProgramType); return S_OK; } catch (const DxvkError& e) { Logger::err(e.message()); diff --git a/src/d3d11/d3d11_device.h b/src/d3d11/d3d11_device.h index f8525510..2558741a 100644 --- a/src/d3d11/d3d11_device.h +++ b/src/d3d11/d3d11_device.h @@ -11,6 +11,7 @@ #include "d3d11_interfaces.h" #include "d3d11_options.h" +#include "d3d11_shader.h" #include "d3d11_state.h" #include "d3d11_util.h" @@ -363,12 +364,14 @@ namespace dxvk { D3D11StateObjectSet m_dsStateObjects; D3D11StateObjectSet m_rsStateObjects; D3D11StateObjectSet m_samplerObjects; + D3D11ShaderModuleSet m_shaderModules; HRESULT CreateShaderModule( D3D11ShaderModule* pShaderModule, const void* pShaderBytecode, size_t BytecodeLength, - ID3D11ClassLinkage* pClassLinkage); + ID3D11ClassLinkage* pClassLinkage, + DxbcProgramType ProgramType); void InitBuffer( D3D11Buffer* pBuffer, diff --git a/src/d3d11/d3d11_shader.cpp b/src/d3d11/d3d11_shader.cpp index 2f2773ea..4b08d72f 100644 --- a/src/d3d11/d3d11_shader.cpp +++ b/src/d3d11/d3d11_shader.cpp @@ -3,29 +3,61 @@ namespace dxvk { + D3D11ShaderKey::D3D11ShaderKey( + DxbcProgramType ProgramType, + const void* pShaderBytecode, + size_t BytecodeLength) + : m_type(ProgramType), + m_hash(Sha1Hash::compute( + reinterpret_cast(pShaderBytecode), + BytecodeLength)) { } + + + std::string D3D11ShaderKey::GetName() const { + static const std::array s_prefix + = {{ "PS_", "VS_", "GS_", "HS_", "DS_", "CS_" }}; + + return str::format( + s_prefix.at(uint32_t(m_type)), + m_hash.toString()); + } + + + size_t D3D11ShaderKey::GetHash() const { + DxvkHashState result; + result.add(uint32_t(m_type)); + + const uint8_t* digest = m_hash.digest(); + for (uint32_t i = 0; i < 5; i++) { + result.add( + uint32_t(digest[4 + i + 0]) << 0 + | uint32_t(digest[4 + i + 1]) << 8 + | uint32_t(digest[4 + i + 2]) << 16 + | uint32_t(digest[4 + i + 3]) << 24); + } + + return result; + } + + D3D11ShaderModule:: D3D11ShaderModule() { } D3D11ShaderModule::~D3D11ShaderModule() { } D3D11ShaderModule::D3D11ShaderModule( - const DxbcOptions* pDxbcOptions, - D3D11Device* pDevice, - const void* pShaderBytecode, - size_t BytecodeLength) { + const D3D11ShaderKey* pShaderKey, + const DxbcOptions* pDxbcOptions, + const void* pShaderBytecode, + size_t BytecodeLength) + : m_name(pShaderKey->GetName()) { + Logger::debug(str::format("Compiling shader ", m_name)); + DxbcReader reader( reinterpret_cast(pShaderBytecode), BytecodeLength); DxbcModule module(reader); - // Construct the shader name that we'll use for - // debug messages and as the dump/read file name - m_name = ConstructFileName( - ComputeShaderHash(pShaderBytecode, BytecodeLength), - module.version().type()); - - Logger::debug(str::format("Compiling shader ", m_name)); - // If requested by the user, dump both the raw DXBC // shader and the compiled SPIR-V module to a file. const std::string dumpPath = env::getEnvVar(L"DXVK_SHADER_DUMP_PATH"); @@ -61,30 +93,40 @@ namespace dxvk { } - Sha1Hash D3D11ShaderModule::ComputeShaderHash( - const void* pShaderBytecode, - size_t BytecodeLength) const { - return Sha1Hash::compute( - reinterpret_cast(pShaderBytecode), - BytecodeLength); - } + D3D11ShaderModuleSet:: D3D11ShaderModuleSet() { } + D3D11ShaderModuleSet::~D3D11ShaderModuleSet() { } - std::string D3D11ShaderModule::ConstructFileName( - const Sha1Hash& hash, - const DxbcProgramType& type) const { - std::string typeStr; + D3D11ShaderModule D3D11ShaderModuleSet::GetShaderModule( + const DxbcOptions* pDxbcOptions, + const void* pShaderBytecode, + size_t BytecodeLength, + DxbcProgramType ProgramType) { + // Compute the shader's unique key so that we can perform a lookup + D3D11ShaderKey key(ProgramType, pShaderBytecode, BytecodeLength); - switch (type) { - case DxbcProgramType::PixelShader: typeStr = "PS_"; break; - case DxbcProgramType::VertexShader: typeStr = "VS_"; break; - case DxbcProgramType::GeometryShader: typeStr = "GS_"; break; - case DxbcProgramType::HullShader: typeStr = "HS_"; break; - case DxbcProgramType::DomainShader: typeStr = "DS_"; break; - case DxbcProgramType::ComputeShader: typeStr = "CS_"; break; + { std::unique_lock lock(m_mutex); + + auto entry = m_modules.find(key); + if (entry != m_modules.end()) + return entry->second; } - return str::format(typeStr, hash.toString()); + // This shader has not been compiled yet, so we have to create a + // new module. This takes a while, so we won't lock the structure. + D3D11ShaderModule module(&key, pDxbcOptions, pShaderBytecode, BytecodeLength); + + // Insert the new module into the lookup table. If another thread + // has compiled the same shader in the meantime, we should return + // that object instead and discard the newly created module. + { std::unique_lock lock(m_mutex); + + auto status = m_modules.insert({ key, module }); + if (!status.second) + return status.first->second; + } + + return module; } } diff --git a/src/d3d11/d3d11_shader.h b/src/d3d11/d3d11_shader.h index c3c1bd40..6483b16a 100644 --- a/src/d3d11/d3d11_shader.h +++ b/src/d3d11/d3d11_shader.h @@ -1,7 +1,10 @@ #pragma once -#include -#include +#include +#include + +#include "../dxbc/dxbc_module.h" +#include "../dxvk/dxvk_device.h" #include "../util/sha1/sha1_util.h" @@ -14,10 +17,51 @@ namespace dxvk { class D3D11Device; + /** + * \brief Shader key + * + * A unique identifier for a shader consisting + * of the program type and the SHA-1 hash of + * the shader's original bytecode. + */ + class D3D11ShaderKey { + + public: + + D3D11ShaderKey( + DxbcProgramType ProgramType, + const void* pShaderBytecode, + size_t BytecodeLength); + + std::string GetName() const; + + size_t GetHash() const; + + bool operator == (const D3D11ShaderKey& other) const { + return m_type == other.m_type + && m_hash == other.m_hash; + } + + private: + + DxbcProgramType m_type; + Sha1Hash m_hash; + + }; + + struct D3D11ShaderKeyHash { + size_t operator () (const D3D11ShaderKey& a) const { + return a.GetHash(); + } + }; + + /** * \brief Shader module * - * + * Stores the compiled SPIR-V shader and the SHA-1 + * hash of the original DXBC shader, which can be + * used to identify the shader. */ class D3D11ShaderModule { @@ -25,17 +69,17 @@ namespace dxvk { D3D11ShaderModule(); D3D11ShaderModule( - const DxbcOptions* pDxbcOptions, - D3D11Device* pDevice, - const void* pShaderBytecode, - size_t BytecodeLength); + const D3D11ShaderKey* pShaderKey, + const DxbcOptions* pDxbcOptions, + const void* pShaderBytecode, + size_t BytecodeLength); ~D3D11ShaderModule(); Rc GetShader() const { return m_shader; } - const std::string& GetName() const { + std::string GetName() const { return m_name; } @@ -44,14 +88,6 @@ namespace dxvk { std::string m_name; Rc m_shader; - Sha1Hash ComputeShaderHash( - const void* pShaderBytecode, - size_t BytecodeLength) const; - - std::string ConstructFileName( - const Sha1Hash& hash, - const DxbcProgramType& type) const; - }; @@ -67,7 +103,7 @@ namespace dxvk { public: - D3D11Shader(D3D11Device* device, D3D11ShaderModule&& module) + D3D11Shader(D3D11Device* device, const D3D11ShaderModule& module) : m_device(device), m_module(std::move(module)) { } ~D3D11Shader() { } @@ -112,4 +148,37 @@ namespace dxvk { using D3D11PixelShader = D3D11Shader; using D3D11ComputeShader = D3D11Shader; + + /** + * \brief Shader module set + * + * Some applications may compile the same shader multiple + * times, so we should cache the resulting shader modules + * and reuse them rather than creating new ones. This + * class is thread-safe. + */ + class D3D11ShaderModuleSet { + + public: + + D3D11ShaderModuleSet(); + ~D3D11ShaderModuleSet(); + + D3D11ShaderModule GetShaderModule( + const DxbcOptions* pDxbcOptions, + const void* pShaderBytecode, + size_t BytecodeLength, + DxbcProgramType ProgramType); + + private: + + std::mutex m_mutex; + + std::unordered_map< + D3D11ShaderKey, + D3D11ShaderModule, + D3D11ShaderKeyHash> m_modules; + + }; + } diff --git a/src/util/sha1/sha1_util.h b/src/util/sha1/sha1_util.h index 19bdc0e7..10a777de 100644 --- a/src/util/sha1/sha1_util.h +++ b/src/util/sha1/sha1_util.h @@ -18,6 +18,17 @@ namespace dxvk { std::string toString() const; + const uint8_t* digest() const { + return m_digest.data(); + } + + bool operator == (const Sha1Hash& other) const { + return !std::memcmp( + this->m_digest.data(), + other.m_digest.data(), + other.m_digest.size()); + } + static Sha1Hash compute( const uint8_t* data, size_t size);