diff --git a/Source/Core/Core/HW/CPU.cpp b/Source/Core/Core/HW/CPU.cpp index 41031cc440..9f7a493316 100644 --- a/Source/Core/Core/HW/CPU.cpp +++ b/Source/Core/Core/HW/CPU.cpp @@ -39,7 +39,6 @@ CPUManager::CPUManager(Core::System& system) : m_system(system) retro_eventcount_init(&m_cpu_idle); #ifdef __LIBRETRO__ retro_atomic_int_init(&m_cpu_run_state_reached, 0); - retro_eventcount_init(&m_run_state_event); #endif } @@ -51,9 +50,6 @@ CPUManager::~CPUManager() node = node->next; delete job; } -#ifdef __LIBRETRO__ - retro_eventcount_free(&m_run_state_event); -#endif retro_eventcount_free(&m_cpu_idle); retro_eventcount_free(&m_cpu_wake); retro_eventcount_free(&m_stepping_released); @@ -162,7 +158,6 @@ void CPUManager::Run() #ifdef __LIBRETRO__ retro_atomic_store_release_int(&m_cpu_run_state_reached, 1); - retro_eventcount_notify(&m_run_state_event); #endif while (LoadState() != State::PowerDown) { diff --git a/Source/Core/Core/HW/CPU.h b/Source/Core/Core/HW/CPU.h index ba1c3dfdc5..691db5e40d 100644 --- a/Source/Core/Core/HW/CPU.h +++ b/Source/Core/Core/HW/CPU.h @@ -113,20 +113,6 @@ public: { return retro_atomic_load_acquire_int(&m_cpu_run_state_reached) != 0; } - // Blocks until the CPU thread has entered its run loop. - void WaitForCPURunState() - { - for (;;) - { - const int key = retro_eventcount_prepare_wait(&m_run_state_event); - if (HasCPURunStateBeenReached()) - { - retro_eventcount_cancel_wait(&m_run_state_event); - return; - } - retro_eventcount_commit_wait(&m_run_state_event, key); - } - } #endif private: struct Job @@ -179,7 +165,6 @@ private: Core::System& m_system; #ifdef __LIBRETRO__ retro_atomic_int_t m_cpu_run_state_reached; - retro_eventcount_t m_run_state_event; #endif }; } // namespace CPU diff --git a/Source/Core/DolphinLibretro/Boot.cpp b/Source/Core/DolphinLibretro/Boot.cpp index eed8f13d5c..dc15061b19 100644 --- a/Source/Core/DolphinLibretro/Boot.cpp +++ b/Source/Core/DolphinLibretro/Boot.cpp @@ -508,7 +508,11 @@ bool retro_load_game(const struct retro_game_info* game) static_cast( Libretro::GetOption( gfx_settings::SHADER_COMPILATION_MODE, - static_cast(ShaderCompilationMode::Synchronous)))); + static_cast(ShaderCompilationMode::AsynchronousUberShaders)))); + /* as many compiler threads as the machine has to spare, for building + * pipelines both before the game starts and while it runs */ + Config::SetBase(Config::GFX_SHADER_COMPILER_THREADS, -1); + Config::SetBase(Config::GFX_SHADER_PRECOMPILER_THREADS, -1); Config::SetBase(Config::GFX_WAIT_FOR_SHADERS_BEFORE_STARTING, Libretro::GetOption(gfx_settings::WAIT_FOR_SHADERS, /*def=*/false)); diff --git a/Source/Core/DolphinLibretro/Common/Options.cpp b/Source/Core/DolphinLibretro/Common/Options.cpp index 9561607771..d609bfe238 100644 --- a/Source/Core/DolphinLibretro/Common/Options.cpp +++ b/Source/Core/DolphinLibretro/Common/Options.cpp @@ -863,17 +863,20 @@ static struct retro_core_option_v2_definition option_defs[] = { Libretro::Options::gfx_settings::SHADER_COMPILATION_MODE, "Graphics > Settings > Shader Compilation", "Shader Compilation", - "Control shader compilation.", + "Control shader compilation. Async (UberShaders) draws with ubershaders, built before the " + "game starts, while each draw's own pipeline builds in the background: exact rendering " + "without compilation stutter, at some GPU cost while new pipelines build. Synchronous " + "builds each new pipeline when it is first drawn, stuttering.", nullptr, CATEGORY_GFX_SETTINGS, { + { "2", "Async (UberShaders)" }, // ShaderCompilationMode::AsynchronousUberShaders { "0", "Synchronous" }, // ShaderCompilationMode::Synchronous { "3", "Async (Skip Rendering)" }, // ShaderCompilationMode::AsynchronousSkipRendering { "1", "Sync (UberShaders)" }, // ShaderCompilationMode::SynchronousUberShaders - { "2", "Async (UberShaders)" }, // ShaderCompilationMode::AsynchronousUberShaders { nullptr, nullptr } }, - "0" + "2" }, { Libretro::Options::gfx_settings::WAIT_FOR_SHADERS, diff --git a/Source/Core/DolphinLibretro/Main.cpp b/Source/Core/DolphinLibretro/Main.cpp index ffba82f4c2..a93ba4cee7 100644 --- a/Source/Core/DolphinLibretro/Main.cpp +++ b/Source/Core/DolphinLibretro/Main.cpp @@ -333,8 +333,13 @@ void retro_run(void) { Core::StartEmuThread(system, std::move(Core::g_boot_params), wsi); - // Wait until CPU thread has reached Run() - system.GetCPU().WaitForCPURunState(); + /* until the CPU thread has reached its run loop, this thread runs + * the GPU: what the CPU thread does first may wait on it, as the + * FIFO player clearing the EFB does */ + Libretro::Video::D3D11Lock(); + while (!system.GetCPU().HasCPURunStateBeenReached()) + system.GetFifo().RunGpuLoop(5000); + Libretro::Video::D3D11Unlock(); } else Core::EmuThread(system, std::move(Core::g_boot_params), wsi); diff --git a/Source/Core/VideoCommon/ShaderCache.cpp b/Source/Core/VideoCommon/ShaderCache.cpp index 945fc3a956..7c540a7d05 100644 --- a/Source/Core/VideoCommon/ShaderCache.cpp +++ b/Source/Core/VideoCommon/ShaderCache.cpp @@ -62,11 +62,32 @@ void ShaderCache::InitializeShaderCache() { LoadCaches(); LoadPipelineUIDCache(); +#ifdef __LIBRETRO__ + if (g_ActiveConfig.UsingUberShaders()) + LoadUberPipelineUIDCache(); +#endif } +#ifdef __LIBRETRO__ + /* With ubershaders, every draw without its specialized pipeline yet is + * drawn with an ubershader one. The ones this game drew with before are + * built before it starts, as a draw would otherwise build its own on + * the spot; every other one there can be, and the specialized ones + * known, go on building in the background while it runs. Without + * threads to build on, the asynchronous modes draw as Synchronous does + * (VertexManagerBase::UpdatePipelineObject), and nothing more is built + * before the game starts than that would. */ + if (g_ActiveConfig.UsingUberShaders() && CanCompileInBackground()) + { + CompileMissingUberPipelines(); + WaitForAsyncCompiler(); + QueueUberShaderPipelines(); + } +#else // Queue ubershader precompiling if required. if (g_ActiveConfig.UsingUberShaders()) QueueUberShaderPipelines(); +#endif // Compile all known UIDs. CompileMissingPipelines(); @@ -77,6 +98,11 @@ void ShaderCache::InitializeShaderCache() m_async_shader_compiler->ResizeWorkerThreads(g_ActiveConfig.GetShaderCompilerThreads()); } +bool ShaderCache::CanCompileInBackground() const +{ + return m_async_shader_compiler && m_async_shader_compiler->HasWorkerThreads(); +} + void ShaderCache::Reload() { WaitForAsyncCompiler(); @@ -155,6 +181,12 @@ std::optional ShaderCache::GetPipelineForUidAsync(const const AbstractPipeline* ShaderCache::GetUberPipelineForUid(const GXUberPipelineUid& uid) { auto it = m_gx_uber_pipeline_cache.find(uid); +#ifdef __LIBRETRO__ + /* every one a draw uses is recorded, to be built before the game + * starts next time */ + if (m_gx_uber_pipeline_uid_cache_file.IsOpen() && !m_recorded_uber_pipeline_uids.contains(uid)) + AppendGXUberPipelineUID(uid); +#endif if (it != m_gx_uber_pipeline_cache.end() && !it->second.second) return it->second.first.get(); @@ -853,10 +885,96 @@ void ShaderCache::ClosePipelineUIDCache() WARN_LOG_FMT(VIDEO, "Writing pipeline UIDs to cache failed."); } m_pending_gx_pipeline_uids.clear(); + if (m_gx_uber_pipeline_uid_cache_file.IsOpen() && !m_pending_gx_uber_pipeline_uids.empty() && + !m_gx_uber_pipeline_uid_cache_file.WriteArray(m_pending_gx_uber_pipeline_uids.data(), + m_pending_gx_uber_pipeline_uids.size())) + { + WARN_LOG_FMT(VIDEO, "Writing ubershader pipeline UIDs to cache failed."); + } + m_pending_gx_uber_pipeline_uids.clear(); + m_recorded_uber_pipeline_uids.clear(); + m_gx_uber_pipeline_uid_cache_file.Close(); #endif m_gx_pipeline_uid_cache_file.Close(); } +#ifdef __LIBRETRO__ +void ShaderCache::LoadUberPipelineUIDCache() +{ + const u32 magic = 0x44495555; // UUID + const u32 header_size = sizeof(u32) + sizeof(u32); + const std::string filename = + File::GetUserPath(D_CACHE_IDX) + SConfig::GetInstance().GetGameID() + ".uberuidcache"; + File::IOFile& file = m_gx_uber_pipeline_uid_cache_file; + bool valid = false; + + if (file.Open(filename, "rb+")) + { + u32 existing_magic; + u32 existing_version; + if (file.ReadBytes(&existing_magic, sizeof(existing_magic)) && + file.ReadBytes(&existing_version, sizeof(existing_version)) && existing_magic == magic && + existing_version == GX_PIPELINE_UID_VERSION) + { + const u64 file_size = file.GetSize(); + const u64 count = (file_size - header_size) / sizeof(SerializedGXUberPipelineUid); + u64 i; + + valid = file_size == count * sizeof(SerializedGXUberPipelineUid) + header_size; + for (i = 0; valid && i < count; i++) + { + SerializedGXUberPipelineUid serialized_uid; + GXUberPipelineUid real_uid; + if (!file.ReadBytes(&serialized_uid, sizeof(serialized_uid))) + { + valid = false; + break; + } + UnserializePipelineUid(serialized_uid, real_uid); + /* known, not built: built with the ubershaders before the game + * starts */ + if (!m_gx_uber_pipeline_cache.contains(real_uid)) + m_gx_uber_pipeline_cache[real_uid].second = false; + m_recorded_uber_pipeline_uids.insert(real_uid); + } + if (valid) + valid = file.Seek(file_size, File::SeekOrigin::Begin); + } + if (!valid) + file.Close(); + } + if (!file.IsOpen() && file.Open(filename, "wb")) + { + file.WriteBytes(&magic, sizeof(magic)); + file.WriteBytes(&GX_PIPELINE_UID_VERSION, sizeof(GX_PIPELINE_UID_VERSION)); + } + /* what a session adds is written when the cache closes: room enough + * that adding is no allocation in the middle of a game */ + m_pending_gx_uber_pipeline_uids.reserve(1024); + m_pending_gx_pipeline_uids.reserve(1024); +} + +void ShaderCache::AppendGXUberPipelineUID(const GXUberPipelineUid& config) +{ + SerializedGXUberPipelineUid disk_uid; + + if (!m_gx_uber_pipeline_uid_cache_file.IsOpen()) + return; + SerializePipelineUid(config, disk_uid); + m_pending_gx_uber_pipeline_uids.push_back(disk_uid); + m_recorded_uber_pipeline_uids.insert(config); +} + +void ShaderCache::CompileMissingUberPipelines() +{ + for (auto& it : m_gx_uber_pipeline_cache) + { + if (!it.second.first && !it.second.second) + QueueUberPipelineCompile(it.first, COMPILE_PRIORITY_UBERSHADER_PIPELINE); + } +} +#endif + void ShaderCache::AddSerializedGXPipelineUID(const SerializedGXPipelineUid& uid) { GXPipelineUid real_uid; diff --git a/Source/Core/VideoCommon/ShaderCache.h b/Source/Core/VideoCommon/ShaderCache.h index 868fe7d565..c7258b972d 100644 --- a/Source/Core/VideoCommon/ShaderCache.h +++ b/Source/Core/VideoCommon/ShaderCache.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,10 @@ namespace VideoCommon class ShaderCache final { public: + /* Whether pipelines can build on threads of their own: without, asking + * for one in the background builds it on the spot all the same. */ + bool CanCompileInBackground() const; + ShaderCache(); ~ShaderCache(); @@ -164,6 +169,12 @@ private: std::unique_ptr pipeline); void AddSerializedGXPipelineUID(const SerializedGXPipelineUid& uid); void AppendGXPipelineUID(const GXPipelineUid& config); +#ifdef __LIBRETRO__ + void LoadUberPipelineUIDCache(); + void AppendGXUberPipelineUID(const GXUberPipelineUid& config); + /* queues the ubershader pipelines known and not built yet */ + void CompileMissingUberPipelines(); +#endif // ASync Compiler Methods void QueueVertexShaderCompile(const VertexShaderUid& uid, u32 priority); @@ -234,6 +245,13 @@ private: File::IOFile m_gx_pipeline_uid_cache_file; #ifdef __LIBRETRO__ std::vector m_pending_gx_pipeline_uids; + /* The ubershader pipelines a game drew with, as for the specialized + * ones: compiled while the game boots next time, so a hybrid draw + * finds its ubershader ready instead of building it on the spot. */ + File::IOFile m_gx_uber_pipeline_uid_cache_file; + std::vector m_pending_gx_uber_pipeline_uids; + /* those already in the file or written to it this session */ + std::set m_recorded_uber_pipeline_uids; #endif Common::LinearDiskCache m_gx_pipeline_disk_cache; Common::LinearDiskCache m_gx_uber_pipeline_disk_cache; diff --git a/Source/Core/VideoCommon/VertexManagerBase.cpp b/Source/Core/VideoCommon/VertexManagerBase.cpp index e03b1e6cfe..13e03a2b8d 100644 --- a/Source/Core/VideoCommon/VertexManagerBase.cpp +++ b/Source/Core/VideoCommon/VertexManagerBase.cpp @@ -880,6 +880,15 @@ void VertexManagerBase::UpdatePipelineObject() case ShaderCompilationMode::AsynchronousUberShaders: case ShaderCompilationMode::AsynchronousSkipRendering: { +#ifdef __LIBRETRO__ + /* no threads to build on: asking for the pipeline in the background + * would build it on the spot, then an ubershader one besides */ + if (!g_shader_cache->CanCompileInBackground()) + { + m_current_pipeline_object = g_shader_cache->GetPipelineForUid(m_current_pipeline_config); + return; + } +#endif // Can we background compile shaders? If so, get the pipeline asynchronously. auto res = g_shader_cache->GetPipelineForUidAsync(m_current_pipeline_config); if (res)