mirror of
https://github.com/libretro/parallel-gx1.git
synced 2026-11-07 04:26:01 -10:00
Shaders: ubershaders by default, seeded per game
Shader compilation defaults to Async (UberShaders): a draw whose own pipeline is not built yet is drawn with an ubershader one, exact rendering, while its own builds on the compiler threads, which now number as many as the machine has to spare (cores - 3, 1 to 4) instead of one. What is left to stutter is an ubershader pipeline itself, built on the spot the first time a draw needs one: one per vertex layout and fixed function state. Every ubershader pipeline a draw uses is now recorded, in <game id>.uberuidcache next to the specialized UID cache, and those are built before the game starts the next time it boots; every other ubershader pipeline there can be, and the specialized pipelines known, build in the background while it runs instead of before it starts, which took 21 s on lavapipe. The record is written when the cache closes, into room reserved when it opens, so recording allocates nothing in the middle of a game. Without compiler threads (OpenGL here, with no shared context for them), asking for a pipeline in the background builds it on the spot and an ubershader one besides: the asynchronous modes draw as Synchronous does then, and nothing extra is built before the game starts. Booting a FIFO log no longer hangs: the CPU thread clears the EFB, which waits on the GPU, before it reaches its run loop, and the frontend's thread waited for that run loop without running the GPU. It runs the GPU until then.
This commit is contained in:
1 parent
58542ddb3a
commit
e4bac5f75c
8 files changed
+163
-26
No files matched your search
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
@@ -508,7 +508,11 @@ bool retro_load_game(const struct retro_game_info* game)
|
||||
static_cast<ShaderCompilationMode>(
|
||||
Libretro::GetOption<int>(
|
||||
gfx_settings::SHADER_COMPILATION_MODE,
|
||||
static_cast<int>(ShaderCompilationMode::Synchronous))));
|
||||
static_cast<int>(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<bool>(gfx_settings::WAIT_FOR_SHADERS, /*def=*/false));
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<const AbstractPipeline*> 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;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
@@ -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<AbstractPipeline> 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<SerializedGXPipelineUid> 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<SerializedGXUberPipelineUid> m_pending_gx_uber_pipeline_uids;
|
||||
/* those already in the file or written to it this session */
|
||||
std::set<GXUberPipelineUid> m_recorded_uber_pipeline_uids;
|
||||
#endif
|
||||
Common::LinearDiskCache<SerializedGXPipelineUid, u8> m_gx_pipeline_disk_cache;
|
||||
Common::LinearDiskCache<SerializedGXUberPipelineUid, u8> m_gx_uber_pipeline_disk_cache;
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user