Merge pull request #6442 from stenzek/async-compiler-priority

ShaderCache: Implement compile priority
This commit is contained in:
Markus Wick 2018-03-19 09:16:53 +01:00 committed by GitHub
commit 98b4716902
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 60 additions and 41 deletions

View File

@ -20,7 +20,7 @@ AsyncShaderCompiler::~AsyncShaderCompiler()
ASSERT(!HasWorkerThreads()); ASSERT(!HasWorkerThreads());
} }
void AsyncShaderCompiler::QueueWorkItem(WorkItemPtr item) void AsyncShaderCompiler::QueueWorkItem(WorkItemPtr item, u32 priority)
{ {
// If no worker threads are available, compile synchronously. // If no worker threads are available, compile synchronously.
if (!HasWorkerThreads()) if (!HasWorkerThreads())
@ -31,7 +31,7 @@ void AsyncShaderCompiler::QueueWorkItem(WorkItemPtr item)
else else
{ {
std::lock_guard<std::mutex> guard(m_pending_work_lock); std::lock_guard<std::mutex> guard(m_pending_work_lock);
m_pending_work.push_back(std::move(item)); m_pending_work.emplace(priority, std::move(item));
m_worker_thread_wake.notify_one(); m_worker_thread_wake.notify_one();
} }
} }
@ -219,8 +219,9 @@ void AsyncShaderCompiler::WorkerThreadRun()
while (!m_pending_work.empty() && !m_exit_flag.IsSet()) while (!m_pending_work.empty() && !m_exit_flag.IsSet())
{ {
m_busy_workers++; m_busy_workers++;
WorkItemPtr item(std::move(m_pending_work.front())); auto iter = m_pending_work.begin();
m_pending_work.pop_front(); WorkItemPtr item(std::move(iter->second));
m_pending_work.erase(iter);
pending_lock.unlock(); pending_lock.unlock();
if (item->Compile()) if (item->Compile())

View File

@ -8,6 +8,7 @@
#include <condition_variable> #include <condition_variable>
#include <deque> #include <deque>
#include <functional> #include <functional>
#include <map>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <thread> #include <thread>
@ -42,7 +43,9 @@ public:
return std::make_unique<T>(std::forward<Params>(params)...); return std::make_unique<T>(std::forward<Params>(params)...);
} }
void QueueWorkItem(WorkItemPtr item); // Queues a new work item to the compiler threads. The lower the priority, the sooner
// this work item will be compiled, relative to the other work items.
void QueueWorkItem(WorkItemPtr item, u32 priority);
void RetrieveWorkItems(); void RetrieveWorkItems();
bool HasPendingWork(); bool HasPendingWork();
bool HasCompletedWork(); bool HasCompletedWork();
@ -74,7 +77,9 @@ private:
std::vector<std::thread> m_worker_threads; std::vector<std::thread> m_worker_threads;
std::atomic_bool m_worker_thread_start_result{false}; std::atomic_bool m_worker_thread_start_result{false};
std::deque<WorkItemPtr> m_pending_work; // A multimap is used to store the work items. We can't use a priority_queue here, because
// there's no way to obtain a non-const reference, which we need for the unique_ptr.
std::multimap<u32, WorkItemPtr> m_pending_work;
std::mutex m_pending_work_lock; std::mutex m_pending_work_lock;
std::condition_variable m_worker_thread_wake; std::condition_variable m_worker_thread_wake;
std::atomic_size_t m_busy_workers{0}; std::atomic_size_t m_busy_workers{0};

View File

@ -129,7 +129,7 @@ std::optional<const AbstractPipeline*> ShaderCache::GetPipelineForUidAsync(const
} }
AppendGXPipelineUID(uid); AppendGXPipelineUID(uid);
QueuePipelineCompile(uid); QueuePipelineCompile(uid, COMPILE_PRIORITY_ONDEMAND_PIPELINE);
return {}; return {};
} }
@ -249,12 +249,12 @@ void ShaderCache::CompileMissingPipelines()
for (auto& it : m_gx_pipeline_cache) for (auto& it : m_gx_pipeline_cache)
{ {
if (!it.second.second) if (!it.second.second)
QueuePipelineCompile(it.first); QueuePipelineCompile(it.first, COMPILE_PRIORITY_SHADERCACHE_PIPELINE);
} }
for (auto& it : m_gx_uber_pipeline_cache) for (auto& it : m_gx_uber_pipeline_cache)
{ {
if (!it.second.second) if (!it.second.second)
QueueUberPipelineCompile(it.first); QueueUberPipelineCompile(it.first, COMPILE_PRIORITY_UBERSHADER_PIPELINE);
} }
} }
@ -655,7 +655,7 @@ void ShaderCache::AppendGXPipelineUID(const GXPipelineUid& config)
} }
} }
void ShaderCache::QueueVertexShaderCompile(const VertexShaderUid& uid) void ShaderCache::QueueVertexShaderCompile(const VertexShaderUid& uid, u32 priority)
{ {
class VertexShaderWorkItem final : public AsyncShaderCompiler::WorkItem class VertexShaderWorkItem final : public AsyncShaderCompiler::WorkItem
{ {
@ -680,10 +680,10 @@ void ShaderCache::QueueVertexShaderCompile(const VertexShaderUid& uid)
m_vs_cache.shader_map[uid].pending = true; m_vs_cache.shader_map[uid].pending = true;
auto wi = m_async_shader_compiler->CreateWorkItem<VertexShaderWorkItem>(this, uid); auto wi = m_async_shader_compiler->CreateWorkItem<VertexShaderWorkItem>(this, uid);
m_async_shader_compiler->QueueWorkItem(std::move(wi)); m_async_shader_compiler->QueueWorkItem(std::move(wi), priority);
} }
void ShaderCache::QueueVertexUberShaderCompile(const UberShader::VertexShaderUid& uid) void ShaderCache::QueueVertexUberShaderCompile(const UberShader::VertexShaderUid& uid, u32 priority)
{ {
class VertexUberShaderWorkItem final : public AsyncShaderCompiler::WorkItem class VertexUberShaderWorkItem final : public AsyncShaderCompiler::WorkItem
{ {
@ -708,10 +708,10 @@ void ShaderCache::QueueVertexUberShaderCompile(const UberShader::VertexShaderUid
m_uber_vs_cache.shader_map[uid].pending = true; m_uber_vs_cache.shader_map[uid].pending = true;
auto wi = m_async_shader_compiler->CreateWorkItem<VertexUberShaderWorkItem>(this, uid); auto wi = m_async_shader_compiler->CreateWorkItem<VertexUberShaderWorkItem>(this, uid);
m_async_shader_compiler->QueueWorkItem(std::move(wi)); m_async_shader_compiler->QueueWorkItem(std::move(wi), priority);
} }
void ShaderCache::QueuePixelShaderCompile(const PixelShaderUid& uid) void ShaderCache::QueuePixelShaderCompile(const PixelShaderUid& uid, u32 priority)
{ {
class PixelShaderWorkItem final : public AsyncShaderCompiler::WorkItem class PixelShaderWorkItem final : public AsyncShaderCompiler::WorkItem
{ {
@ -736,10 +736,10 @@ void ShaderCache::QueuePixelShaderCompile(const PixelShaderUid& uid)
m_ps_cache.shader_map[uid].pending = true; m_ps_cache.shader_map[uid].pending = true;
auto wi = m_async_shader_compiler->CreateWorkItem<PixelShaderWorkItem>(this, uid); auto wi = m_async_shader_compiler->CreateWorkItem<PixelShaderWorkItem>(this, uid);
m_async_shader_compiler->QueueWorkItem(std::move(wi)); m_async_shader_compiler->QueueWorkItem(std::move(wi), priority);
} }
void ShaderCache::QueuePixelUberShaderCompile(const UberShader::PixelShaderUid& uid) void ShaderCache::QueuePixelUberShaderCompile(const UberShader::PixelShaderUid& uid, u32 priority)
{ {
class PixelUberShaderWorkItem final : public AsyncShaderCompiler::WorkItem class PixelUberShaderWorkItem final : public AsyncShaderCompiler::WorkItem
{ {
@ -764,16 +764,16 @@ void ShaderCache::QueuePixelUberShaderCompile(const UberShader::PixelShaderUid&
m_uber_ps_cache.shader_map[uid].pending = true; m_uber_ps_cache.shader_map[uid].pending = true;
auto wi = m_async_shader_compiler->CreateWorkItem<PixelUberShaderWorkItem>(this, uid); auto wi = m_async_shader_compiler->CreateWorkItem<PixelUberShaderWorkItem>(this, uid);
m_async_shader_compiler->QueueWorkItem(std::move(wi)); m_async_shader_compiler->QueueWorkItem(std::move(wi), priority);
} }
void ShaderCache::QueuePipelineCompile(const GXPipelineUid& uid) void ShaderCache::QueuePipelineCompile(const GXPipelineUid& uid, u32 priority)
{ {
class PipelineWorkItem final : public AsyncShaderCompiler::WorkItem class PipelineWorkItem final : public AsyncShaderCompiler::WorkItem
{ {
public: public:
PipelineWorkItem(ShaderCache* shader_cache_, const GXPipelineUid& uid_) PipelineWorkItem(ShaderCache* shader_cache_, const GXPipelineUid& uid_, u32 priority_)
: shader_cache(shader_cache_), uid(uid_) : shader_cache(shader_cache_), uid(uid_), priority(priority_)
{ {
// Check if all the stages required for this pipeline have been compiled. // Check if all the stages required for this pipeline have been compiled.
// If not, this work item becomes a no-op, and re-queues the pipeline for the next frame. // If not, this work item becomes a no-op, and re-queues the pipeline for the next frame.
@ -788,12 +788,12 @@ void ShaderCache::QueuePipelineCompile(const GXPipelineUid& uid)
auto vs_it = shader_cache->m_vs_cache.shader_map.find(uid.vs_uid); auto vs_it = shader_cache->m_vs_cache.shader_map.find(uid.vs_uid);
stages_ready &= vs_it != shader_cache->m_vs_cache.shader_map.end() && !vs_it->second.pending; stages_ready &= vs_it != shader_cache->m_vs_cache.shader_map.end() && !vs_it->second.pending;
if (vs_it == shader_cache->m_vs_cache.shader_map.end()) if (vs_it == shader_cache->m_vs_cache.shader_map.end())
shader_cache->QueueVertexShaderCompile(uid.vs_uid); shader_cache->QueueVertexShaderCompile(uid.vs_uid, priority);
auto ps_it = shader_cache->m_ps_cache.shader_map.find(uid.ps_uid); auto ps_it = shader_cache->m_ps_cache.shader_map.find(uid.ps_uid);
stages_ready &= ps_it != shader_cache->m_ps_cache.shader_map.end() && !ps_it->second.pending; stages_ready &= ps_it != shader_cache->m_ps_cache.shader_map.end() && !ps_it->second.pending;
if (ps_it == shader_cache->m_ps_cache.shader_map.end()) if (ps_it == shader_cache->m_ps_cache.shader_map.end())
shader_cache->QueuePixelShaderCompile(uid.ps_uid); shader_cache->QueuePixelShaderCompile(uid.ps_uid, priority);
return stages_ready; return stages_ready;
} }
@ -815,8 +815,8 @@ void ShaderCache::QueuePipelineCompile(const GXPipelineUid& uid)
{ {
// Re-queue for next frame. // Re-queue for next frame.
auto wi = shader_cache->m_async_shader_compiler->CreateWorkItem<PipelineWorkItem>( auto wi = shader_cache->m_async_shader_compiler->CreateWorkItem<PipelineWorkItem>(
shader_cache, uid); shader_cache, uid, priority);
shader_cache->m_async_shader_compiler->QueueWorkItem(std::move(wi)); shader_cache->m_async_shader_compiler->QueueWorkItem(std::move(wi), priority);
} }
} }
@ -824,22 +824,23 @@ void ShaderCache::QueuePipelineCompile(const GXPipelineUid& uid)
ShaderCache* shader_cache; ShaderCache* shader_cache;
std::unique_ptr<AbstractPipeline> pipeline; std::unique_ptr<AbstractPipeline> pipeline;
GXPipelineUid uid; GXPipelineUid uid;
u32 priority;
std::optional<AbstractPipelineConfig> config; std::optional<AbstractPipelineConfig> config;
bool stages_ready; bool stages_ready;
}; };
auto wi = m_async_shader_compiler->CreateWorkItem<PipelineWorkItem>(this, uid); auto wi = m_async_shader_compiler->CreateWorkItem<PipelineWorkItem>(this, uid, priority);
m_async_shader_compiler->QueueWorkItem(std::move(wi)); m_async_shader_compiler->QueueWorkItem(std::move(wi), priority);
m_gx_pipeline_cache[uid].second = true; m_gx_pipeline_cache[uid].second = true;
} }
void ShaderCache::QueueUberPipelineCompile(const GXUberPipelineUid& uid) void ShaderCache::QueueUberPipelineCompile(const GXUberPipelineUid& uid, u32 priority)
{ {
class UberPipelineWorkItem final : public AsyncShaderCompiler::WorkItem class UberPipelineWorkItem final : public AsyncShaderCompiler::WorkItem
{ {
public: public:
UberPipelineWorkItem(ShaderCache* shader_cache_, const GXUberPipelineUid& uid_) UberPipelineWorkItem(ShaderCache* shader_cache_, const GXUberPipelineUid& uid_, u32 priority_)
: shader_cache(shader_cache_), uid(uid_) : shader_cache(shader_cache_), uid(uid_), priority(priority_)
{ {
// Check if all the stages required for this UberPipeline have been compiled. // Check if all the stages required for this UberPipeline have been compiled.
// If not, this work item becomes a no-op, and re-queues the UberPipeline for the next frame. // If not, this work item becomes a no-op, and re-queues the UberPipeline for the next frame.
@ -855,13 +856,13 @@ void ShaderCache::QueueUberPipelineCompile(const GXUberPipelineUid& uid)
stages_ready &= stages_ready &=
vs_it != shader_cache->m_uber_vs_cache.shader_map.end() && !vs_it->second.pending; vs_it != shader_cache->m_uber_vs_cache.shader_map.end() && !vs_it->second.pending;
if (vs_it == shader_cache->m_uber_vs_cache.shader_map.end()) if (vs_it == shader_cache->m_uber_vs_cache.shader_map.end())
shader_cache->QueueVertexUberShaderCompile(uid.vs_uid); shader_cache->QueueVertexUberShaderCompile(uid.vs_uid, priority);
auto ps_it = shader_cache->m_uber_ps_cache.shader_map.find(uid.ps_uid); auto ps_it = shader_cache->m_uber_ps_cache.shader_map.find(uid.ps_uid);
stages_ready &= stages_ready &=
ps_it != shader_cache->m_uber_ps_cache.shader_map.end() && !ps_it->second.pending; ps_it != shader_cache->m_uber_ps_cache.shader_map.end() && !ps_it->second.pending;
if (ps_it == shader_cache->m_uber_ps_cache.shader_map.end()) if (ps_it == shader_cache->m_uber_ps_cache.shader_map.end())
shader_cache->QueuePixelUberShaderCompile(uid.ps_uid); shader_cache->QueuePixelUberShaderCompile(uid.ps_uid, priority);
return stages_ready; return stages_ready;
} }
@ -883,8 +884,8 @@ void ShaderCache::QueueUberPipelineCompile(const GXUberPipelineUid& uid)
{ {
// Re-queue for next frame. // Re-queue for next frame.
auto wi = shader_cache->m_async_shader_compiler->CreateWorkItem<UberPipelineWorkItem>( auto wi = shader_cache->m_async_shader_compiler->CreateWorkItem<UberPipelineWorkItem>(
shader_cache, uid); shader_cache, uid, priority);
shader_cache->m_async_shader_compiler->QueueWorkItem(std::move(wi)); shader_cache->m_async_shader_compiler->QueueWorkItem(std::move(wi), priority);
} }
} }
@ -892,12 +893,13 @@ void ShaderCache::QueueUberPipelineCompile(const GXUberPipelineUid& uid)
ShaderCache* shader_cache; ShaderCache* shader_cache;
std::unique_ptr<AbstractPipeline> UberPipeline; std::unique_ptr<AbstractPipeline> UberPipeline;
GXUberPipelineUid uid; GXUberPipelineUid uid;
u32 priority;
std::optional<AbstractPipelineConfig> config; std::optional<AbstractPipelineConfig> config;
bool stages_ready; bool stages_ready;
}; };
auto wi = m_async_shader_compiler->CreateWorkItem<UberPipelineWorkItem>(this, uid); auto wi = m_async_shader_compiler->CreateWorkItem<UberPipelineWorkItem>(this, uid, priority);
m_async_shader_compiler->QueueWorkItem(std::move(wi)); m_async_shader_compiler->QueueWorkItem(std::move(wi), priority);
m_gx_uber_pipeline_cache[uid].second = true; m_gx_uber_pipeline_cache[uid].second = true;
} }

View File

@ -108,12 +108,23 @@ private:
void AppendGXPipelineUID(const GXPipelineUid& config); void AppendGXPipelineUID(const GXPipelineUid& config);
// ASync Compiler Methods // ASync Compiler Methods
void QueueVertexShaderCompile(const VertexShaderUid& uid); void QueueVertexShaderCompile(const VertexShaderUid& uid, u32 priority);
void QueueVertexUberShaderCompile(const UberShader::VertexShaderUid& uid); void QueueVertexUberShaderCompile(const UberShader::VertexShaderUid& uid, u32 priority);
void QueuePixelShaderCompile(const PixelShaderUid& uid); void QueuePixelShaderCompile(const PixelShaderUid& uid, u32 priority);
void QueuePixelUberShaderCompile(const UberShader::PixelShaderUid& uid); void QueuePixelUberShaderCompile(const UberShader::PixelShaderUid& uid, u32 priority);
void QueuePipelineCompile(const GXPipelineUid& uid); void QueuePipelineCompile(const GXPipelineUid& uid, u32 priority);
void QueueUberPipelineCompile(const GXUberPipelineUid& uid); void QueueUberPipelineCompile(const GXUberPipelineUid& uid, u32 priority);
// Priorities for compiling. The lower the value, the sooner the pipeline is compiled.
// The shader cache is compiled last, as it is the least likely to be required. On demand
// shaders are always compiled before pending ubershaders, as we want to use the ubershader
// for as few frames as possible, otherwise we risk framerate drops.
enum : u32
{
COMPILE_PRIORITY_ONDEMAND_PIPELINE = 100,
COMPILE_PRIORITY_UBERSHADER_PIPELINE = 200,
COMPILE_PRIORITY_SHADERCACHE_PIPELINE = 300
};
// Configuration bits. // Configuration bits.
APIType m_api_type = APIType::Nothing; APIType m_api_type = APIType::Nothing;