From 94e5f08f187d9ee335b4f8d962b7f4ddfcebcc66 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 13 Sep 2026 12:54:48 +1000 Subject: [PATCH] GPU/HW: Use multi-threaded shader compilation --- src/core/gpu_hw.cpp | 235 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 183 insertions(+), 52 deletions(-) diff --git a/src/core/gpu_hw.cpp b/src/core/gpu_hw.cpp index 0b720dcae..67c214964 100644 --- a/src/core/gpu_hw.cpp +++ b/src/core/gpu_hw.cpp @@ -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(m_total), static_cast(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* 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::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(m_total), static_cast(m_progress)); + } +} + +bool ShaderCompileProgressTracker::CompileShader(std::unique_ptr* 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(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(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)),