GPU/HW: Use multi-threaded shader compilation

pull/3797/merge
Stenzek 1 week ago
parent 7c239271fd
commit 94e5f08f18
No known key found for this signature in database

@ -199,52 +199,36 @@ namespace {
class ShaderCompileProgressTracker
{
public:
ShaderCompileProgressTracker(u32 total)
: m_image(System::GetImageForLoadingScreen(VideoThread::GetGamePath())),
m_min_time(Timer::ConvertSecondsToValue(1.0)), m_update_interval(Timer::ConvertSecondsToValue(0.1)),
m_start_time(Timer::GetCurrentValue()), m_last_update_time(0), m_progress(0), m_total(total)
{
}
~ShaderCompileProgressTracker() = default;
double GetElapsedMilliseconds() const
{
return Timer::ConvertValueToMilliseconds(Timer::GetCurrentValue() - m_start_time);
}
bool Increment(u32 progress, Error* error)
{
m_progress += progress;
if (System::IsStartupCancelled())
{
Error::SetStringView(error, TRANSLATE_SV("System", "Startup was cancelled."));
ERROR_LOG("Shader compilation aborted due to cancelled startup");
return false;
}
const u64 tv = Timer::GetCurrentValue();
if ((tv - m_start_time) >= m_min_time && (tv - m_last_update_time) >= m_update_interval)
{
FullscreenUI::RenderLoadingScreen(
m_image, TRANSLATE_SV("GPU_HW", "Compiling Shaders..."),
SmallString::from_format(TRANSLATE_FS("GPU_HW", "{} of {} pipelines"), m_progress, m_total), 0,
static_cast<int>(m_total), static_cast<int>(m_progress));
m_last_update_time = tv;
}
ShaderCompileProgressTracker(u32 total);
~ShaderCompileProgressTracker();
return true;
}
double GetElapsedMilliseconds() const;
bool Increment(u32 progress, Error* error);
bool CompileShader(std::unique_ptr<GPUShader>* dest, const GPUShaderCacheKey& key, std::string source, Error* error);
bool WaitForCompletion(Error* error);
private:
bool CheckForCancelledStartup(Error* error);
bool ConsumeCompletedTasks(Error* error);
void UpdateProgressScreen();
std::string m_image;
Timer::Value m_min_time;
Timer::Value m_update_interval;
Timer::Value m_start_time;
Timer::Value m_last_update_time;
u32 m_progress;
Timer::Value m_last_update_time = 0;
u32 m_progress = 0;
u32 m_total;
u32 m_last_progress = std::numeric_limits<u32>::max();
s32 m_tasks_remaining = 0;
// False sharing on these, meh, whatever.
std::atomic_int32_t m_tasks_completed{0};
std::atomic_flag m_tasks_abort = ATOMIC_FLAG_INIT;
};
} // namespace
GPU_HW::GPU_HW() : GPUBackend()
@ -1099,6 +1083,142 @@ void GPU_HW::DestroyBuffers()
g_gpu_device->RecycleTexture(std::move(m_vram_readback_texture));
}
ShaderCompileProgressTracker::ShaderCompileProgressTracker(u32 total)
: m_image(System::GetImageForLoadingScreen(VideoThread::GetGamePath())),
m_min_time(Timer::ConvertSecondsToValue(1.0)), m_update_interval(Timer::ConvertSecondsToValue(0.1)),
m_start_time(Timer::GetCurrentValue()), m_total(total)
{
}
ShaderCompileProgressTracker::~ShaderCompileProgressTracker() = default;
double ShaderCompileProgressTracker::GetElapsedMilliseconds() const
{
return Timer::ConvertValueToMilliseconds(Timer::GetCurrentValue() - m_start_time);
}
bool ShaderCompileProgressTracker::Increment(u32 progress, Error* error)
{
m_progress += progress;
if (CheckForCancelledStartup(error))
{
// some shaders may not have been cached, so we need to explicitly wait here
WaitForCompletion(nullptr);
return false;
}
UpdateProgressScreen();
return true;
}
bool ShaderCompileProgressTracker::CheckForCancelledStartup(Error* error)
{
if (!System::IsStartupCancelled())
return false;
Error::SetStringView(error, TRANSLATE_SV("System", "Startup was cancelled."));
ERROR_LOG("Shader compilation aborted due to cancelled startup");
m_tasks_abort.test_and_set(std::memory_order_acq_rel);
return true;
}
void ShaderCompileProgressTracker::UpdateProgressScreen()
{
const u64 tv = Timer::GetCurrentValue();
if ((tv - m_start_time) >= m_min_time && m_progress != m_last_progress &&
(tv - m_last_update_time) >= m_update_interval)
{
m_last_progress = m_progress;
m_last_update_time = tv;
FullscreenUI::RenderLoadingScreen(
m_image, TRANSLATE_SV("GPU_HW", "Compiling Shaders..."),
SmallString::from_format(TRANSLATE_FS("GPU_HW", "{} of {} pipelines"), m_progress, m_total), 0,
static_cast<int>(m_total), static_cast<int>(m_progress));
}
}
bool ShaderCompileProgressTracker::CompileShader(std::unique_ptr<GPUShader>* dest, const GPUShaderCacheKey& key,
std::string source, Error* error)
{
if (!g_gpu_device->GetFeatures().thread_safe_shader_compile)
{
if (!(*dest = g_gpu_device->CompileShader(key, source, error)))
return false;
return Increment(1, error);
}
// Have we previously aborted? Don't enqueue if so.
if (!ConsumeCompletedTasks(error))
return false;
m_tasks_remaining++;
Host::QueueAsyncTask([this, dest, key, source = std::move(source)]() {
// bail out on error
if (!m_tasks_abort.test(std::memory_order_acquire))
{
Error error;
if (!(*dest = g_gpu_device->CompileShader(key, source, &error)))
{
ERROR_LOG("Failed to compile {} shader: {}", GPUShader::GetStageName(key.stage), error.GetDescription());
m_tasks_abort.test_and_set(std::memory_order_acq_rel);
}
}
m_tasks_completed.fetch_add(1, std::memory_order_acq_rel);
});
return true;
}
bool ShaderCompileProgressTracker::WaitForCompletion(Error* error)
{
bool ret = true;
while (m_tasks_remaining > 0)
{
// still need to wait for the workers to finish, even on error
ret &= ConsumeCompletedTasks(error);
if (m_tasks_remaining <= 0)
break;
// if we're just waiting for someone else to finish, don't spam updates
UpdateProgressScreen();
VideoPresenter::ThrottlePresentation();
}
Assert(m_tasks_remaining == 0);
return ret;
}
bool ShaderCompileProgressTracker::ConsumeCompletedTasks(Error* error)
{
const s32 done = m_tasks_completed.load(std::memory_order_acquire);
if (done == 0)
return true;
m_tasks_completed.fetch_sub(done, std::memory_order_acq_rel);
m_tasks_remaining -= done;
m_progress += done;
if (m_tasks_abort.test(std::memory_order_acquire)) [[unlikely]]
{
Error::SetStringView(error, "One or more shaders failed to compile.");
return false;
}
else if (CheckForCancelledStartup(error)) [[unlikely]]
{
// this is called from WaitForCompletion(), which will drain
return false;
}
UpdateProgressScreen();
return true;
}
bool GPU_HW::CompileCommonShaders(Error* error)
{
const GPU_HW_ShaderGen shadergen(g_gpu_device->GetRenderAPI(), m_supports_dual_source_blend,
@ -1258,15 +1378,19 @@ bool GPU_HW::CompilePipelines(Error* error)
const GPUShaderCacheKey key =
GPUDevice::GetShaderCacheKey(GPUShaderStage::Vertex, shadergen.GetLanguage(),
static_cast<u16>(ShaderCacheKeyType::HWBatchVertex), &vssel, sizeof(vssel));
if (!(batch_vertex_shaders[textured][palette][sprite] = g_gpu_device->LoadShader(key)) &&
!(batch_vertex_shaders[textured][palette][sprite] =
g_gpu_device->CompileShader(key, shadergen.GenerateBatchVertexShader(vssel), error)))
if (!(batch_vertex_shaders[textured][palette][sprite] = g_gpu_device->LoadShader(key)))
{
return false;
if (!progress.CompileShader(&batch_vertex_shaders[textured][palette][sprite], key,
shadergen.GenerateBatchVertexShader(vssel), error)) [[unlikely]]
{
return false;
}
}
else
{
if (!progress.Increment(1, error)) [[unlikely]]
return false;
}
if (!progress.Increment(1, error)) [[unlikely]]
return false;
}
}
}
@ -1378,16 +1502,20 @@ bool GPU_HW::CompilePipelines(Error* error)
static_cast<u16>(ShaderCacheKeyType::HWBatchFragment), &fssel, sizeof(fssel));
if (!(batch_fragment_shaders[depth_test][render_mode][transparency_mode][texture_mode][check_mask]
[dithering][interlacing] = g_gpu_device->LoadShader(key)) &&
!(batch_fragment_shaders[depth_test][render_mode][transparency_mode][texture_mode][check_mask]
[dithering][interlacing] = g_gpu_device->CompileShader(
key, shadergen.GenerateBatchFragmentShader(fssel), error)))
[dithering][interlacing] = g_gpu_device->LoadShader(key)))
{
return false;
if (!progress.CompileShader(&batch_fragment_shaders[depth_test][render_mode][transparency_mode]
[texture_mode][check_mask][dithering][interlacing],
key, shadergen.GenerateBatchFragmentShader(fssel), error)) [[unlikely]]
{
return false;
}
}
else
{
if (!progress.Increment(1, error)) [[unlikely]]
return false;
}
if (!progress.Increment(1, error)) [[unlikely]]
return false;
}
}
}
@ -1396,6 +1524,9 @@ bool GPU_HW::CompilePipelines(Error* error)
}
}
if (!progress.WaitForCompletion(error))
return false;
static constexpr GPUPipeline::VertexAttribute vertex_attributes[] = {
GPUPipeline::VertexAttribute::Make(0, GPUPipeline::VertexAttribute::Semantic::Position, 0,
GPUPipeline::VertexAttribute::Type::Float, 4, OFFSETOF(BatchVertex, x)),

Loading…
Cancel
Save