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

@ -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;