Files
renderdoc/renderdoc/driver/d3d12/d3d12_shader_cache.cpp
T

718 lines
21 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2019-2024 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#include "d3d12_shader_cache.h"
#include "common/shader_cache.h"
#include "core/plugins.h"
#include "core/settings.h"
#include "driver/dx/official/d3dcompiler.h"
#include "driver/dx/official/dxcapi.h"
#include "driver/dxgi/dxgi_common.h"
#include "driver/shaders/dxbc/dxbc_container.h"
#include "strings/string_utils.h"
#include "d3d12_device.h"
RDOC_CONFIG(rdcstr, D3D12_DXCPath, "", "The location of the dxcompiler.dll library to use.");
RDOC_EXTERN_CONFIG(bool, D3D12_Debug_EnableGPUVA);
static void *SearchForDXC(rdcstr &path)
{
void *ret = NULL;
path = D3D12_DXCPath();
if(strlower(path).contains("dxcompiler.dll") && FileIO::exists(path))
return Process::LoadModule(path);
path = LocatePluginFile("d3d12", "dxcompiler.dll");
// don't do a plain load yet, only load if we got an actual file
if(path != "dxcompiler.dll")
return Process::LoadModule(path);
// Search windows SDK folders, using the registry to locate the SDK
for(int wow64Pass = 0; wow64Pass < 2; wow64Pass++)
{
rdcstr regpath = "SOFTWARE\\";
if(wow64Pass == 1)
regpath += "Wow6432Node\\";
regpath += "Microsoft\\Microsoft SDKs\\Windows\\v10.0";
HKEY key = NULL;
LSTATUS regret = RegOpenKeyExA(HKEY_LOCAL_MACHINE, regpath.c_str(), 0, KEY_READ, &key);
if(regret != ERROR_SUCCESS)
{
if(key)
RegCloseKey(key);
continue;
}
DWORD dataSize = 0;
regret = RegGetValueW(key, NULL, L"InstallationFolder", RRF_RT_ANY, NULL, NULL, &dataSize);
if(regret == ERROR_SUCCESS)
{
// this is the size in bytes
wchar_t *data = new wchar_t[dataSize / sizeof(wchar_t)];
RegGetValueW(key, NULL, L"InstallationFolder", RRF_RT_ANY, NULL, data, &dataSize);
rdcstr sdkpath = StringFormat::Wide2UTF8(data);
RegCloseKey(key);
delete[] data;
if(sdkpath.back() != '\\')
sdkpath.push_back('\\');
// next search the versioned bin folders, from newest to oldest
sdkpath += "bin\\";
// sort by name
rdcarray<PathEntry> entries;
FileIO::GetFilesInDirectory(sdkpath, entries);
std::sort(entries.begin(), entries.end());
// do a reverse iteration so we get the latest SDK first
for(size_t i = 0; i < entries.size(); i++)
{
const PathEntry &e = entries[entries.size() - 1 - i];
// skip any files
if(!(e.flags & PathProperty::Directory))
continue;
// we've found an SDK! check to see if it contains dxcompiler.dll
if(e.filename.beginsWith("10.0."))
{
path = sdkpath + e.filename + "\\x64\\dxcompiler.dll";
if(FileIO::exists(path))
return Process::LoadModule(path);
}
}
// try in the Redist folder
path = sdkpath + "..\\Redist\\D3D\\x64\\dxcompiler.dll";
if(FileIO::exists(path))
return Process::LoadModule(path);
// if we've gotten here and haven't returned anything, then try just the base x64 folder
path = sdkpath + "x64\\dxcompiler.dll";
if(FileIO::exists(path))
return Process::LoadModule(path);
}
else
{
RegCloseKey(key);
}
}
// if we got here without finding any specific path'd dxc, just try loading it from anywhere
ret = Process::LoadModule("dxcompiler.dll");
path = "PATH";
return ret;
}
static HMODULE GetDXC()
{
static bool searched = false;
static HMODULE ret = NULL;
if(searched)
return ret;
searched = true;
// we actively do not try to load dxil.dll as we rely on our own hashing and the validator is not
// useful otherwise. This simplifies the search as we only need to find dxcompiler.dll, preferring
// one specified with a config variable, then our shipped version, then one in a windows SDK
// before finally searching PATH.
rdcstr path;
ret = (HMODULE)SearchForDXC(path);
if(ret)
RDCLOG("Loaded dxcompiler.dll from %s", path.c_str());
else
RDCERR("Couldn't load dxcompiler.dll");
return ret;
}
typedef HRESULT(WINAPI *pD3DCreateBlob)(SIZE_T Size, ID3DBlob **ppBlob);
typedef DXC_API_IMPORT HRESULT(__stdcall *pDxcCreateInstance)(REFCLSID rclsid, REFIID riid,
LPVOID *ppv);
struct D3D12BlobShaderCallbacks
{
pD3DCreateBlob GetCreateBlob() const
{
static pD3DCreateBlob blobCreate = NULL;
if(!blobCreate)
{
HMODULE d3dcompiler = GetD3DCompiler();
if(d3dcompiler == NULL)
RDCFATAL("Can't get handle to d3dcompiler_??.dll");
blobCreate = (pD3DCreateBlob)GetProcAddress(d3dcompiler, "D3DCreateBlob");
if(blobCreate == NULL)
RDCFATAL("d3dcompiler.dll doesn't contain D3DCreateBlob");
}
return blobCreate;
}
bool Create(uint32_t size, const void *data, ID3DBlob **ret) const
{
RDCASSERT(ret);
pD3DCreateBlob blobCreate = GetCreateBlob();
*ret = NULL;
HRESULT hr = blobCreate((SIZE_T)size, ret);
if(FAILED(hr))
{
RDCERR("Couldn't create blob of size %u from shadercache: HRESULT: %s", size,
ToStr(hr).c_str());
return false;
}
memcpy((*ret)->GetBufferPointer(), data, size);
return true;
}
void Destroy(ID3DBlob *blob) const { blob->Release(); }
uint32_t GetSize(ID3DBlob *blob) const { return (uint32_t)blob->GetBufferSize(); }
const byte *GetData(ID3DBlob *blob) const { return (const byte *)blob->GetBufferPointer(); }
} D3D12ShaderCacheCallbacks;
class EmbeddedID3DIncludeHandler : public IDxcIncludeHandler
{
public:
EmbeddedID3DIncludeHandler(IDxcLibrary *dxcLib, const rdcarray<rdcstr> &includeDirs,
const rdcarray<rdcpair<rdcstr, IDxcBlob *>> fixedFileBlobs)
: m_dxcLibrary(dxcLib)
{
m_includeDirs = includeDirs;
m_fixedFileBlobs = fixedFileBlobs;
if(m_dxcLibrary)
{
IDxcIncludeHandler *includeHandler = NULL;
HRESULT res = m_dxcLibrary->CreateIncludeHandler(&includeHandler);
if(SUCCEEDED(res))
m_defaultHandler = includeHandler;
}
}
virtual ~EmbeddedID3DIncludeHandler() { SAFE_RELEASE(m_defaultHandler); }
HRESULT STDMETHODCALLTYPE LoadSource(
_In_z_ LPCWSTR pFilename, // Candidate filename.
_COM_Outptr_result_maybenull_ IDxcBlob *
*ppIncludeSource // Resultant source object for included file, nullptr if not found.
)
{
IDxcBlob *dxcBlob = NULL;
rdcstr filename = StringFormat::Wide2UTF8(pFilename);
rdcstr fileNameWithoutRelSep = filename;
if(FileIO::IsRelativePath(filename))
{
size_t index = filename.find_first_of("./");
fileNameWithoutRelSep = filename.substr(index + 2);
}
for(const rdcpair<rdcstr, IDxcBlob *> &f : m_fixedFileBlobs)
{
if(filename == f.first || fileNameWithoutRelSep == f.first)
{
dxcBlob = f.second;
break;
}
}
if(!dxcBlob && FileIO::IsRelativePath(filename))
{
rdcstr absFilePath = fileNameWithoutRelSep;
for(const rdcstr &dir : m_includeDirs)
{
absFilePath = dir + absFilePath;
rdcstr source;
if(FileIO::exists(absFilePath) && FileIO::ReadAll(absFilePath, source) && m_dxcLibrary)
{
IDxcBlobEncoding *encodedBlob = NULL;
HRESULT res = m_dxcLibrary->CreateBlobWithEncodingFromPinned(
source.c_str(), (UINT32)source.size(), CP_UTF8, &encodedBlob);
if(!SUCCEEDED(res))
{
RDCERR("Unable to creata Blob");
}
else
{
dxcBlob = encodedBlob;
}
break;
}
}
}
if(dxcBlob)
{
*ppIncludeSource = dxcBlob;
return S_OK;
}
if(!dxcBlob && m_defaultHandler)
{
return m_defaultHandler->LoadSource(pFilename, ppIncludeSource);
}
return E_FAIL;
}
virtual HRESULT STDMETHODCALLTYPE QueryInterface(
/* [in] */ REFIID riid,
/* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR *__RPC_FAR *ppvObject)
{
if(m_defaultHandler)
{
return m_defaultHandler->QueryInterface(riid, ppvObject);
}
return E_FAIL;
}
virtual ULONG STDMETHODCALLTYPE AddRef(void)
{
if(m_defaultHandler)
return m_defaultHandler->AddRef();
return 0;
}
virtual ULONG STDMETHODCALLTYPE Release(void)
{
if(m_defaultHandler)
return m_defaultHandler->Release();
return 0;
}
private:
rdcarray<rdcstr> m_includeDirs;
rdcarray<rdcpair<rdcstr, IDxcBlob *>> m_fixedFileBlobs;
IDxcLibrary *m_dxcLibrary;
IDxcIncludeHandler *m_defaultHandler;
};
D3D12ShaderCache::D3D12ShaderCache(WrappedID3D12Device *device)
{
bool success = LoadShaderCache("d3dshaders.cache", m_ShaderCacheMagic, m_ShaderCacheVersion,
m_ShaderCache, D3D12ShaderCacheCallbacks);
// if we failed to load from the cache
m_ShaderCacheDirty = !success;
bool unopt = false;
if(RenderDoc::Inst().IsReplayApp())
{
RDCLOG("Unoptimising internal shaders for self-capture of replay");
static const GUID IRenderDoc_uuid = {
0xa7aa6116, 0x9c8d, 0x4bba, {0x90, 0x83, 0xb4, 0xd8, 0x16, 0xb7, 0x1b, 0x78}};
// if we're being self-captured, the 'real' device will respond to renderdoc's UUID. Enable
// debug shaders
IUnknown *dummy = NULL;
if(device->GetReal())
device->GetReal()->QueryInterface(IRenderDoc_uuid, (void **)&dummy);
unopt |= (dummy != NULL);
SAFE_RELEASE(dummy);
}
if(D3D12_Debug_EnableGPUVA())
{
RDCLOG("Unoptimising internal shaders for GPUVA");
unopt = true;
}
if(unopt)
{
m_CompileFlags |=
D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION | D3DCOMPILE_OPTIMIZATION_LEVEL0;
}
}
D3D12ShaderCache::~D3D12ShaderCache()
{
if(m_ShaderCacheDirty)
{
SaveShaderCache("d3dshaders.cache", m_ShaderCacheMagic, m_ShaderCacheVersion, m_ShaderCache,
D3D12ShaderCacheCallbacks);
}
else
{
for(auto it = m_ShaderCache.begin(); it != m_ShaderCache.end(); ++it)
D3D12ShaderCacheCallbacks.Destroy(it->second);
}
}
rdcstr D3D12ShaderCache::GetShaderBlob(const char *source, const char *entry,
const ShaderCompileFlags &compileFlags,
const rdcarray<rdcstr> &includeDirs, const char *profile,
ID3DBlob **srcblob)
{
rdcstr cbuffers = GetEmbeddedResource(hlsl_cbuffers_h);
rdcstr texsample = GetEmbeddedResource(hlsl_texsample_h);
uint32_t hash = strhash(source);
hash = strhash(entry, hash);
hash = strhash(profile, hash);
hash = strhash(cbuffers.c_str(), hash);
hash = strhash(texsample.c_str(), hash);
for(const ShaderCompileFlag &f : compileFlags.flags)
{
hash = strhash(f.name.c_str(), hash);
hash = strhash(f.value.c_str(), hash);
}
if(m_ShaderCache.find(hash) != m_ShaderCache.end())
{
*srcblob = m_ShaderCache[hash];
(*srcblob)->AddRef();
return "";
}
HRESULT hr = S_OK;
ID3DBlob *byteBlob = NULL;
ID3DBlob *errBlob = NULL;
if(profile[3] >= '6')
{
// compile as DXIL
UINT prevErrorMode = GetErrorMode();
SetErrorMode(prevErrorMode | SEM_FAILCRITICALERRORS);
HMODULE dxc = GetDXC();
SetErrorMode(prevErrorMode);
if(dxc == NULL)
{
return "Couldn't locate dxcompiler.dll. Ensure you have a Windows 10 SDK installed or place "
"dxcompiler.dll in RenderDoc's plugins/d3d12 folder.";
}
else
{
pDxcCreateInstance dxcCreate = (pDxcCreateInstance)GetProcAddress(dxc, "DxcCreateInstance");
IDxcLibrary *library = NULL;
hr = dxcCreate(CLSID_DxcLibrary, __uuidof(IDxcLibrary), (void **)&library);
if(FAILED(hr))
{
SAFE_RELEASE(library);
return "Couldn't create DXC library";
}
IDxcCompiler *compiler = NULL;
hr = dxcCreate(CLSID_DxcCompiler, __uuidof(IDxcCompiler), (void **)&compiler);
if(FAILED(hr))
{
SAFE_RELEASE(library);
SAFE_RELEASE(compiler);
return "Couldn't create DXC compiler";
}
IDxcBlobEncoding *sourceBlob = NULL;
hr = library->CreateBlobWithEncodingFromPinned(source, (UINT)strlen(source), CP_UTF8,
&sourceBlob);
if(FAILED(hr))
{
SAFE_RELEASE(library);
SAFE_RELEASE(compiler);
SAFE_RELEASE(sourceBlob);
return "Couldn't create DXC blob";
}
IDxcBlobEncoding *texSampleBlob = NULL;
hr = library->CreateBlobWithEncodingFromPinned(texsample.c_str(), (UINT)texsample.size(),
CP_UTF8, &texSampleBlob);
if(FAILED(hr))
{
SAFE_RELEASE(library);
SAFE_RELEASE(compiler);
SAFE_RELEASE(sourceBlob);
SAFE_RELEASE(texSampleBlob);
return "Couldn't create DXC blob";
}
IDxcBlobEncoding *cBufferBlob = NULL;
hr = library->CreateBlobWithEncodingFromPinned(cbuffers.c_str(), (UINT)cbuffers.size(),
CP_UTF8, &cBufferBlob);
if(FAILED(hr))
{
SAFE_RELEASE(library);
SAFE_RELEASE(compiler);
SAFE_RELEASE(sourceBlob);
SAFE_RELEASE(texSampleBlob);
SAFE_RELEASE(cBufferBlob);
return "Couldn't create DXC blob";
}
EmbeddedID3DIncludeHandler includeHandler(
library, includeDirs,
{{"hlsl_texsample.h", texSampleBlob}, {"hlsl_cbuffers.h", cBufferBlob}});
IDxcOperationResult *result = NULL;
uint32_t flags = DXBC::DecodeFlags(compileFlags) & ~D3DCOMPILE_NO_PRESHADER;
rdcarray<rdcwstr> argsData;
DXBC::EncodeDXCFlags(flags, argsData);
for(const ShaderCompileFlag &flag : compileFlags.flags)
{
if(flag.name == "@compile_option")
argsData.push_back(StringFormat::UTF82Wide(flag.value));
}
argsData.push_back(L"-select-validator internal");
rdcarray<LPCWSTR> arguments;
for(const rdcwstr &arg : argsData)
arguments.push_back(arg.c_str());
hr = compiler->Compile(sourceBlob, NULL, StringFormat::UTF82Wide(entry).c_str(),
StringFormat::UTF82Wide(profile).c_str(), arguments.data(),
arguments.count(), NULL, 0, &includeHandler, &result);
SAFE_RELEASE(sourceBlob);
if(SUCCEEDED(hr) && result)
result->GetStatus(&hr);
if(SUCCEEDED(hr))
{
IDxcBlob *code = NULL;
result->GetResult(&code);
D3D12ShaderCacheCallbacks.Create((uint32_t)code->GetBufferSize(), code->GetBufferPointer(),
&byteBlob);
if(!DXBC::DXBCContainer::IsHashedContainer(byteBlob->GetBufferPointer(),
byteBlob->GetBufferSize()))
DXBC::DXBCContainer::HashContainer(byteBlob->GetBufferPointer(), byteBlob->GetBufferSize());
SAFE_RELEASE(code);
}
else
{
if(result)
{
IDxcBlobEncoding *dxcErrors = NULL;
hr = result->GetErrorBuffer(&dxcErrors);
if(SUCCEEDED(hr) && dxcErrors)
{
D3D12ShaderCacheCallbacks.Create((uint32_t)dxcErrors->GetBufferSize(),
dxcErrors->GetBufferPointer(), &errBlob);
}
SAFE_RELEASE(dxcErrors);
}
if(!errBlob)
{
rdcstr err = "No compilation result found from DXC compile";
D3D12ShaderCacheCallbacks.Create((uint32_t)err.size(), err.c_str(), &errBlob);
}
}
SAFE_RELEASE(library);
SAFE_RELEASE(compiler);
SAFE_RELEASE(result);
}
}
else
{
EmbeddedD3DIncluder includer(includeDirs, {
{"hlsl_texsample.h", texsample},
{"hlsl_cbuffers.h", cbuffers},
});
HMODULE d3dcompiler = GetD3DCompiler();
if(d3dcompiler == NULL)
{
RDCFATAL("Can't get handle to d3dcompiler_??.dll");
}
pD3DCompile compileFunc = (pD3DCompile)GetProcAddress(d3dcompiler, "D3DCompile");
if(compileFunc == NULL)
{
RDCFATAL("Can't get D3DCompile from d3dcompiler_??.dll");
}
uint32_t flags = DXBC::DecodeFlags(compileFlags) & ~D3DCOMPILE_NO_PRESHADER;
hr = compileFunc(source, strlen(source), entry, NULL, &includer, entry, profile, flags, 0,
&byteBlob, &errBlob);
}
rdcstr errors = "";
if(errBlob)
{
errors = (char *)errBlob->GetBufferPointer();
rdcstr logerror = errors;
if(logerror.length() > 1024)
logerror = logerror.substr(0, 1024) + "...";
RDCWARN("Shader compile error in '%s':\n%s", entry, logerror.c_str());
SAFE_RELEASE(errBlob);
if(FAILED(hr))
{
SAFE_RELEASE(byteBlob);
return errors;
}
}
if(m_CacheShaders && byteBlob)
{
m_ShaderCache[hash] = byteBlob;
byteBlob->AddRef();
m_ShaderCacheDirty = true;
}
SAFE_RELEASE(errBlob);
*srcblob = byteBlob;
return errors;
}
rdcstr D3D12ShaderCache::GetShaderBlob(const char *source, const char *entry, uint32_t compileFlags,
const rdcarray<rdcstr> &includeDirs, const char *profile,
ID3DBlob **srcblob)
{
return GetShaderBlob(source, entry, DXBC::EncodeFlags(compileFlags | m_CompileFlags, profile),
includeDirs, profile, srcblob);
}
ID3DBlob *D3D12ShaderCache::MakeFixedColShader(FixedColVariant variant, bool dxil)
{
ID3DBlob *ret = NULL;
rdcstr hlsl =
StringFormat::Fmt("#define VARIANT %u\n\n", variant) + GetEmbeddedResource(fixedcol_hlsl);
bool wasCaching = m_CacheShaders;
m_CacheShaders = true;
GetShaderBlob(hlsl.c_str(), "main", ShaderCompileFlags(), {}, dxil ? "ps_6_0" : "ps_5_0", &ret);
m_CacheShaders = wasCaching;
if(!ret)
{
const rdcstr embedded[] = {
GetEmbeddedResource(fixedcol_0_dxbc),
GetEmbeddedResource(fixedcol_1_dxbc),
GetEmbeddedResource(fixedcol_2_dxbc),
GetEmbeddedResource(fixedcol_3_dxbc),
};
D3D12ShaderCacheCallbacks.Create((uint32_t)embedded[variant].size(), embedded[variant].data(),
&ret);
}
return ret;
}
ID3DBlob *D3D12ShaderCache::GetQuadShaderDXILBlob()
{
rdcstr embedded = GetEmbeddedResource(quadwrite_dxbc);
if(embedded.empty() || !embedded.beginsWith("DXBC"))
return NULL;
ID3DBlob *ret = NULL;
D3D12ShaderCacheCallbacks.Create((uint32_t)embedded.size(), embedded.data(), &ret);
return ret;
}
ID3DBlob *D3D12ShaderCache::GetPrimitiveIDShaderDXILBlob()
{
rdcstr embedded = GetEmbeddedResource(pixelhistory_primitiveid_dxbc);
if(embedded.empty() || !embedded.beginsWith("DXBC"))
return NULL;
ID3DBlob *ret = NULL;
D3D12ShaderCacheCallbacks.Create((uint32_t)embedded.size(), embedded.data(), &ret);
return ret;
}
ID3DBlob *D3D12ShaderCache::GetFixedColorShaderDXILBlob(uint32_t variant)
{
const rdcstr variants[] = {
GetEmbeddedResource(pixelhistory_fixedcol_0_dxbc),
GetEmbeddedResource(pixelhistory_fixedcol_1_dxbc),
GetEmbeddedResource(pixelhistory_fixedcol_2_dxbc),
GetEmbeddedResource(pixelhistory_fixedcol_3_dxbc),
GetEmbeddedResource(pixelhistory_fixedcol_4_dxbc),
GetEmbeddedResource(pixelhistory_fixedcol_5_dxbc),
GetEmbeddedResource(pixelhistory_fixedcol_6_dxbc),
GetEmbeddedResource(pixelhistory_fixedcol_7_dxbc),
};
const rdcstr embedded = variants[variant];
if(embedded.empty() || !embedded.beginsWith("DXBC"))
return NULL;
ID3DBlob *ret = NULL;
D3D12ShaderCacheCallbacks.Create((uint32_t)embedded.size(), embedded.data(), &ret);
return ret;
}
void D3D12ShaderCache::LoadDXC()
{
GetDXC();
}