ShaderCache: Implement background shader compilation

This enables shaders to be compiled while the game is starting, instead
of blocking startup. If a shader is needed before it is compiled,
emulation will block.
This commit is contained in:
Stenzek 2018-03-01 19:21:06 +10:00
parent 9fa24700b6
commit e31cc1f679
15 changed files with 202 additions and 145 deletions

View File

@ -76,6 +76,8 @@ const ConfigInfo<bool> GFX_BACKEND_MULTITHREADING{
const ConfigInfo<int> GFX_COMMAND_BUFFER_EXECUTE_INTERVAL{
{System::GFX, "Settings", "CommandBufferExecuteInterval"}, 100};
const ConfigInfo<bool> GFX_SHADER_CACHE{{System::GFX, "Settings", "ShaderCache"}, true};
const ConfigInfo<bool> GFX_WAIT_FOR_SHADERS_BEFORE_STARTING{
{System::GFX, "Settings", "WaitForShadersBeforeStarting"}, false};
const ConfigInfo<int> GFX_UBERSHADER_MODE{{System::GFX, "Settings", "UberShaderMode"},
static_cast<int>(UberShaderMode::Disabled)};
const ConfigInfo<int> GFX_SHADER_COMPILER_THREADS{

View File

@ -59,6 +59,7 @@ extern const ConfigInfo<bool> GFX_ENABLE_VALIDATION_LAYER;
extern const ConfigInfo<bool> GFX_BACKEND_MULTITHREADING;
extern const ConfigInfo<int> GFX_COMMAND_BUFFER_EXECUTE_INTERVAL;
extern const ConfigInfo<bool> GFX_SHADER_CACHE;
extern const ConfigInfo<bool> GFX_WAIT_FOR_SHADERS_BEFORE_STARTING;
extern const ConfigInfo<int> GFX_UBERSHADER_MODE;
extern const ConfigInfo<int> GFX_SHADER_COMPILER_THREADS;
extern const ConfigInfo<int> GFX_SHADER_PRECOMPILER_THREADS;

View File

@ -46,8 +46,8 @@ bool IsSettingSaveable(const Config::ConfigLocation& config_location)
Config::GFX_DISABLE_FOG.location, Config::GFX_BORDERLESS_FULLSCREEN.location,
Config::GFX_ENABLE_VALIDATION_LAYER.location, Config::GFX_BACKEND_MULTITHREADING.location,
Config::GFX_COMMAND_BUFFER_EXECUTE_INTERVAL.location, Config::GFX_SHADER_CACHE.location,
Config::GFX_UBERSHADER_MODE.location, Config::GFX_SHADER_COMPILER_THREADS.location,
Config::GFX_SHADER_PRECOMPILER_THREADS.location,
Config::GFX_WAIT_FOR_SHADERS_BEFORE_STARTING.location, Config::GFX_UBERSHADER_MODE.location,
Config::GFX_SHADER_COMPILER_THREADS.location, Config::GFX_SHADER_PRECOMPILER_THREADS.location,
Config::GFX_SW_ZCOMPLOC.location, Config::GFX_SW_ZFREEZE.location,
Config::GFX_SW_DUMP_OBJECTS.location, Config::GFX_SW_DUMP_TEV_STAGES.location,

View File

@ -87,6 +87,8 @@ void GeneralWidget::CreateWidgets()
m_keep_window_top = new QCheckBox(tr("Keep Window on Top"));
m_hide_cursor = new QCheckBox(tr("Hide Mouse Cursor"));
m_render_main_window = new QCheckBox(tr("Render to Main Window"));
m_wait_for_shaders = new GraphicsBool(tr("Immediately Compile Shaders"),
Config::GFX_WAIT_FOR_SHADERS_BEFORE_STARTING);
m_options_box->setLayout(m_options_layout);
@ -101,6 +103,7 @@ void GeneralWidget::CreateWidgets()
m_options_layout->addWidget(m_hide_cursor, 3, 0);
m_options_layout->addWidget(m_render_main_window, 3, 1);
m_options_layout->addWidget(m_wait_for_shaders, 4, 0);
main_layout->addWidget(m_video_box);
main_layout->addWidget(m_options_box);
@ -265,6 +268,12 @@ void GeneralWidget::AddDescriptions()
static const char* TR_SHOW_NETPLAY_MESSAGES_DESCRIPTION =
QT_TR_NOOP("When playing on NetPlay, show chat messages, buffer changes and "
"desync alerts.\n\nIf unsure, leave this unchecked.");
static const char* TR_WAIT_FOR_SHADERS_DESCRIPTION = QT_TR_NOOP(
"Waits for all shaders to finish compiling before starting a game. Enabling this "
"option may reduce stuttering or hitching for a short time after the game is "
"started, at the cost of a longer delay before the game starts.\n\nFor systems "
"with two or fewer cores, it is recommended to enable this option, as a large "
"shader queue may reduce frame rates. Otherwise, if unsure, leave this unchecked.");
AddDescription(m_backend_combo, TR_BACKEND_DESCRIPTION);
#ifdef _WIN32
@ -282,6 +291,7 @@ void GeneralWidget::AddDescriptions()
AddDescription(m_show_messages, TR_SHOW_FPS_DESCRIPTION);
AddDescription(m_keep_window_top, TR_KEEP_WINDOW_ON_TOP_DESCRIPTION);
AddDescription(m_show_messages, TR_SHOW_NETPLAY_MESSAGES_DESCRIPTION);
AddDescription(m_wait_for_shaders, TR_WAIT_FOR_SHADERS_DESCRIPTION);
}
void GeneralWidget::OnBackendChanged(const QString& backend_name)
{

View File

@ -52,6 +52,7 @@ private:
QCheckBox* m_keep_window_top;
QCheckBox* m_hide_cursor;
QCheckBox* m_render_main_window;
QCheckBox* m_wait_for_shaders;
X11Utils::XRRConfiguration* m_xrr_config;
};

View File

@ -317,6 +317,12 @@ static wxString ubershader_desc =
"stuttering. Balances performance and smoothness.\n\n"
"Exclusive: Ubershaders will always be used. Only recommended for high-end "
"systems.");
static wxString wait_for_shaders_desc =
wxTRANSLATE("Waits for all shaders to finish compiling before starting a game. Enabling this "
"option may reduce stuttering or hitching for a short time after the game is "
"started, at the cost of a longer delay before the game starts.\n\nFor systems "
"with two or fewer cores, it is recommended to enable this option, as a large "
"shader queue may reduce frame rates. Otherwise, if unsure, leave this unchecked.");
VideoConfigDiag::VideoConfigDiag(wxWindow* parent, const std::string& title)
: wxDialog(parent, wxID_ANY, wxString::Format(_("Dolphin %s Graphics Configuration"),
@ -442,6 +448,10 @@ VideoConfigDiag::VideoConfigDiag(wxWindow* parent, const std::string& title)
wxGetTranslation(backend_multithreading_desc),
Config::GFX_BACKEND_MULTITHREADING));
}
szr_other->Add(CreateCheckBox(page_general, _("Immediately Compile Shaders"),
wxGetTranslation(wait_for_shaders_desc),
Config::GFX_WAIT_FOR_SHADERS_BEFORE_STARTING));
}
wxStaticBoxSizer* const group_basic =

View File

@ -18,7 +18,6 @@ AsyncShaderCompiler::~AsyncShaderCompiler()
// Pending work can be left at shutdown.
// The work item classes are expected to clean up after themselves.
_assert_(!HasWorkerThreads());
_assert_(m_completed_work.empty());
}
void AsyncShaderCompiler::QueueWorkItem(WorkItemPtr item)

View File

@ -0,0 +1,75 @@
// Copyright 2018 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include "VideoCommon/GeometryShaderGen.h"
#include "VideoCommon/PixelShaderGen.h"
#include "VideoCommon/RenderState.h"
#include "VideoCommon/UberShaderPixel.h"
#include "VideoCommon/UberShaderVertex.h"
#include "VideoCommon/VertexShaderGen.h"
class NativeVertexFormat;
namespace VideoCommon
{
struct GXPipelineUid
{
const NativeVertexFormat* vertex_format;
VertexShaderUid vs_uid;
GeometryShaderUid gs_uid;
PixelShaderUid ps_uid;
RasterizationState rasterization_state;
DepthState depth_state;
BlendingState blending_state;
// We use memcmp() for comparing pipelines as std::tie generates a large number of instructions,
// and this map lookup can happen every draw call. However, as using memcmp() will also compare
// any padding bytes, we have to ensure these are zeroed out.
GXPipelineUid() { std::memset(this, 0, sizeof(*this)); }
GXPipelineUid(const GXPipelineUid& rhs) { std::memcpy(this, &rhs, sizeof(*this)); }
GXPipelineUid& operator=(const GXPipelineUid& rhs)
{
std::memcpy(this, &rhs, sizeof(*this));
return *this;
}
bool operator<(const GXPipelineUid& rhs) const
{
return std::memcmp(this, &rhs, sizeof(*this)) < 0;
}
bool operator==(const GXPipelineUid& rhs) const
{
return std::memcmp(this, &rhs, sizeof(*this)) == 0;
}
bool operator!=(const GXPipelineUid& rhs) const { return !operator==(rhs); }
};
struct GXUberPipelineUid
{
const NativeVertexFormat* vertex_format;
UberShader::VertexShaderUid vs_uid;
GeometryShaderUid gs_uid;
UberShader::PixelShaderUid ps_uid;
RasterizationState rasterization_state;
DepthState depth_state;
BlendingState blending_state;
GXUberPipelineUid() { std::memset(this, 0, sizeof(*this)); }
GXUberPipelineUid(const GXUberPipelineUid& rhs) { std::memcpy(this, &rhs, sizeof(*this)); }
GXUberPipelineUid& operator=(const GXUberPipelineUid& rhs)
{
std::memcpy(this, &rhs, sizeof(*this));
return *this;
}
bool operator<(const GXUberPipelineUid& rhs) const
{
return std::memcmp(this, &rhs, sizeof(*this)) < 0;
}
bool operator==(const GXUberPipelineUid& rhs) const
{
return std::memcmp(this, &rhs, sizeof(*this)) == 0;
}
bool operator!=(const GXUberPipelineUid& rhs) const { return !operator==(rhs); }
};
} // namespace VideoCommon

View File

@ -43,6 +43,8 @@ bool ShaderCache::Initialize()
// Compile all known UIDs.
CompileMissingPipelines();
if (g_ActiveConfig.bWaitForShadersBeforeStarting)
WaitForAsyncCompiler();
// Switch to the runtime shader compiler thread configuration.
m_async_shader_compiler->ResizeWorkerThreads(g_ActiveConfig.GetShaderCompilerThreads());
@ -61,9 +63,7 @@ void ShaderCache::SetHostConfig(const ShaderHostConfig& host_config, u32 efb_mul
void ShaderCache::Reload()
{
m_async_shader_compiler->WaitUntilCompletion();
m_async_shader_compiler->RetrieveWorkItems();
WaitForAsyncCompiler();
InvalidateCachedPipelines();
ClearShaderCaches();
@ -77,6 +77,8 @@ void ShaderCache::Reload()
// UIDs are still be in the map. Therefore, when these are rebuilt, the shaders will also
// be recompiled.
CompileMissingPipelines();
if (g_ActiveConfig.bWaitForShadersBeforeStarting)
WaitForAsyncCompiler();
m_async_shader_compiler->ResizeWorkerThreads(g_ActiveConfig.GetShaderCompilerThreads());
}
@ -87,14 +89,14 @@ void ShaderCache::RetrieveAsyncShaders()
void ShaderCache::Shutdown()
{
// This may leave shaders uncommitted to the cache, but it's better than blocking shutdown
// until everything has finished compiling.
m_async_shader_compiler->StopWorkerThreads();
m_async_shader_compiler->RetrieveWorkItems();
ClearShaderCaches();
ClearPipelineCaches();
}
const AbstractPipeline* ShaderCache::GetPipelineForUid(const GXPipelineConfig& uid)
const AbstractPipeline* ShaderCache::GetPipelineForUid(const GXPipelineUid& uid)
{
auto it = m_gx_pipeline_cache.find(uid);
if (it != m_gx_pipeline_cache.end() && !it->second.second)
@ -109,8 +111,7 @@ const AbstractPipeline* ShaderCache::GetPipelineForUid(const GXPipelineConfig& u
return InsertGXPipeline(uid, std::move(pipeline));
}
std::optional<const AbstractPipeline*>
ShaderCache::GetPipelineForUidAsync(const GXPipelineConfig& uid)
std::optional<const AbstractPipeline*> ShaderCache::GetPipelineForUidAsync(const GXPipelineUid& uid)
{
auto it = m_gx_pipeline_cache.find(uid);
if (it != m_gx_pipeline_cache.end())
@ -159,7 +160,7 @@ ShaderCache::GetPipelineForUidAsync(const GXPipelineConfig& uid)
return {};
}
const AbstractPipeline* ShaderCache::GetUberPipelineForUid(const GXUberPipelineConfig& uid)
const AbstractPipeline* ShaderCache::GetUberPipelineForUid(const GXUberPipelineUid& uid)
{
auto it = m_gx_uber_pipeline_cache.find(uid);
if (it != m_gx_uber_pipeline_cache.end() && !it->second.second)
@ -172,12 +173,16 @@ const AbstractPipeline* ShaderCache::GetUberPipelineForUid(const GXUberPipelineC
return InsertGXUberPipeline(uid, std::move(pipeline));
}
void ShaderCache::WaitForAsyncCompiler(const std::string& msg)
void ShaderCache::WaitForAsyncCompiler()
{
m_async_shader_compiler->WaitUntilCompletion([&msg](size_t completed, size_t total) {
Host_UpdateProgressDialog(msg.c_str(), static_cast<int>(completed), static_cast<int>(total));
});
m_async_shader_compiler->RetrieveWorkItems();
while (m_async_shader_compiler->HasPendingWork())
{
m_async_shader_compiler->WaitUntilCompletion([](size_t completed, size_t total) {
Host_UpdateProgressDialog(GetStringT("Compiling shaders...").c_str(),
static_cast<int>(completed), static_cast<int>(total));
});
m_async_shader_compiler->RetrieveWorkItems();
}
Host_UpdateProgressDialog("", -1, -1);
}
@ -274,7 +279,7 @@ void ShaderCache::LoadPipelineUIDCache()
CacheReader(ShaderCache* shader_cache_) : shader_cache(shader_cache_) {}
void Read(const GXPipelineDiskCacheUid& key, const u8* data, u32 data_size)
{
GXPipelineConfig config = {};
GXPipelineUid config = {};
config.vertex_format = VertexLoaderManager::GetOrCreateMatchingFormat(key.vertex_decl);
config.vs_uid = key.vs_uid;
config.gs_uid = key.gs_uid;
@ -315,8 +320,6 @@ void ShaderCache::CompileMissingPipelines()
if (!it.second.second)
QueueUberPipelineCompile(it.first);
}
WaitForAsyncCompiler(GetStringT("Compiling shaders..."));
}
void ShaderCache::InvalidateCachedPipelines()
@ -508,8 +511,7 @@ AbstractPipelineConfig ShaderCache::GetGXPipelineConfig(
return config;
}
std::optional<AbstractPipelineConfig>
ShaderCache::GetGXPipelineConfig(const GXPipelineConfig& config)
std::optional<AbstractPipelineConfig> ShaderCache::GetGXPipelineConfig(const GXPipelineUid& config)
{
const AbstractShader* vs;
auto vs_iter = m_vs_cache.shader_map.find(config.vs_uid);
@ -545,7 +547,7 @@ ShaderCache::GetGXPipelineConfig(const GXPipelineConfig& config)
}
std::optional<AbstractPipelineConfig>
ShaderCache::GetGXUberPipelineConfig(const GXUberPipelineConfig& config)
ShaderCache::GetGXUberPipelineConfig(const GXUberPipelineUid& config)
{
const AbstractShader* vs;
auto vs_iter = m_uber_vs_cache.shader_map.find(config.vs_uid);
@ -580,7 +582,7 @@ ShaderCache::GetGXUberPipelineConfig(const GXUberPipelineConfig& config)
config.depth_state, config.blending_state);
}
const AbstractPipeline* ShaderCache::InsertGXPipeline(const GXPipelineConfig& config,
const AbstractPipeline* ShaderCache::InsertGXPipeline(const GXPipelineUid& config,
std::unique_ptr<AbstractPipeline> pipeline)
{
auto& entry = m_gx_pipeline_cache[config];
@ -592,7 +594,7 @@ const AbstractPipeline* ShaderCache::InsertGXPipeline(const GXPipelineConfig& co
}
const AbstractPipeline*
ShaderCache::InsertGXUberPipeline(const GXUberPipelineConfig& config,
ShaderCache::InsertGXUberPipeline(const GXUberPipelineUid& config,
std::unique_ptr<AbstractPipeline> pipeline)
{
auto& entry = m_gx_uber_pipeline_cache[config];
@ -603,7 +605,7 @@ ShaderCache::InsertGXUberPipeline(const GXUberPipelineConfig& config,
return entry.first.get();
}
void ShaderCache::AppendGXPipelineUID(const GXPipelineConfig& config)
void ShaderCache::AppendGXPipelineUID(const GXPipelineUid& config)
{
// Convert to disk format.
GXPipelineDiskCacheUid disk_uid = {};
@ -633,7 +635,7 @@ void ShaderCache::QueueVertexShaderCompile(const VertexShaderUid& uid)
return true;
}
virtual void Retrieve() override { shader_cache->InsertVertexShader(uid, std::move(shader)); }
void Retrieve() override { shader_cache->InsertVertexShader(uid, std::move(shader)); }
private:
ShaderCache* shader_cache;
std::unique_ptr<AbstractShader> shader;
@ -661,11 +663,7 @@ void ShaderCache::QueueVertexUberShaderCompile(const UberShader::VertexShaderUid
return true;
}
virtual void Retrieve() override
{
shader_cache->InsertVertexUberShader(uid, std::move(shader));
}
void Retrieve() override { shader_cache->InsertVertexUberShader(uid, std::move(shader)); }
private:
ShaderCache* shader_cache;
std::unique_ptr<AbstractShader> shader;
@ -693,7 +691,7 @@ void ShaderCache::QueuePixelShaderCompile(const PixelShaderUid& uid)
return true;
}
virtual void Retrieve() override { shader_cache->InsertPixelShader(uid, std::move(shader)); }
void Retrieve() override { shader_cache->InsertPixelShader(uid, std::move(shader)); }
private:
ShaderCache* shader_cache;
std::unique_ptr<AbstractShader> shader;
@ -721,11 +719,7 @@ void ShaderCache::QueuePixelUberShaderCompile(const UberShader::PixelShaderUid&
return true;
}
virtual void Retrieve() override
{
shader_cache->InsertPixelUberShader(uid, std::move(shader));
}
void Retrieve() override { shader_cache->InsertPixelUberShader(uid, std::move(shader)); }
private:
ShaderCache* shader_cache;
std::unique_ptr<AbstractShader> shader;
@ -737,12 +731,12 @@ void ShaderCache::QueuePixelUberShaderCompile(const UberShader::PixelShaderUid&
m_async_shader_compiler->QueueWorkItem(std::move(wi));
}
void ShaderCache::QueuePipelineCompile(const GXPipelineConfig& uid)
void ShaderCache::QueuePipelineCompile(const GXPipelineUid& uid)
{
class PipelineWorkItem final : public AsyncShaderCompiler::WorkItem
{
public:
PipelineWorkItem(ShaderCache* shader_cache_, const GXPipelineConfig& uid_,
PipelineWorkItem(ShaderCache* shader_cache_, const GXPipelineUid& uid_,
const AbstractPipelineConfig& config_)
: shader_cache(shader_cache_), uid(uid_), config(config_)
{
@ -754,11 +748,11 @@ void ShaderCache::QueuePipelineCompile(const GXPipelineConfig& uid)
return true;
}
virtual void Retrieve() override { shader_cache->InsertGXPipeline(uid, std::move(pipeline)); }
void Retrieve() override { shader_cache->InsertGXPipeline(uid, std::move(pipeline)); }
private:
ShaderCache* shader_cache;
std::unique_ptr<AbstractPipeline> pipeline;
GXPipelineConfig uid;
GXPipelineUid uid;
AbstractPipelineConfig config;
};
@ -775,13 +769,15 @@ void ShaderCache::QueuePipelineCompile(const GXPipelineConfig& uid)
m_gx_pipeline_cache[uid].second = true;
}
void ShaderCache::QueueUberPipelineCompile(const GXUberPipelineConfig& uid)
void ShaderCache::QueueUberPipelineCompile(const GXUberPipelineUid& uid)
{
class UberPipelineWorkItem final : public AsyncShaderCompiler::WorkItem
// Since the shaders may not be compiled at pipelines request time, we do this in two passes.
// This is necessary because we can't access the caches in the worker thread.
class UberPipelineCompilePass final : public AsyncShaderCompiler::WorkItem
{
public:
UberPipelineWorkItem(ShaderCache* shader_cache_, const GXUberPipelineConfig& uid_,
const AbstractPipelineConfig& config_)
UberPipelineCompilePass(ShaderCache* shader_cache_, const GXUberPipelineUid& uid_,
const AbstractPipelineConfig& config_)
: shader_cache(shader_cache_), uid(uid_), config(config_)
{
}
@ -792,27 +788,43 @@ void ShaderCache::QueueUberPipelineCompile(const GXUberPipelineConfig& uid)
return true;
}
virtual void Retrieve() override
void Retrieve() override { shader_cache->InsertGXUberPipeline(uid, std::move(pipeline)); }
private:
ShaderCache* shader_cache;
std::unique_ptr<AbstractPipeline> pipeline;
GXUberPipelineUid uid;
AbstractPipelineConfig config;
};
class UberPipelinePreparePass final : public AsyncShaderCompiler::WorkItem
{
public:
UberPipelinePreparePass(ShaderCache* shader_cache_, const GXUberPipelineUid& uid_)
: shader_cache(shader_cache_), uid(uid_)
{
shader_cache->InsertGXUberPipeline(uid, std::move(pipeline));
}
bool Compile() override { return true; }
void Retrieve() override
{
auto config = shader_cache->GetGXUberPipelineConfig(uid);
if (!config)
{
// One or more stages failed to compile.
shader_cache->InsertGXUberPipeline(uid, nullptr);
return;
}
auto wi = shader_cache->m_async_shader_compiler->CreateWorkItem<UberPipelineCompilePass>(
shader_cache, uid, *config);
shader_cache->m_async_shader_compiler->QueueWorkItem(std::move(wi));
}
private:
ShaderCache* shader_cache;
std::unique_ptr<AbstractPipeline> pipeline;
GXUberPipelineConfig uid;
AbstractPipelineConfig config;
GXUberPipelineUid uid;
};
auto config = GetGXUberPipelineConfig(uid);
if (!config)
{
// One or more stages failed to compile.
InsertGXUberPipeline(uid, nullptr);
return;
}
auto wi = m_async_shader_compiler->CreateWorkItem<UberPipelineWorkItem>(this, uid, *config);
auto wi = m_async_shader_compiler->CreateWorkItem<UberPipelinePreparePass>(this, uid);
m_async_shader_compiler->QueueWorkItem(std::move(wi));
m_gx_uber_pipeline_cache[uid].second = true;
}
@ -841,9 +853,6 @@ void ShaderCache::PrecompileUberShaders()
QueuePixelUberShaderCompile(puid);
});
// Wait for shaders to finish compiling.
WaitForAsyncCompiler(GetStringT("Compiling uber shaders..."));
// Create a dummy vertex format with no attributes.
// All attributes will be enabled in GetUberVertexFormat.
PortableVertexDeclaration dummy_vertex_decl = {};
@ -856,7 +865,7 @@ void ShaderCache::PrecompileUberShaders()
auto QueueDummyPipeline = [&](const UberShader::VertexShaderUid& vs_uid,
const GeometryShaderUid& gs_uid,
const UberShader::PixelShaderUid& ps_uid) {
GXUberPipelineConfig config;
GXUberPipelineUid config;
config.vertex_format = dummy_vertex_format;
config.vs_uid = vs_uid;
config.gs_uid = gs_uid;

View File

@ -19,10 +19,10 @@
#include "VideoCommon/AbstractPipeline.h"
#include "VideoCommon/AbstractShader.h"
#include "VideoCommon/NativeVertexFormat.h"
#include "VideoCommon/AsyncShaderCompiler.h"
#include "VideoCommon/GXPipelineTypes.h"
#include "VideoCommon/GeometryShaderGen.h"
#include "VideoCommon/NativeVertexFormat.h"
#include "VideoCommon/PixelShaderGen.h"
#include "VideoCommon/RenderState.h"
#include "VideoCommon/UberShaderPixel.h"
@ -33,64 +33,6 @@ class NativeVertexFormat;
namespace VideoCommon
{
struct GXPipelineConfig
{
const NativeVertexFormat* vertex_format;
VertexShaderUid vs_uid;
GeometryShaderUid gs_uid;
PixelShaderUid ps_uid;
RasterizationState rasterization_state;
DepthState depth_state;
BlendingState blending_state;
// We use memcmp() for comparing pipelines as std::tie generates a large number of instructions,
// and this map lookup can happen every draw call. However, as using memcmp() will also compare
// any padding bytes, we have to ensure these are zeroed out.
GXPipelineConfig() { std::memset(this, 0, sizeof(*this)); }
GXPipelineConfig(const GXPipelineConfig& rhs) { std::memcpy(this, &rhs, sizeof(*this)); }
GXPipelineConfig& operator=(const GXPipelineConfig& rhs)
{
std::memcpy(this, &rhs, sizeof(*this));
return *this;
}
bool operator<(const GXPipelineConfig& rhs) const
{
return std::memcmp(this, &rhs, sizeof(*this)) < 0;
}
bool operator==(const GXPipelineConfig& rhs) const
{
return std::memcmp(this, &rhs, sizeof(*this)) == 0;
}
bool operator!=(const GXPipelineConfig& rhs) const { return !operator==(rhs); }
};
struct GXUberPipelineConfig
{
const NativeVertexFormat* vertex_format;
UberShader::VertexShaderUid vs_uid;
GeometryShaderUid gs_uid;
UberShader::PixelShaderUid ps_uid;
RasterizationState rasterization_state;
DepthState depth_state;
BlendingState blending_state;
GXUberPipelineConfig() { std::memset(this, 0, sizeof(*this)); }
GXUberPipelineConfig(const GXUberPipelineConfig& rhs) { std::memcpy(this, &rhs, sizeof(*this)); }
GXUberPipelineConfig& operator=(const GXUberPipelineConfig& rhs)
{
std::memcpy(this, &rhs, sizeof(*this));
return *this;
}
bool operator<(const GXUberPipelineConfig& rhs) const
{
return std::memcmp(this, &rhs, sizeof(*this)) < 0;
}
bool operator==(const GXUberPipelineConfig& rhs) const
{
return std::memcmp(this, &rhs, sizeof(*this)) == 0;
}
bool operator!=(const GXUberPipelineConfig& rhs) const { return !operator==(rhs); }
};
class ShaderCache final
{
public:
@ -114,15 +56,15 @@ public:
std::string GetUtilityShaderHeader() const;
// Accesses ShaderGen shader caches
const AbstractPipeline* GetPipelineForUid(const GXPipelineConfig& uid);
const AbstractPipeline* GetUberPipelineForUid(const GXUberPipelineConfig& uid);
const AbstractPipeline* GetPipelineForUid(const GXPipelineUid& uid);
const AbstractPipeline* GetUberPipelineForUid(const GXUberPipelineUid& uid);
// Accesses ShaderGen shader caches asynchronously.
// The optional will be empty if this pipeline is now background compiling.
std::optional<const AbstractPipeline*> GetPipelineForUidAsync(const GXPipelineConfig& uid);
std::optional<const AbstractPipeline*> GetPipelineForUidAsync(const GXPipelineUid& uid);
private:
void WaitForAsyncCompiler(const std::string& msg);
void WaitForAsyncCompiler();
void LoadShaderCaches();
void ClearShaderCaches();
void LoadPipelineUIDCache();
@ -155,21 +97,21 @@ private:
const AbstractShader* geometry_shader, const AbstractShader* pixel_shader,
const RasterizationState& rasterization_state, const DepthState& depth_state,
const BlendingState& blending_state);
std::optional<AbstractPipelineConfig> GetGXPipelineConfig(const GXPipelineConfig& uid);
std::optional<AbstractPipelineConfig> GetGXUberPipelineConfig(const GXUberPipelineConfig& uid);
const AbstractPipeline* InsertGXPipeline(const GXPipelineConfig& config,
std::optional<AbstractPipelineConfig> GetGXPipelineConfig(const GXPipelineUid& uid);
std::optional<AbstractPipelineConfig> GetGXUberPipelineConfig(const GXUberPipelineUid& uid);
const AbstractPipeline* InsertGXPipeline(const GXPipelineUid& config,
std::unique_ptr<AbstractPipeline> pipeline);
const AbstractPipeline* InsertGXUberPipeline(const GXUberPipelineConfig& config,
const AbstractPipeline* InsertGXUberPipeline(const GXUberPipelineUid& config,
std::unique_ptr<AbstractPipeline> pipeline);
void AppendGXPipelineUID(const GXPipelineConfig& config);
void AppendGXPipelineUID(const GXPipelineUid& config);
// ASync Compiler Methods
void QueueVertexShaderCompile(const VertexShaderUid& uid);
void QueueVertexUberShaderCompile(const UberShader::VertexShaderUid& uid);
void QueuePixelShaderCompile(const PixelShaderUid& uid);
void QueuePixelUberShaderCompile(const UberShader::PixelShaderUid& uid);
void QueuePipelineCompile(const GXPipelineConfig& uid);
void QueueUberPipelineCompile(const GXUberPipelineConfig& uid);
void QueuePipelineCompile(const GXPipelineUid& uid);
void QueueUberPipelineCompile(const GXUberPipelineUid& uid);
// Configuration bits.
APIType m_api_type = APIType::Nothing;
@ -196,10 +138,8 @@ private:
ShaderModuleCache<UberShader::PixelShaderUid> m_uber_ps_cache;
// GX Pipeline Caches - .first - pipeline, .second - pending
// TODO: Use unordered_map for speed.
std::map<GXPipelineConfig, std::pair<std::unique_ptr<AbstractPipeline>, bool>>
m_gx_pipeline_cache;
std::map<GXUberPipelineConfig, std::pair<std::unique_ptr<AbstractPipeline>, bool>>
std::map<GXPipelineUid, std::pair<std::unique_ptr<AbstractPipeline>, bool>> m_gx_pipeline_cache;
std::map<GXUberPipelineUid, std::pair<std::unique_ptr<AbstractPipeline>, bool>>
m_gx_uber_pipeline_cache;
// Disk cache of pipeline UIDs

View File

@ -83,8 +83,8 @@ protected:
Slope m_zslope = {};
void CalculateZSlope(NativeVertexFormat* format);
VideoCommon::GXPipelineConfig m_current_pipeline_config;
VideoCommon::GXUberPipelineConfig m_current_uber_pipeline_config;
VideoCommon::GXPipelineUid m_current_pipeline_config;
VideoCommon::GXUberPipelineUid m_current_uber_pipeline_config;
const AbstractPipeline* m_current_pipeline_object = nullptr;
PrimitiveType m_current_primitive_type = PrimitiveType::Points;
bool m_pipeline_config_changed = true;

View File

@ -120,6 +120,7 @@
<ClInclude Include="Fifo.h" />
<ClInclude Include="FPSCounter.h" />
<ClInclude Include="FramebufferManagerBase.h" />
<ClInclude Include="GXPipelineTypes.h" />
<ClInclude Include="ShaderCache.h" />
<ClInclude Include="UberShaderCommon.h" />
<ClInclude Include="UberShaderPixel.h" />
@ -190,4 +191,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -198,7 +198,7 @@
<Filter>Base</Filter>
</ClCompile>
<ClCompile Include="ShaderCache.cpp">
<Filter>Shader Managers</Filter>
<Filter>Shader Generators</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
@ -381,11 +381,14 @@
<ClInclude Include="AbstractFramebuffer.h">
<Filter>Base</Filter>
</ClInclude>
<ClInclude Include="GXPipelineTypes.h">
<Filter>Shader Generators</Filter>
</ClInclude>
<ClInclude Include="ShaderCache.h">
<Filter>Shader Managers</Filter>
<Filter>Shader Generators</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Text Include="CMakeLists.txt" />
</ItemGroup>
</Project>
</Project>

View File

@ -102,6 +102,7 @@ void VideoConfig::Refresh()
bBackendMultithreading = Config::Get(Config::GFX_BACKEND_MULTITHREADING);
iCommandBufferExecuteInterval = Config::Get(Config::GFX_COMMAND_BUFFER_EXECUTE_INTERVAL);
bShaderCache = Config::Get(Config::GFX_SHADER_CACHE);
bWaitForShadersBeforeStarting = Config::Get(Config::GFX_WAIT_FOR_SHADERS_BEFORE_STARTING);
iUberShaderMode = static_cast<UberShaderMode>(Config::Get(Config::GFX_UBERSHADER_MODE));
iShaderCompilerThreads = Config::Get(Config::GFX_SHADER_COMPILER_THREADS);
iShaderPrecompilerThreads = Config::Get(Config::GFX_SHADER_PRECOMPILER_THREADS);
@ -196,6 +197,10 @@ u32 VideoConfig::GetShaderCompilerThreads() const
u32 VideoConfig::GetShaderPrecompilerThreads() const
{
// When using background compilation, always keep the same thread count.
if (bWaitForShadersBeforeStarting)
return GetShaderCompilerThreads();
if (!backend_info.bSupportsBackgroundCompiling)
return 0;

View File

@ -169,6 +169,7 @@ struct VideoConfig final
int iCommandBufferExecuteInterval;
// Shader compilation settings.
bool bWaitForShadersBeforeStarting;
UberShaderMode iUberShaderMode;
// Number of shader compiler threads.