dolphin/Source/Core/VideoBackends/D3D12/ShaderCache.cpp
Scott Mansell 1a831cfc7d Multithreadded Shadergen: Second Pass over vertex/lighting Shadergens
As much as possible, the asserts have been moved out of the GetUID
function. But there are some places where asserts depend on variables
that aren't stored in the shader UID.
2016-06-26 16:13:21 +12:00

417 lines
13 KiB
C++

// Copyright 2015 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "Common/LinearDiskCache.h"
#include "Core/ConfigManager.h"
#include "VideoBackends/D3D12/D3DCommandListManager.h"
#include "VideoBackends/D3D12/D3DShader.h"
#include "VideoBackends/D3D12/ShaderCache.h"
#include "VideoCommon/Debugger.h"
#include "VideoCommon/Statistics.h"
namespace DX12
{
// Primitive topology type is always triangle, unless the GS stage is used. This is consumed
// by the PSO created in Renderer::ApplyState.
static D3D12_PRIMITIVE_TOPOLOGY_TYPE s_current_primitive_topology =
D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
using GsBytecodeCache = std::map<GeometryShaderUid, D3D12_SHADER_BYTECODE>;
using PsBytecodeCache = std::map<PixelShaderUid, D3D12_SHADER_BYTECODE>;
using VsBytecodeCache = std::map<VertexShaderUid, D3D12_SHADER_BYTECODE>;
GsBytecodeCache s_gs_bytecode_cache;
PsBytecodeCache s_ps_bytecode_cache;
VsBytecodeCache s_vs_bytecode_cache;
// Used to keep track of blobs to release at Shutdown time.
static std::vector<ID3DBlob*> s_shader_blob_list;
// Only used for shader debugging..
using GsHlslCache = std::map<GeometryShaderUid, std::string>;
using PsHlslCache = std::map<PixelShaderUid, std::string>;
using VsHlslCache = std::map<VertexShaderUid, std::string>;
static GsHlslCache s_gs_hlsl_cache;
static PsHlslCache s_ps_hlsl_cache;
static VsHlslCache s_vs_hlsl_cache;
static LinearDiskCache<GeometryShaderUid, u8> s_gs_disk_cache;
static LinearDiskCache<PixelShaderUid, u8> s_ps_disk_cache;
static LinearDiskCache<VertexShaderUid, u8> s_vs_disk_cache;
static UidChecker<GeometryShaderUid, ShaderCode> s_geometry_uid_checker;
static UidChecker<PixelShaderUid, ShaderCode> s_pixel_uid_checker;
static UidChecker<VertexShaderUid, ShaderCode> s_vertex_uid_checker;
static D3D12_SHADER_BYTECODE s_last_geometry_shader_bytecode;
static D3D12_SHADER_BYTECODE s_last_pixel_shader_bytecode;
static D3D12_SHADER_BYTECODE s_last_vertex_shader_bytecode;
static GeometryShaderUid s_last_geometry_shader_uid;
static PixelShaderUid s_last_pixel_shader_uid;
static VertexShaderUid s_last_vertex_shader_uid;
template <class UidType, class ShaderCacheType, ShaderCacheType* cache>
class ShaderCacheInserter final : public LinearDiskCacheReader<UidType, u8>
{
public:
void Read(const UidType& key, const u8* value, u32 value_size)
{
ID3DBlob* blob = nullptr;
CheckHR(d3d_create_blob(value_size, &blob));
memcpy(blob->GetBufferPointer(), value, value_size);
ShaderCache::InsertByteCode<UidType, ShaderCacheType>(key, cache, blob);
}
};
void ShaderCache::Init()
{
// This class intentionally shares its shader cache files with DX11, as the shaders are (right
// now) identical.
// Reduces unnecessary compilation when switching between APIs.
s_last_geometry_shader_bytecode = {};
s_last_pixel_shader_bytecode = {};
s_last_vertex_shader_bytecode = {};
s_last_geometry_shader_uid = {};
s_last_pixel_shader_uid = {};
s_last_vertex_shader_uid = {};
// Ensure shader cache directory exists..
std::string shader_cache_path = File::GetUserPath(D_SHADERCACHE_IDX);
if (!File::Exists(shader_cache_path))
File::CreateDir(File::GetUserPath(D_SHADERCACHE_IDX));
std::string title_unique_id = SConfig::GetInstance().m_strUniqueID.c_str();
std::string gs_cache_filename =
StringFromFormat("%sdx11-%s-gs.cache", shader_cache_path.c_str(), title_unique_id.c_str());
std::string ps_cache_filename =
StringFromFormat("%sdx11-%s-ps.cache", shader_cache_path.c_str(), title_unique_id.c_str());
std::string vs_cache_filename =
StringFromFormat("%sdx11-%s-vs.cache", shader_cache_path.c_str(), title_unique_id.c_str());
ShaderCacheInserter<GeometryShaderUid, GsBytecodeCache, &s_gs_bytecode_cache> gs_inserter;
s_gs_disk_cache.OpenAndRead(gs_cache_filename, gs_inserter);
ShaderCacheInserter<PixelShaderUid, PsBytecodeCache, &s_ps_bytecode_cache> ps_inserter;
s_ps_disk_cache.OpenAndRead(ps_cache_filename, ps_inserter);
ShaderCacheInserter<VertexShaderUid, VsBytecodeCache, &s_vs_bytecode_cache> vs_inserter;
s_vs_disk_cache.OpenAndRead(vs_cache_filename, vs_inserter);
// Clear out cache when debugging shaders to ensure stale ones don't stick around..
if (g_Config.bEnableShaderDebugging)
Clear();
SETSTAT(stats.numPixelShadersAlive, static_cast<int>(s_ps_bytecode_cache.size()));
SETSTAT(stats.numPixelShadersCreated, static_cast<int>(s_ps_bytecode_cache.size()));
SETSTAT(stats.numVertexShadersAlive, static_cast<int>(s_vs_bytecode_cache.size()));
SETSTAT(stats.numVertexShadersCreated, static_cast<int>(s_vs_bytecode_cache.size()));
}
void ShaderCache::Clear()
{
for (auto& iter : s_shader_blob_list)
SAFE_RELEASE(iter);
s_shader_blob_list.clear();
s_gs_bytecode_cache.clear();
s_ps_bytecode_cache.clear();
s_vs_bytecode_cache.clear();
s_last_geometry_shader_bytecode = {};
s_last_geometry_shader_uid = {};
s_last_pixel_shader_bytecode = {};
s_last_pixel_shader_uid = {};
s_last_vertex_shader_bytecode = {};
s_last_vertex_shader_uid = {};
}
void ShaderCache::Shutdown()
{
Clear();
s_gs_disk_cache.Sync();
s_gs_disk_cache.Close();
s_ps_disk_cache.Sync();
s_ps_disk_cache.Close();
s_vs_disk_cache.Sync();
s_vs_disk_cache.Close();
if (g_Config.bEnableShaderDebugging)
{
s_gs_hlsl_cache.clear();
s_ps_hlsl_cache.clear();
s_vs_hlsl_cache.clear();
}
s_geometry_uid_checker.Invalidate();
s_pixel_uid_checker.Invalidate();
s_vertex_uid_checker.Invalidate();
}
void ShaderCache::LoadAndSetActiveShaders(DSTALPHA_MODE ps_dst_alpha_mode, u32 gs_primitive_type)
{
SetCurrentPrimitiveTopology(gs_primitive_type);
GeometryShaderUid gs_uid = GetGeometryShaderUid(gs_primitive_type);
PixelShaderUid ps_uid = GetPixelShaderUid(ps_dst_alpha_mode, API_D3D);
VertexShaderUid vs_uid = GetVertexShaderUid();
bool gs_changed = gs_uid != s_last_geometry_shader_uid;
bool ps_changed = ps_uid != s_last_pixel_shader_uid;
bool vs_changed = vs_uid != s_last_vertex_shader_uid;
if (!gs_changed && !ps_changed && !vs_changed)
{
return;
}
if (gs_changed)
{
HandleGSUIDChange(gs_uid, gs_primitive_type);
}
if (ps_changed)
{
HandlePSUIDChange(ps_uid, ps_dst_alpha_mode);
}
if (vs_changed)
{
HandleVSUIDChange(vs_uid);
}
// A Uid has changed, so the PSO will need to be reset at next ApplyState.
D3D::command_list_mgr->SetCommandListDirtyState(COMMAND_LIST_STATE_PSO, true);
}
void ShaderCache::SetCurrentPrimitiveTopology(u32 gs_primitive_type)
{
switch (gs_primitive_type)
{
case PRIMITIVE_TRIANGLES:
s_current_primitive_topology = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
break;
case PRIMITIVE_LINES:
s_current_primitive_topology = D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE;
break;
case PRIMITIVE_POINTS:
s_current_primitive_topology = D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT;
break;
default:
CHECK(0, "Invalid primitive type.");
break;
}
}
void ShaderCache::HandleGSUIDChange(GeometryShaderUid gs_uid, u32 gs_primitive_type)
{
s_last_geometry_shader_uid = gs_uid;
if (g_ActiveConfig.bEnableShaderDebugging)
{
ShaderCode code = GenerateGeometryShaderCode(gs_primitive_type, API_D3D, gs_uid.GetUidData());
s_geometry_uid_checker.AddToIndexAndCheck(code, gs_uid, "Geometry", "g");
}
if (gs_uid.GetUidData()->IsPassthrough())
{
s_last_geometry_shader_bytecode = {};
return;
}
auto gs_iterator = s_gs_bytecode_cache.find(gs_uid);
if (gs_iterator != s_gs_bytecode_cache.end())
{
s_last_geometry_shader_bytecode = gs_iterator->second;
}
else
{
ShaderCode gs_code =
GenerateGeometryShaderCode(gs_primitive_type, API_D3D, gs_uid.GetUidData());
ID3DBlob* gs_bytecode = nullptr;
if (!D3D::CompileGeometryShader(gs_code.GetBuffer(), &gs_bytecode))
{
GFX_DEBUGGER_PAUSE_AT(NEXT_ERROR, true);
return;
}
s_last_geometry_shader_bytecode = InsertByteCode(gs_uid, &s_gs_bytecode_cache, gs_bytecode);
s_gs_disk_cache.Append(gs_uid, reinterpret_cast<u8*>(gs_bytecode->GetBufferPointer()),
static_cast<u32>(gs_bytecode->GetBufferSize()));
if (g_ActiveConfig.bEnableShaderDebugging)
{
s_gs_hlsl_cache[gs_uid] = gs_code.GetBuffer();
}
}
}
void ShaderCache::HandlePSUIDChange(PixelShaderUid ps_uid, DSTALPHA_MODE ps_dst_alpha_mode)
{
s_last_pixel_shader_uid = ps_uid;
if (g_ActiveConfig.bEnableShaderDebugging)
{
ShaderCode code = GeneratePixelShaderCode(ps_dst_alpha_mode, API_D3D);
s_pixel_uid_checker.AddToIndexAndCheck(code, ps_uid, "Pixel", "p");
}
auto ps_iterator = s_ps_bytecode_cache.find(ps_uid);
if (ps_iterator != s_ps_bytecode_cache.end())
{
s_last_pixel_shader_bytecode = ps_iterator->second;
GFX_DEBUGGER_PAUSE_AT(NEXT_PIXEL_SHADER_CHANGE, true);
}
else
{
ShaderCode ps_code = GeneratePixelShaderCode(ps_dst_alpha_mode, API_D3D);
ID3DBlob* ps_bytecode = nullptr;
if (!D3D::CompilePixelShader(ps_code.GetBuffer(), &ps_bytecode))
{
GFX_DEBUGGER_PAUSE_AT(NEXT_ERROR, true);
return;
}
s_last_pixel_shader_bytecode = InsertByteCode(ps_uid, &s_ps_bytecode_cache, ps_bytecode);
s_ps_disk_cache.Append(ps_uid, reinterpret_cast<u8*>(ps_bytecode->GetBufferPointer()),
static_cast<u32>(ps_bytecode->GetBufferSize()));
SETSTAT(stats.numPixelShadersAlive, static_cast<int>(s_ps_bytecode_cache.size()));
INCSTAT(stats.numPixelShadersCreated);
if (g_ActiveConfig.bEnableShaderDebugging)
{
s_ps_hlsl_cache[ps_uid] = ps_code.GetBuffer();
}
}
}
void ShaderCache::HandleVSUIDChange(VertexShaderUid vs_uid)
{
s_last_vertex_shader_uid = vs_uid;
if (g_ActiveConfig.bEnableShaderDebugging)
{
ShaderCode code = GenerateVertexShaderCode(API_D3D, vs_uid.GetUidData());
s_vertex_uid_checker.AddToIndexAndCheck(code, vs_uid, "Vertex", "v");
}
auto vs_iterator = s_vs_bytecode_cache.find(vs_uid);
if (vs_iterator != s_vs_bytecode_cache.end())
{
s_last_vertex_shader_bytecode = vs_iterator->second;
GFX_DEBUGGER_PAUSE_AT(NEXT_VERTEX_SHADER_CHANGE, true);
}
else
{
ShaderCode vs_code = GenerateVertexShaderCode(API_D3D, vs_uid.GetUidData());
ID3DBlob* vs_bytecode = nullptr;
if (!D3D::CompileVertexShader(vs_code.GetBuffer(), &vs_bytecode))
{
GFX_DEBUGGER_PAUSE_AT(NEXT_ERROR, true);
return;
}
s_last_vertex_shader_bytecode = InsertByteCode(vs_uid, &s_vs_bytecode_cache, vs_bytecode);
s_vs_disk_cache.Append(vs_uid, reinterpret_cast<u8*>(vs_bytecode->GetBufferPointer()),
static_cast<u32>(vs_bytecode->GetBufferSize()));
SETSTAT(stats.numVertexShadersAlive, static_cast<int>(s_vs_bytecode_cache.size()));
INCSTAT(stats.numVertexShadersCreated);
if (g_ActiveConfig.bEnableShaderDebugging)
{
s_vs_hlsl_cache[vs_uid] = vs_code.GetBuffer();
}
}
}
template <class UidType, class ShaderCacheType>
D3D12_SHADER_BYTECODE ShaderCache::InsertByteCode(const UidType& uid, ShaderCacheType* shader_cache,
ID3DBlob* bytecode_blob)
{
// Note: Don't release the incoming bytecode, we need it to stick around, since in D3D12
// the raw bytecode itself is bound. It is released at Shutdown() time.
s_shader_blob_list.push_back(bytecode_blob);
D3D12_SHADER_BYTECODE shader_bytecode;
shader_bytecode.pShaderBytecode = bytecode_blob->GetBufferPointer();
shader_bytecode.BytecodeLength = bytecode_blob->GetBufferSize();
(*shader_cache)[uid] = shader_bytecode;
return shader_bytecode;
}
D3D12_PRIMITIVE_TOPOLOGY_TYPE ShaderCache::GetCurrentPrimitiveTopology()
{
return s_current_primitive_topology;
}
D3D12_SHADER_BYTECODE ShaderCache::GetActiveGeometryShaderBytecode()
{
return s_last_geometry_shader_bytecode;
}
D3D12_SHADER_BYTECODE ShaderCache::GetActivePixelShaderBytecode()
{
return s_last_pixel_shader_bytecode;
}
D3D12_SHADER_BYTECODE ShaderCache::GetActiveVertexShaderBytecode()
{
return s_last_vertex_shader_bytecode;
}
const GeometryShaderUid* ShaderCache::GetActiveGeometryShaderUid()
{
return &s_last_geometry_shader_uid;
}
const PixelShaderUid* ShaderCache::GetActivePixelShaderUid()
{
return &s_last_pixel_shader_uid;
}
const VertexShaderUid* ShaderCache::GetActiveVertexShaderUid()
{
return &s_last_vertex_shader_uid;
}
D3D12_SHADER_BYTECODE ShaderCache::GetGeometryShaderFromUid(const GeometryShaderUid* uid)
{
auto bytecode = s_gs_bytecode_cache.find(*uid);
if (bytecode != s_gs_bytecode_cache.end())
return bytecode->second;
return D3D12_SHADER_BYTECODE();
}
D3D12_SHADER_BYTECODE ShaderCache::GetPixelShaderFromUid(const PixelShaderUid* uid)
{
auto bytecode = s_ps_bytecode_cache.find(*uid);
if (bytecode != s_ps_bytecode_cache.end())
return bytecode->second;
return D3D12_SHADER_BYTECODE();
}
D3D12_SHADER_BYTECODE ShaderCache::GetVertexShaderFromUid(const VertexShaderUid* uid)
{
auto bytecode = s_vs_bytecode_cache.find(*uid);
if (bytecode != s_vs_bytecode_cache.end())
return bytecode->second;
return D3D12_SHADER_BYTECODE();
}
}