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{ const ConfigInfo<int> GFX_COMMAND_BUFFER_EXECUTE_INTERVAL{
{System::GFX, "Settings", "CommandBufferExecuteInterval"}, 100}; {System::GFX, "Settings", "CommandBufferExecuteInterval"}, 100};
const ConfigInfo<bool> GFX_SHADER_CACHE{{System::GFX, "Settings", "ShaderCache"}, true}; 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"}, const ConfigInfo<int> GFX_UBERSHADER_MODE{{System::GFX, "Settings", "UberShaderMode"},
static_cast<int>(UberShaderMode::Disabled)}; static_cast<int>(UberShaderMode::Disabled)};
const ConfigInfo<int> GFX_SHADER_COMPILER_THREADS{ 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<bool> GFX_BACKEND_MULTITHREADING;
extern const ConfigInfo<int> GFX_COMMAND_BUFFER_EXECUTE_INTERVAL; extern const ConfigInfo<int> GFX_COMMAND_BUFFER_EXECUTE_INTERVAL;
extern const ConfigInfo<bool> GFX_SHADER_CACHE; 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_UBERSHADER_MODE;
extern const ConfigInfo<int> GFX_SHADER_COMPILER_THREADS; extern const ConfigInfo<int> GFX_SHADER_COMPILER_THREADS;
extern const ConfigInfo<int> GFX_SHADER_PRECOMPILER_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_DISABLE_FOG.location, Config::GFX_BORDERLESS_FULLSCREEN.location,
Config::GFX_ENABLE_VALIDATION_LAYER.location, Config::GFX_BACKEND_MULTITHREADING.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_COMMAND_BUFFER_EXECUTE_INTERVAL.location, Config::GFX_SHADER_CACHE.location,
Config::GFX_UBERSHADER_MODE.location, Config::GFX_SHADER_COMPILER_THREADS.location, Config::GFX_WAIT_FOR_SHADERS_BEFORE_STARTING.location, Config::GFX_UBERSHADER_MODE.location,
Config::GFX_SHADER_PRECOMPILER_THREADS.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_ZCOMPLOC.location, Config::GFX_SW_ZFREEZE.location,
Config::GFX_SW_DUMP_OBJECTS.location, Config::GFX_SW_DUMP_TEV_STAGES.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_keep_window_top = new QCheckBox(tr("Keep Window on Top"));
m_hide_cursor = new QCheckBox(tr("Hide Mouse Cursor")); m_hide_cursor = new QCheckBox(tr("Hide Mouse Cursor"));
m_render_main_window = new QCheckBox(tr("Render to Main Window")); 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); 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_hide_cursor, 3, 0);
m_options_layout->addWidget(m_render_main_window, 3, 1); 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_video_box);
main_layout->addWidget(m_options_box); main_layout->addWidget(m_options_box);
@ -265,6 +268,12 @@ void GeneralWidget::AddDescriptions()
static const char* TR_SHOW_NETPLAY_MESSAGES_DESCRIPTION = static const char* TR_SHOW_NETPLAY_MESSAGES_DESCRIPTION =
QT_TR_NOOP("When playing on NetPlay, show chat messages, buffer changes and " QT_TR_NOOP("When playing on NetPlay, show chat messages, buffer changes and "
"desync alerts.\n\nIf unsure, leave this unchecked."); "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); AddDescription(m_backend_combo, TR_BACKEND_DESCRIPTION);
#ifdef _WIN32 #ifdef _WIN32
@ -282,6 +291,7 @@ void GeneralWidget::AddDescriptions()
AddDescription(m_show_messages, TR_SHOW_FPS_DESCRIPTION); AddDescription(m_show_messages, TR_SHOW_FPS_DESCRIPTION);
AddDescription(m_keep_window_top, TR_KEEP_WINDOW_ON_TOP_DESCRIPTION); AddDescription(m_keep_window_top, TR_KEEP_WINDOW_ON_TOP_DESCRIPTION);
AddDescription(m_show_messages, TR_SHOW_NETPLAY_MESSAGES_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) void GeneralWidget::OnBackendChanged(const QString& backend_name)
{ {

View File

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

View File

@ -317,6 +317,12 @@ static wxString ubershader_desc =
"stuttering. Balances performance and smoothness.\n\n" "stuttering. Balances performance and smoothness.\n\n"
"Exclusive: Ubershaders will always be used. Only recommended for high-end " "Exclusive: Ubershaders will always be used. Only recommended for high-end "
"systems."); "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) VideoConfigDiag::VideoConfigDiag(wxWindow* parent, const std::string& title)
: wxDialog(parent, wxID_ANY, wxString::Format(_("Dolphin %s Graphics Configuration"), : 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), wxGetTranslation(backend_multithreading_desc),
Config::GFX_BACKEND_MULTITHREADING)); 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 = wxStaticBoxSizer* const group_basic =

View File

@ -18,7 +18,6 @@ AsyncShaderCompiler::~AsyncShaderCompiler()
// Pending work can be left at shutdown. // Pending work can be left at shutdown.
// The work item classes are expected to clean up after themselves. // The work item classes are expected to clean up after themselves.
_assert_(!HasWorkerThreads()); _assert_(!HasWorkerThreads());
_assert_(m_completed_work.empty());
} }
void AsyncShaderCompiler::QueueWorkItem(WorkItemPtr item) 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. // Compile all known UIDs.
CompileMissingPipelines(); CompileMissingPipelines();
if (g_ActiveConfig.bWaitForShadersBeforeStarting)
WaitForAsyncCompiler();
// Switch to the runtime shader compiler thread configuration. // Switch to the runtime shader compiler thread configuration.
m_async_shader_compiler->ResizeWorkerThreads(g_ActiveConfig.GetShaderCompilerThreads()); 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() void ShaderCache::Reload()
{ {
m_async_shader_compiler->WaitUntilCompletion(); WaitForAsyncCompiler();
m_async_shader_compiler->RetrieveWorkItems();
InvalidateCachedPipelines(); InvalidateCachedPipelines();
ClearShaderCaches(); ClearShaderCaches();
@ -77,6 +77,8 @@ void ShaderCache::Reload()
// UIDs are still be in the map. Therefore, when these are rebuilt, the shaders will also // UIDs are still be in the map. Therefore, when these are rebuilt, the shaders will also
// be recompiled. // be recompiled.
CompileMissingPipelines(); CompileMissingPipelines();
if (g_ActiveConfig.bWaitForShadersBeforeStarting)
WaitForAsyncCompiler();
m_async_shader_compiler->ResizeWorkerThreads(g_ActiveConfig.GetShaderCompilerThreads()); m_async_shader_compiler->ResizeWorkerThreads(g_ActiveConfig.GetShaderCompilerThreads());
} }
@ -87,14 +89,14 @@ void ShaderCache::RetrieveAsyncShaders()
void ShaderCache::Shutdown() 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->StopWorkerThreads();
m_async_shader_compiler->RetrieveWorkItems();
ClearShaderCaches(); ClearShaderCaches();
ClearPipelineCaches(); ClearPipelineCaches();
} }
const AbstractPipeline* ShaderCache::GetPipelineForUid(const GXPipelineConfig& uid) const AbstractPipeline* ShaderCache::GetPipelineForUid(const GXPipelineUid& uid)
{ {
auto it = m_gx_pipeline_cache.find(uid); auto it = m_gx_pipeline_cache.find(uid);
if (it != m_gx_pipeline_cache.end() && !it->second.second) 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)); return InsertGXPipeline(uid, std::move(pipeline));
} }
std::optional<const AbstractPipeline*> std::optional<const AbstractPipeline*> ShaderCache::GetPipelineForUidAsync(const GXPipelineUid& uid)
ShaderCache::GetPipelineForUidAsync(const GXPipelineConfig& uid)
{ {
auto it = m_gx_pipeline_cache.find(uid); auto it = m_gx_pipeline_cache.find(uid);
if (it != m_gx_pipeline_cache.end()) if (it != m_gx_pipeline_cache.end())
@ -159,7 +160,7 @@ ShaderCache::GetPipelineForUidAsync(const GXPipelineConfig& uid)
return {}; return {};
} }
const AbstractPipeline* ShaderCache::GetUberPipelineForUid(const GXUberPipelineConfig& uid) const AbstractPipeline* ShaderCache::GetUberPipelineForUid(const GXUberPipelineUid& uid)
{ {
auto it = m_gx_uber_pipeline_cache.find(uid); auto it = m_gx_uber_pipeline_cache.find(uid);
if (it != m_gx_uber_pipeline_cache.end() && !it->second.second) 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)); 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) { while (m_async_shader_compiler->HasPendingWork())
Host_UpdateProgressDialog(msg.c_str(), static_cast<int>(completed), static_cast<int>(total)); {
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(); m_async_shader_compiler->RetrieveWorkItems();
}
Host_UpdateProgressDialog("", -1, -1); Host_UpdateProgressDialog("", -1, -1);
} }
@ -274,7 +279,7 @@ void ShaderCache::LoadPipelineUIDCache()
CacheReader(ShaderCache* shader_cache_) : shader_cache(shader_cache_) {} CacheReader(ShaderCache* shader_cache_) : shader_cache(shader_cache_) {}
void Read(const GXPipelineDiskCacheUid& key, const u8* data, u32 data_size) void Read(const GXPipelineDiskCacheUid& key, const u8* data, u32 data_size)
{ {
GXPipelineConfig config = {}; GXPipelineUid config = {};
config.vertex_format = VertexLoaderManager::GetOrCreateMatchingFormat(key.vertex_decl); config.vertex_format = VertexLoaderManager::GetOrCreateMatchingFormat(key.vertex_decl);
config.vs_uid = key.vs_uid; config.vs_uid = key.vs_uid;
config.gs_uid = key.gs_uid; config.gs_uid = key.gs_uid;
@ -315,8 +320,6 @@ void ShaderCache::CompileMissingPipelines()
if (!it.second.second) if (!it.second.second)
QueueUberPipelineCompile(it.first); QueueUberPipelineCompile(it.first);
} }
WaitForAsyncCompiler(GetStringT("Compiling shaders..."));
} }
void ShaderCache::InvalidateCachedPipelines() void ShaderCache::InvalidateCachedPipelines()
@ -508,8 +511,7 @@ AbstractPipelineConfig ShaderCache::GetGXPipelineConfig(
return config; return config;
} }
std::optional<AbstractPipelineConfig> std::optional<AbstractPipelineConfig> ShaderCache::GetGXPipelineConfig(const GXPipelineUid& config)
ShaderCache::GetGXPipelineConfig(const GXPipelineConfig& config)
{ {
const AbstractShader* vs; const AbstractShader* vs;
auto vs_iter = m_vs_cache.shader_map.find(config.vs_uid); auto vs_iter = m_vs_cache.shader_map.find(config.vs_uid);
@ -545,7 +547,7 @@ ShaderCache::GetGXPipelineConfig(const GXPipelineConfig& config)
} }
std::optional<AbstractPipelineConfig> std::optional<AbstractPipelineConfig>
ShaderCache::GetGXUberPipelineConfig(const GXUberPipelineConfig& config) ShaderCache::GetGXUberPipelineConfig(const GXUberPipelineUid& config)
{ {
const AbstractShader* vs; const AbstractShader* vs;
auto vs_iter = m_uber_vs_cache.shader_map.find(config.vs_uid); 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); 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) std::unique_ptr<AbstractPipeline> pipeline)
{ {
auto& entry = m_gx_pipeline_cache[config]; auto& entry = m_gx_pipeline_cache[config];
@ -592,7 +594,7 @@ const AbstractPipeline* ShaderCache::InsertGXPipeline(const GXPipelineConfig& co
} }
const AbstractPipeline* const AbstractPipeline*
ShaderCache::InsertGXUberPipeline(const GXUberPipelineConfig& config, ShaderCache::InsertGXUberPipeline(const GXUberPipelineUid& config,
std::unique_ptr<AbstractPipeline> pipeline) std::unique_ptr<AbstractPipeline> pipeline)
{ {
auto& entry = m_gx_uber_pipeline_cache[config]; auto& entry = m_gx_uber_pipeline_cache[config];
@ -603,7 +605,7 @@ ShaderCache::InsertGXUberPipeline(const GXUberPipelineConfig& config,
return entry.first.get(); return entry.first.get();
} }
void ShaderCache::AppendGXPipelineUID(const GXPipelineConfig& config) void ShaderCache::AppendGXPipelineUID(const GXPipelineUid& config)
{ {
// Convert to disk format. // Convert to disk format.
GXPipelineDiskCacheUid disk_uid = {}; GXPipelineDiskCacheUid disk_uid = {};
@ -633,7 +635,7 @@ void ShaderCache::QueueVertexShaderCompile(const VertexShaderUid& uid)
return true; return true;
} }
virtual void Retrieve() override { shader_cache->InsertVertexShader(uid, std::move(shader)); } void Retrieve() override { shader_cache->InsertVertexShader(uid, std::move(shader)); }
private: private:
ShaderCache* shader_cache; ShaderCache* shader_cache;
std::unique_ptr<AbstractShader> shader; std::unique_ptr<AbstractShader> shader;
@ -661,11 +663,7 @@ void ShaderCache::QueueVertexUberShaderCompile(const UberShader::VertexShaderUid
return true; return true;
} }
virtual void Retrieve() override void Retrieve() override { shader_cache->InsertVertexUberShader(uid, std::move(shader)); }
{
shader_cache->InsertVertexUberShader(uid, std::move(shader));
}
private: private:
ShaderCache* shader_cache; ShaderCache* shader_cache;
std::unique_ptr<AbstractShader> shader; std::unique_ptr<AbstractShader> shader;
@ -693,7 +691,7 @@ void ShaderCache::QueuePixelShaderCompile(const PixelShaderUid& uid)
return true; return true;
} }
virtual void Retrieve() override { shader_cache->InsertPixelShader(uid, std::move(shader)); } void Retrieve() override { shader_cache->InsertPixelShader(uid, std::move(shader)); }
private: private:
ShaderCache* shader_cache; ShaderCache* shader_cache;
std::unique_ptr<AbstractShader> shader; std::unique_ptr<AbstractShader> shader;
@ -721,11 +719,7 @@ void ShaderCache::QueuePixelUberShaderCompile(const UberShader::PixelShaderUid&
return true; return true;
} }
virtual void Retrieve() override void Retrieve() override { shader_cache->InsertPixelUberShader(uid, std::move(shader)); }
{
shader_cache->InsertPixelUberShader(uid, std::move(shader));
}
private: private:
ShaderCache* shader_cache; ShaderCache* shader_cache;
std::unique_ptr<AbstractShader> shader; std::unique_ptr<AbstractShader> shader;
@ -737,12 +731,12 @@ void ShaderCache::QueuePixelUberShaderCompile(const UberShader::PixelShaderUid&
m_async_shader_compiler->QueueWorkItem(std::move(wi)); 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 class PipelineWorkItem final : public AsyncShaderCompiler::WorkItem
{ {
public: public:
PipelineWorkItem(ShaderCache* shader_cache_, const GXPipelineConfig& uid_, PipelineWorkItem(ShaderCache* shader_cache_, const GXPipelineUid& uid_,
const AbstractPipelineConfig& config_) const AbstractPipelineConfig& config_)
: shader_cache(shader_cache_), uid(uid_), config(config_) : shader_cache(shader_cache_), uid(uid_), config(config_)
{ {
@ -754,11 +748,11 @@ void ShaderCache::QueuePipelineCompile(const GXPipelineConfig& uid)
return true; return true;
} }
virtual void Retrieve() override { shader_cache->InsertGXPipeline(uid, std::move(pipeline)); } void Retrieve() override { shader_cache->InsertGXPipeline(uid, std::move(pipeline)); }
private: private:
ShaderCache* shader_cache; ShaderCache* shader_cache;
std::unique_ptr<AbstractPipeline> pipeline; std::unique_ptr<AbstractPipeline> pipeline;
GXPipelineConfig uid; GXPipelineUid uid;
AbstractPipelineConfig config; AbstractPipelineConfig config;
}; };
@ -775,12 +769,14 @@ void ShaderCache::QueuePipelineCompile(const GXPipelineConfig& uid)
m_gx_pipeline_cache[uid].second = true; 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: public:
UberPipelineWorkItem(ShaderCache* shader_cache_, const GXUberPipelineConfig& uid_, UberPipelineCompilePass(ShaderCache* shader_cache_, const GXUberPipelineUid& uid_,
const AbstractPipelineConfig& config_) const AbstractPipelineConfig& config_)
: shader_cache(shader_cache_), uid(uid_), config(config_) : shader_cache(shader_cache_), uid(uid_), config(config_)
{ {
@ -792,27 +788,43 @@ void ShaderCache::QueueUberPipelineCompile(const GXUberPipelineConfig& uid)
return true; 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
{ {
shader_cache->InsertGXUberPipeline(uid, std::move(pipeline)); public:
UberPipelinePreparePass(ShaderCache* shader_cache_, const GXUberPipelineUid& uid_)
: shader_cache(shader_cache_), uid(uid_)
{
}
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: private:
ShaderCache* shader_cache; ShaderCache* shader_cache;
std::unique_ptr<AbstractPipeline> pipeline; GXUberPipelineUid uid;
GXUberPipelineConfig uid;
AbstractPipelineConfig config;
}; };
auto config = GetGXUberPipelineConfig(uid); auto wi = m_async_shader_compiler->CreateWorkItem<UberPipelinePreparePass>(this, 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);
m_async_shader_compiler->QueueWorkItem(std::move(wi)); m_async_shader_compiler->QueueWorkItem(std::move(wi));
m_gx_uber_pipeline_cache[uid].second = true; m_gx_uber_pipeline_cache[uid].second = true;
} }
@ -841,9 +853,6 @@ void ShaderCache::PrecompileUberShaders()
QueuePixelUberShaderCompile(puid); QueuePixelUberShaderCompile(puid);
}); });
// Wait for shaders to finish compiling.
WaitForAsyncCompiler(GetStringT("Compiling uber shaders..."));
// Create a dummy vertex format with no attributes. // Create a dummy vertex format with no attributes.
// All attributes will be enabled in GetUberVertexFormat. // All attributes will be enabled in GetUberVertexFormat.
PortableVertexDeclaration dummy_vertex_decl = {}; PortableVertexDeclaration dummy_vertex_decl = {};
@ -856,7 +865,7 @@ void ShaderCache::PrecompileUberShaders()
auto QueueDummyPipeline = [&](const UberShader::VertexShaderUid& vs_uid, auto QueueDummyPipeline = [&](const UberShader::VertexShaderUid& vs_uid,
const GeometryShaderUid& gs_uid, const GeometryShaderUid& gs_uid,
const UberShader::PixelShaderUid& ps_uid) { const UberShader::PixelShaderUid& ps_uid) {
GXUberPipelineConfig config; GXUberPipelineUid config;
config.vertex_format = dummy_vertex_format; config.vertex_format = dummy_vertex_format;
config.vs_uid = vs_uid; config.vs_uid = vs_uid;
config.gs_uid = gs_uid; config.gs_uid = gs_uid;

View File

@ -19,10 +19,10 @@
#include "VideoCommon/AbstractPipeline.h" #include "VideoCommon/AbstractPipeline.h"
#include "VideoCommon/AbstractShader.h" #include "VideoCommon/AbstractShader.h"
#include "VideoCommon/NativeVertexFormat.h"
#include "VideoCommon/AsyncShaderCompiler.h" #include "VideoCommon/AsyncShaderCompiler.h"
#include "VideoCommon/GXPipelineTypes.h"
#include "VideoCommon/GeometryShaderGen.h" #include "VideoCommon/GeometryShaderGen.h"
#include "VideoCommon/NativeVertexFormat.h"
#include "VideoCommon/PixelShaderGen.h" #include "VideoCommon/PixelShaderGen.h"
#include "VideoCommon/RenderState.h" #include "VideoCommon/RenderState.h"
#include "VideoCommon/UberShaderPixel.h" #include "VideoCommon/UberShaderPixel.h"
@ -33,64 +33,6 @@ class NativeVertexFormat;
namespace VideoCommon 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 class ShaderCache final
{ {
public: public:
@ -114,15 +56,15 @@ public:
std::string GetUtilityShaderHeader() const; std::string GetUtilityShaderHeader() const;
// Accesses ShaderGen shader caches // Accesses ShaderGen shader caches
const AbstractPipeline* GetPipelineForUid(const GXPipelineConfig& uid); const AbstractPipeline* GetPipelineForUid(const GXPipelineUid& uid);
const AbstractPipeline* GetUberPipelineForUid(const GXUberPipelineConfig& uid); const AbstractPipeline* GetUberPipelineForUid(const GXUberPipelineUid& uid);
// Accesses ShaderGen shader caches asynchronously. // Accesses ShaderGen shader caches asynchronously.
// The optional will be empty if this pipeline is now background compiling. // 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: private:
void WaitForAsyncCompiler(const std::string& msg); void WaitForAsyncCompiler();
void LoadShaderCaches(); void LoadShaderCaches();
void ClearShaderCaches(); void ClearShaderCaches();
void LoadPipelineUIDCache(); void LoadPipelineUIDCache();
@ -155,21 +97,21 @@ private:
const AbstractShader* geometry_shader, const AbstractShader* pixel_shader, const AbstractShader* geometry_shader, const AbstractShader* pixel_shader,
const RasterizationState& rasterization_state, const DepthState& depth_state, const RasterizationState& rasterization_state, const DepthState& depth_state,
const BlendingState& blending_state); const BlendingState& blending_state);
std::optional<AbstractPipelineConfig> GetGXPipelineConfig(const GXPipelineConfig& uid); std::optional<AbstractPipelineConfig> GetGXPipelineConfig(const GXPipelineUid& uid);
std::optional<AbstractPipelineConfig> GetGXUberPipelineConfig(const GXUberPipelineConfig& uid); std::optional<AbstractPipelineConfig> GetGXUberPipelineConfig(const GXUberPipelineUid& uid);
const AbstractPipeline* InsertGXPipeline(const GXPipelineConfig& config, const AbstractPipeline* InsertGXPipeline(const GXPipelineUid& config,
std::unique_ptr<AbstractPipeline> pipeline); std::unique_ptr<AbstractPipeline> pipeline);
const AbstractPipeline* InsertGXUberPipeline(const GXUberPipelineConfig& config, const AbstractPipeline* InsertGXUberPipeline(const GXUberPipelineUid& config,
std::unique_ptr<AbstractPipeline> pipeline); std::unique_ptr<AbstractPipeline> pipeline);
void AppendGXPipelineUID(const GXPipelineConfig& config); void AppendGXPipelineUID(const GXPipelineUid& config);
// ASync Compiler Methods // ASync Compiler Methods
void QueueVertexShaderCompile(const VertexShaderUid& uid); void QueueVertexShaderCompile(const VertexShaderUid& uid);
void QueueVertexUberShaderCompile(const UberShader::VertexShaderUid& uid); void QueueVertexUberShaderCompile(const UberShader::VertexShaderUid& uid);
void QueuePixelShaderCompile(const PixelShaderUid& uid); void QueuePixelShaderCompile(const PixelShaderUid& uid);
void QueuePixelUberShaderCompile(const UberShader::PixelShaderUid& uid); void QueuePixelUberShaderCompile(const UberShader::PixelShaderUid& uid);
void QueuePipelineCompile(const GXPipelineConfig& uid); void QueuePipelineCompile(const GXPipelineUid& uid);
void QueueUberPipelineCompile(const GXUberPipelineConfig& uid); void QueueUberPipelineCompile(const GXUberPipelineUid& uid);
// Configuration bits. // Configuration bits.
APIType m_api_type = APIType::Nothing; APIType m_api_type = APIType::Nothing;
@ -196,10 +138,8 @@ private:
ShaderModuleCache<UberShader::PixelShaderUid> m_uber_ps_cache; ShaderModuleCache<UberShader::PixelShaderUid> m_uber_ps_cache;
// GX Pipeline Caches - .first - pipeline, .second - pending // GX Pipeline Caches - .first - pipeline, .second - pending
// TODO: Use unordered_map for speed. std::map<GXPipelineUid, std::pair<std::unique_ptr<AbstractPipeline>, bool>> m_gx_pipeline_cache;
std::map<GXPipelineConfig, std::pair<std::unique_ptr<AbstractPipeline>, bool>> std::map<GXUberPipelineUid, std::pair<std::unique_ptr<AbstractPipeline>, bool>>
m_gx_pipeline_cache;
std::map<GXUberPipelineConfig, std::pair<std::unique_ptr<AbstractPipeline>, bool>>
m_gx_uber_pipeline_cache; m_gx_uber_pipeline_cache;
// Disk cache of pipeline UIDs // Disk cache of pipeline UIDs

View File

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

View File

@ -120,6 +120,7 @@
<ClInclude Include="Fifo.h" /> <ClInclude Include="Fifo.h" />
<ClInclude Include="FPSCounter.h" /> <ClInclude Include="FPSCounter.h" />
<ClInclude Include="FramebufferManagerBase.h" /> <ClInclude Include="FramebufferManagerBase.h" />
<ClInclude Include="GXPipelineTypes.h" />
<ClInclude Include="ShaderCache.h" /> <ClInclude Include="ShaderCache.h" />
<ClInclude Include="UberShaderCommon.h" /> <ClInclude Include="UberShaderCommon.h" />
<ClInclude Include="UberShaderPixel.h" /> <ClInclude Include="UberShaderPixel.h" />

View File

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

View File

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

View File

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