123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611 |
- // Copyright 2018 Dolphin Emulator Project
- // SPDX-License-Identifier: GPL-2.0-or-later
- #include "VideoCommon/ShaderCache.h"
- #include <fmt/format.h>
- #include "Common/Assert.h"
- #include "Common/FileUtil.h"
- #include "Common/MsgHandler.h"
- #include "Core/ConfigManager.h"
- #include "VideoCommon/AbstractGfx.h"
- #include "VideoCommon/ConstantManager.h"
- #include "VideoCommon/DriverDetails.h"
- #include "VideoCommon/FramebufferManager.h"
- #include "VideoCommon/FramebufferShaderGen.h"
- #include "VideoCommon/Present.h"
- #include "VideoCommon/Statistics.h"
- #include "VideoCommon/VertexLoaderManager.h"
- #include "VideoCommon/VertexManagerBase.h"
- #include "VideoCommon/VideoCommon.h"
- #include "VideoCommon/VideoConfig.h"
- #include <imgui.h>
- std::unique_ptr<VideoCommon::ShaderCache> g_shader_cache;
- namespace VideoCommon
- {
- ShaderCache::ShaderCache() : m_api_type{APIType::Nothing}
- {
- }
- ShaderCache::~ShaderCache()
- {
- ClearCaches();
- }
- bool ShaderCache::Initialize()
- {
- m_api_type = g_ActiveConfig.backend_info.api_type;
- m_host_config.bits = ShaderHostConfig::GetCurrent().bits;
- if (!CompileSharedPipelines())
- return false;
- m_async_shader_compiler = g_gfx->CreateAsyncShaderCompiler();
- m_frame_end_handler = AfterFrameEvent::Register([this](Core::System&) { RetrieveAsyncShaders(); },
- "RetrieveAsyncShaders");
- return true;
- }
- void ShaderCache::InitializeShaderCache()
- {
- m_async_shader_compiler->ResizeWorkerThreads(g_ActiveConfig.GetShaderPrecompilerThreads());
- // Load shader and UID caches.
- if (g_ActiveConfig.bShaderCache && m_api_type != APIType::Nothing)
- {
- LoadCaches();
- LoadPipelineUIDCache();
- }
- // Queue ubershader precompiling if required.
- if (g_ActiveConfig.UsingUberShaders())
- QueueUberShaderPipelines();
- // Compile all known UIDs.
- CompileMissingPipelines();
- if (g_ActiveConfig.bWaitForShadersBeforeStarting)
- WaitForAsyncCompiler();
- // Switch to the runtime shader compiler thread configuration.
- m_async_shader_compiler->ResizeWorkerThreads(g_ActiveConfig.GetShaderCompilerThreads());
- }
- void ShaderCache::Reload()
- {
- WaitForAsyncCompiler();
- ClosePipelineUIDCache();
- ClearCaches();
- if (!CompileSharedPipelines())
- PanicAlertFmt("Failed to compile shared pipelines after reload.");
- if (g_ActiveConfig.bShaderCache)
- LoadCaches();
- // Switch to the precompiling shader configuration while we rebuild.
- m_async_shader_compiler->ResizeWorkerThreads(g_ActiveConfig.GetShaderPrecompilerThreads());
- // We don't need to explicitly recompile the individual ubershaders here, as the pipelines
- // UIDs are still be in the map. Therefore, when these are rebuilt, the shaders will also
- // be recompiled.
- CompileMissingPipelines();
- if (g_ActiveConfig.bWaitForShadersBeforeStarting)
- WaitForAsyncCompiler();
- m_async_shader_compiler->ResizeWorkerThreads(g_ActiveConfig.GetShaderCompilerThreads());
- }
- void ShaderCache::RetrieveAsyncShaders()
- {
- m_async_shader_compiler->RetrieveWorkItems();
- }
- void ShaderCache::Shutdown()
- {
- // This may leave shaders uncommitted to the cache, but it's better than blocking shutdown
- // until everything has finished compiling.
- if (m_async_shader_compiler)
- m_async_shader_compiler->StopWorkerThreads();
- ClosePipelineUIDCache();
- }
- const AbstractPipeline* ShaderCache::GetPipelineForUid(const GXPipelineUid& uid)
- {
- auto it = m_gx_pipeline_cache.find(uid);
- if (it != m_gx_pipeline_cache.end() && !it->second.second)
- return it->second.first.get();
- const bool exists_in_cache = it != m_gx_pipeline_cache.end();
- std::unique_ptr<AbstractPipeline> pipeline;
- std::optional<AbstractPipelineConfig> pipeline_config = GetGXPipelineConfig(uid);
- if (pipeline_config)
- pipeline = g_gfx->CreatePipeline(*pipeline_config);
- if (g_ActiveConfig.bShaderCache && !exists_in_cache)
- AppendGXPipelineUID(uid);
- return InsertGXPipeline(uid, std::move(pipeline));
- }
- std::optional<const AbstractPipeline*> ShaderCache::GetPipelineForUidAsync(const GXPipelineUid& uid)
- {
- auto it = m_gx_pipeline_cache.find(uid);
- if (it != m_gx_pipeline_cache.end())
- {
- // .second is the pending flag, i.e. compiling in the background.
- if (!it->second.second)
- return it->second.first.get();
- else
- return {};
- }
- AppendGXPipelineUID(uid);
- QueuePipelineCompile(uid, COMPILE_PRIORITY_ONDEMAND_PIPELINE);
- return {};
- }
- const AbstractPipeline* ShaderCache::GetUberPipelineForUid(const GXUberPipelineUid& uid)
- {
- auto it = m_gx_uber_pipeline_cache.find(uid);
- if (it != m_gx_uber_pipeline_cache.end() && !it->second.second)
- return it->second.first.get();
- std::unique_ptr<AbstractPipeline> pipeline;
- std::optional<AbstractPipelineConfig> pipeline_config = GetGXPipelineConfig(uid);
- if (pipeline_config)
- pipeline = g_gfx->CreatePipeline(*pipeline_config);
- return InsertGXUberPipeline(uid, std::move(pipeline));
- }
- void ShaderCache::WaitForAsyncCompiler()
- {
- bool running = true;
- constexpr auto update_ui_progress = [](size_t completed, size_t total) {
- const float center_x = ImGui::GetIO().DisplaySize.x * 0.5f;
- const float center_y = ImGui::GetIO().DisplaySize.y * 0.5f;
- const float scale = ImGui::GetIO().DisplayFramebufferScale.x;
- ImGui::SetNextWindowSize(ImVec2(400.0f * scale, 50.0f * scale), ImGuiCond_Always);
- ImGui::SetNextWindowPos(ImVec2(center_x, center_y), ImGuiCond_Always, ImVec2(0.5f, 0.5f));
- if (ImGui::Begin(Common::GetStringT("Compiling Shaders").c_str(), nullptr,
- ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs |
- ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
- ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav |
- ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing))
- {
- ImGui::Text("Compiling shaders: %zu/%zu", completed, total);
- ImGui::ProgressBar(static_cast<float>(completed) /
- static_cast<float>(std::max(total, static_cast<size_t>(1))),
- ImVec2(-1.0f, 0.0f), "");
- }
- ImGui::End();
- g_presenter->Present();
- };
- while (running &&
- (m_async_shader_compiler->HasPendingWork() || m_async_shader_compiler->HasCompletedWork()))
- {
- running = m_async_shader_compiler->WaitUntilCompletion(update_ui_progress);
- m_async_shader_compiler->RetrieveWorkItems();
- }
- // An extra Present to clear the screen
- g_presenter->Present();
- }
- template <typename SerializedUidType, typename UidType>
- static void SerializePipelineUid(const UidType& uid, SerializedUidType& serialized_uid)
- {
- // Convert to disk format. Ensure all padding bytes are zero.
- std::memset(reinterpret_cast<u8*>(&serialized_uid), 0, sizeof(serialized_uid));
- serialized_uid.vertex_decl = uid.vertex_format->GetVertexDeclaration();
- serialized_uid.vs_uid = uid.vs_uid;
- serialized_uid.gs_uid = uid.gs_uid;
- serialized_uid.ps_uid = uid.ps_uid;
- serialized_uid.rasterization_state_bits = uid.rasterization_state.hex;
- serialized_uid.depth_state_bits = uid.depth_state.hex;
- serialized_uid.blending_state_bits = uid.blending_state.hex;
- }
- template <typename UidType, typename SerializedUidType>
- static void UnserializePipelineUid(const SerializedUidType& uid, UidType& real_uid)
- {
- real_uid.vertex_format = VertexLoaderManager::GetOrCreateMatchingFormat(uid.vertex_decl);
- real_uid.vs_uid = uid.vs_uid;
- real_uid.gs_uid = uid.gs_uid;
- real_uid.ps_uid = uid.ps_uid;
- real_uid.rasterization_state.hex = uid.rasterization_state_bits;
- real_uid.depth_state.hex = uid.depth_state_bits;
- real_uid.blending_state.hex = uid.blending_state_bits;
- }
- template <ShaderStage stage, typename K, typename T>
- void ShaderCache::LoadShaderCache(T& cache, APIType api_type, const char* type, bool include_gameid)
- {
- class CacheReader : public Common::LinearDiskCacheReader<K, u8>
- {
- public:
- CacheReader(T& cache_) : cache(cache_) {}
- void Read(const K& key, const u8* value, u32 value_size) override
- {
- auto shader = g_gfx->CreateShaderFromBinary(stage, value, value_size);
- if (shader)
- {
- auto& entry = cache.shader_map[key];
- entry.shader = std::move(shader);
- entry.pending = false;
- switch (stage)
- {
- case ShaderStage::Vertex:
- INCSTAT(g_stats.num_vertex_shaders_created);
- INCSTAT(g_stats.num_vertex_shaders_alive);
- break;
- case ShaderStage::Pixel:
- INCSTAT(g_stats.num_pixel_shaders_created);
- INCSTAT(g_stats.num_pixel_shaders_alive);
- break;
- default:
- break;
- }
- }
- }
- private:
- T& cache;
- };
- std::string filename = GetDiskShaderCacheFileName(api_type, type, include_gameid, true);
- CacheReader reader(cache);
- u32 count = cache.disk_cache.OpenAndRead(filename, reader);
- INFO_LOG_FMT(VIDEO, "Loaded {} cached shaders from {}", count, filename);
- }
- template <typename T>
- void ShaderCache::ClearShaderCache(T& cache)
- {
- cache.disk_cache.Sync();
- cache.disk_cache.Close();
- cache.shader_map.clear();
- }
- template <typename KeyType, typename DiskKeyType, typename T>
- void ShaderCache::LoadPipelineCache(T& cache, Common::LinearDiskCache<DiskKeyType, u8>& disk_cache,
- APIType api_type, const char* type, bool include_gameid)
- {
- class CacheReader : public Common::LinearDiskCacheReader<DiskKeyType, u8>
- {
- public:
- CacheReader(ShaderCache* this_ptr_, T& cache_) : this_ptr(this_ptr_), cache(cache_) {}
- bool AnyFailed() const { return failed; }
- void Read(const DiskKeyType& key, const u8* value, u32 value_size) override
- {
- KeyType real_uid;
- UnserializePipelineUid(key, real_uid);
- // Skip those which are already compiled.
- if (failed || cache.contains(real_uid))
- return;
- auto config = this_ptr->GetGXPipelineConfig(real_uid);
- if (!config)
- return;
- auto pipeline = g_gfx->CreatePipeline(*config, value, value_size);
- if (!pipeline)
- {
- // If any of the pipelines fail to create, consider the cache stale.
- failed = true;
- return;
- }
- auto& entry = cache[real_uid];
- entry.first = std::move(pipeline);
- entry.second = false;
- }
- private:
- ShaderCache* this_ptr;
- T& cache;
- bool failed = false;
- };
- std::string filename = GetDiskShaderCacheFileName(api_type, type, include_gameid, true);
- CacheReader reader(this, cache);
- const u32 count = disk_cache.OpenAndRead(filename, reader);
- INFO_LOG_FMT(VIDEO, "Loaded {} cached pipelines from {}", count, filename);
- // If any of the pipelines in the cache failed to create, it's likely because of a change of
- // driver version, or system configuration. In this case, when the UID cache picks up the pipeline
- // later on, we'll write a duplicate entry to the pipeline cache. There's also no point in keeping
- // the old cache data around, so discard and recreate the disk cache.
- if (reader.AnyFailed())
- {
- WARN_LOG_FMT(VIDEO, "Failed to load one or more pipelines from cache '{}'. Discarding.",
- filename);
- disk_cache.Close();
- File::Delete(filename);
- disk_cache.OpenAndRead(filename, reader);
- }
- }
- template <typename T, typename Y>
- void ShaderCache::ClearPipelineCache(T& cache, Y& disk_cache)
- {
- disk_cache.Sync();
- disk_cache.Close();
- // Set the pending flag to false, and destroy the pipeline.
- for (auto& it : cache)
- {
- it.second.first.reset();
- it.second.second = false;
- }
- }
- void ShaderCache::LoadCaches()
- {
- // Ubershader caches, if present.
- if (g_ActiveConfig.backend_info.bSupportsShaderBinaries)
- {
- LoadShaderCache<ShaderStage::Vertex, UberShader::VertexShaderUid>(m_uber_vs_cache, m_api_type,
- "uber-vs", false);
- LoadShaderCache<ShaderStage::Pixel, UberShader::PixelShaderUid>(m_uber_ps_cache, m_api_type,
- "uber-ps", false);
- // We also share geometry shaders, as there aren't many variants.
- if (m_host_config.backend_geometry_shaders)
- LoadShaderCache<ShaderStage::Geometry, GeometryShaderUid>(m_gs_cache, m_api_type, "gs",
- false);
- // Specialized shaders, gameid-specific.
- LoadShaderCache<ShaderStage::Vertex, VertexShaderUid>(m_vs_cache, m_api_type, "specialized-vs",
- true);
- LoadShaderCache<ShaderStage::Pixel, PixelShaderUid>(m_ps_cache, m_api_type, "specialized-ps",
- true);
- }
- if (g_ActiveConfig.backend_info.bSupportsPipelineCacheData)
- {
- LoadPipelineCache<GXPipelineUid, SerializedGXPipelineUid>(
- m_gx_pipeline_cache, m_gx_pipeline_disk_cache, m_api_type, "specialized-pipeline", true);
- LoadPipelineCache<GXUberPipelineUid, SerializedGXUberPipelineUid>(
- m_gx_uber_pipeline_cache, m_gx_uber_pipeline_disk_cache, m_api_type, "uber-pipeline",
- false);
- }
- }
- void ShaderCache::ClearCaches()
- {
- ClearPipelineCache(m_gx_pipeline_cache, m_gx_pipeline_disk_cache);
- ClearShaderCache(m_vs_cache);
- ClearShaderCache(m_gs_cache);
- ClearShaderCache(m_ps_cache);
- ClearPipelineCache(m_gx_uber_pipeline_cache, m_gx_uber_pipeline_disk_cache);
- ClearShaderCache(m_uber_vs_cache);
- ClearShaderCache(m_uber_ps_cache);
- m_screen_quad_vertex_shader.reset();
- m_texture_copy_vertex_shader.reset();
- m_efb_copy_vertex_shader.reset();
- m_texcoord_geometry_shader.reset();
- m_color_geometry_shader.reset();
- m_texture_copy_pixel_shader.reset();
- m_color_pixel_shader.reset();
- m_efb_copy_to_vram_pipelines.clear();
- m_efb_copy_to_ram_pipelines.clear();
- m_copy_rgba8_pipeline.reset();
- m_rgba8_stereo_copy_pipeline.reset();
- for (auto& pipeline : m_palette_conversion_pipelines)
- pipeline.reset();
- m_texture_reinterpret_pipelines.clear();
- m_texture_decoding_shaders.clear();
- SETSTAT(g_stats.num_pixel_shaders_created, 0);
- SETSTAT(g_stats.num_pixel_shaders_alive, 0);
- SETSTAT(g_stats.num_vertex_shaders_created, 0);
- SETSTAT(g_stats.num_vertex_shaders_alive, 0);
- }
- void ShaderCache::CompileMissingPipelines()
- {
- // Queue all uids with a null pipeline for compilation.
- for (auto& it : m_gx_pipeline_cache)
- {
- if (!it.second.first)
- QueuePipelineCompile(it.first, COMPILE_PRIORITY_SHADERCACHE_PIPELINE);
- }
- for (auto& it : m_gx_uber_pipeline_cache)
- {
- if (!it.second.first)
- QueueUberPipelineCompile(it.first, COMPILE_PRIORITY_UBERSHADER_PIPELINE);
- }
- }
- std::unique_ptr<AbstractShader> ShaderCache::CompileVertexShader(const VertexShaderUid& uid) const
- {
- const ShaderCode source_code =
- GenerateVertexShaderCode(m_api_type, m_host_config, uid.GetUidData());
- return g_gfx->CreateShaderFromSource(ShaderStage::Vertex, source_code.GetBuffer());
- }
- std::unique_ptr<AbstractShader>
- ShaderCache::CompileVertexUberShader(const UberShader::VertexShaderUid& uid) const
- {
- const ShaderCode source_code =
- UberShader::GenVertexShader(m_api_type, m_host_config, uid.GetUidData());
- return g_gfx->CreateShaderFromSource(ShaderStage::Vertex, source_code.GetBuffer(),
- fmt::to_string(*uid.GetUidData()));
- }
- std::unique_ptr<AbstractShader> ShaderCache::CompilePixelShader(const PixelShaderUid& uid) const
- {
- const ShaderCode source_code =
- GeneratePixelShaderCode(m_api_type, m_host_config, uid.GetUidData(), {});
- return g_gfx->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer());
- }
- std::unique_ptr<AbstractShader>
- ShaderCache::CompilePixelUberShader(const UberShader::PixelShaderUid& uid) const
- {
- const ShaderCode source_code =
- UberShader::GenPixelShader(m_api_type, m_host_config, uid.GetUidData(), {});
- return g_gfx->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer(),
- fmt::to_string(*uid.GetUidData()));
- }
- const AbstractShader* ShaderCache::InsertVertexShader(const VertexShaderUid& uid,
- std::unique_ptr<AbstractShader> shader)
- {
- auto& entry = m_vs_cache.shader_map[uid];
- entry.pending = false;
- if (shader && !entry.shader)
- {
- if (g_ActiveConfig.bShaderCache && g_ActiveConfig.backend_info.bSupportsShaderBinaries)
- {
- auto binary = shader->GetBinary();
- if (!binary.empty())
- m_vs_cache.disk_cache.Append(uid, binary.data(), static_cast<u32>(binary.size()));
- }
- INCSTAT(g_stats.num_vertex_shaders_created);
- INCSTAT(g_stats.num_vertex_shaders_alive);
- entry.shader = std::move(shader);
- }
- return entry.shader.get();
- }
- const AbstractShader* ShaderCache::InsertVertexUberShader(const UberShader::VertexShaderUid& uid,
- std::unique_ptr<AbstractShader> shader)
- {
- auto& entry = m_uber_vs_cache.shader_map[uid];
- entry.pending = false;
- if (shader && !entry.shader)
- {
- if (g_ActiveConfig.bShaderCache && g_ActiveConfig.backend_info.bSupportsShaderBinaries)
- {
- auto binary = shader->GetBinary();
- if (!binary.empty())
- m_uber_vs_cache.disk_cache.Append(uid, binary.data(), static_cast<u32>(binary.size()));
- }
- INCSTAT(g_stats.num_vertex_shaders_created);
- INCSTAT(g_stats.num_vertex_shaders_alive);
- entry.shader = std::move(shader);
- }
- return entry.shader.get();
- }
- const AbstractShader* ShaderCache::InsertPixelShader(const PixelShaderUid& uid,
- std::unique_ptr<AbstractShader> shader)
- {
- auto& entry = m_ps_cache.shader_map[uid];
- entry.pending = false;
- if (shader && !entry.shader)
- {
- if (g_ActiveConfig.bShaderCache && g_ActiveConfig.backend_info.bSupportsShaderBinaries)
- {
- auto binary = shader->GetBinary();
- if (!binary.empty())
- m_ps_cache.disk_cache.Append(uid, binary.data(), static_cast<u32>(binary.size()));
- }
- INCSTAT(g_stats.num_pixel_shaders_created);
- INCSTAT(g_stats.num_pixel_shaders_alive);
- entry.shader = std::move(shader);
- }
- return entry.shader.get();
- }
- const AbstractShader* ShaderCache::InsertPixelUberShader(const UberShader::PixelShaderUid& uid,
- std::unique_ptr<AbstractShader> shader)
- {
- auto& entry = m_uber_ps_cache.shader_map[uid];
- entry.pending = false;
- if (shader && !entry.shader)
- {
- if (g_ActiveConfig.bShaderCache && g_ActiveConfig.backend_info.bSupportsShaderBinaries)
- {
- auto binary = shader->GetBinary();
- if (!binary.empty())
- m_uber_ps_cache.disk_cache.Append(uid, binary.data(), static_cast<u32>(binary.size()));
- }
- INCSTAT(g_stats.num_pixel_shaders_created);
- INCSTAT(g_stats.num_pixel_shaders_alive);
- entry.shader = std::move(shader);
- }
- return entry.shader.get();
- }
- const AbstractShader* ShaderCache::CreateGeometryShader(const GeometryShaderUid& uid)
- {
- const ShaderCode source_code =
- GenerateGeometryShaderCode(m_api_type, m_host_config, uid.GetUidData());
- std::unique_ptr<AbstractShader> shader =
- g_gfx->CreateShaderFromSource(ShaderStage::Geometry, source_code.GetBuffer(),
- fmt::format("Geometry shader: {}", *uid.GetUidData()));
- auto& entry = m_gs_cache.shader_map[uid];
- entry.pending = false;
- if (shader && !entry.shader)
- {
- if (g_ActiveConfig.bShaderCache && g_ActiveConfig.backend_info.bSupportsShaderBinaries)
- {
- auto binary = shader->GetBinary();
- if (!binary.empty())
- m_gs_cache.disk_cache.Append(uid, binary.data(), static_cast<u32>(binary.size()));
- }
- entry.shader = std::move(shader);
- }
- return entry.shader.get();
- }
- bool ShaderCache::NeedsGeometryShader(const GeometryShaderUid& uid) const
- {
- return m_host_config.backend_geometry_shaders && !uid.GetUidData()->IsPassthrough();
- }
- bool ShaderCache::UseGeometryShaderForEFBCopies() const
- {
- return m_host_config.backend_geometry_shaders && m_host_config.stereo;
- }
- AbstractPipelineConfig ShaderCache::GetGXPipelineConfig(
- const NativeVertexFormat* vertex_format, const AbstractShader* vertex_shader,
- const AbstractShader* geometry_shader, const AbstractShader* pixel_shader,
- const RasterizationState& rasterization_state, const DepthState& depth_state,
- const BlendingState& blending_state, AbstractPipelineUsage usage)
- {
- AbstractPipelineConfig config = {};
- config.usage = usage;
- config.vertex_format = vertex_format;
- config.vertex_shader = vertex_shader;
- config.geometry_shader = geometry_shader;
- config.pixel_shader = pixel_shader;
- config.rasterization_state = rasterization_state;
- config.depth_state = depth_state;
- config.blending_state = blending_state;
- config.framebuffer_state = g_framebuffer_manager->GetEFBFramebufferState();
- return config;
- }
- /// Edits the UID based on driver bugs and other special configurations
- static GXPipelineUid ApplyDriverBugs(const GXPipelineUid& in)
- {
- GXPipelineUid out;
- // TODO: static_assert(std::is_trivially_copyable_v<GXPipelineUid>);
- // GXPipelineUid is not trivially copyable because RasterizationState and BlendingState aren't
- // either, but we can pretend it is for now. This will be improved after PR #10848 is finished.
- memcpy(static_cast<void*>(&out), static_cast<const void*>(&in), sizeof(out)); // copy padding
- pixel_shader_uid_data* ps = out.ps_uid.GetUidData();
- BlendingState& blend = out.blending_state;
- if (ps->ztest == EmulatedZ::ForcedEarly && !out.depth_state.updateenable)
- {
- // No need to force early depth test if you're not writing z
- ps->ztest = EmulatedZ::Early;
- }
- // If framebuffer fetch is available, we can emulate logic ops in the fragment shader
- // and don't need the below blend approximation
- if (blend.logicopenable && !g_ActiveConfig.backend_info.bSupportsLogicOp &&
- !g_ActiveConfig.backend_info.bSupportsFramebufferFetch)
- {
- if (!blend.LogicOpApproximationIsExact())
- WARN_LOG_FMT(VIDEO,
- "Approximating logic op with blending, this will produce incorrect rendering.");
- if (blend.LogicOpApproximationWantsShaderHelp())
- {
- ps->emulate_logic_op_with_blend = true;
- ps->logic_op_mode = static_cast<u32>(blend.logicmode.Value());
- }
- blend.ApproximateLogicOpWithBlending();
- }
- const bool benefits_from_ps_dual_source_off =
- (!g_ActiveConfig.backend_info.bSupportsDualSourceBlend &&
- g_ActiveConfig.backend_info.bSupportsFramebufferFetch) ||
- DriverDetails::HasBug(DriverDetails::BUG_BROKEN_DUAL_SOURCE_BLENDING);
- if (benefits_from_ps_dual_source_off && !blend.RequiresDualSrc())
- {
- // Only use dual-source blending when required on drivers that don't support it very well.
- ps->no_dual_src = true;
- blend.usedualsrc = false;
- }
- if (g_ActiveConfig.backend_info.bSupportsFramebufferFetch)
- {
- bool fbfetch_blend = false;
- if ((DriverDetails::HasBug(DriverDetails::BUG_BROKEN_DISCARD_WITH_EARLY_Z) ||
- !g_ActiveConfig.backend_info.bSupportsEarlyZ) &&
- ps->ztest == EmulatedZ::ForcedEarly)
- {
- ps->ztest = EmulatedZ::EarlyWithFBFetch;
- fbfetch_blend |= static_cast<bool>(out.blending_state.blendenable);
- ps->no_dual_src = true;
- }
- fbfetch_blend |= blend.logicopenable && !g_ActiveConfig.backend_info.bSupportsLogicOp;
- fbfetch_blend |= blend.usedualsrc && !g_ActiveConfig.backend_info.bSupportsDualSourceBlend;
- if (fbfetch_blend)
- {
- ps->no_dual_src = true;
- if (blend.logicopenable)
- {
- ps->logic_op_enable = true;
- ps->logic_op_mode = static_cast<u32>(blend.logicmode.Value());
- blend.logicopenable = false;
- }
- if (blend.blendenable)
- {
- ps->blend_enable = true;
- ps->blend_src_factor = blend.srcfactor;
- ps->blend_src_factor_alpha = blend.srcfactoralpha;
- ps->blend_dst_factor = blend.dstfactor;
- ps->blend_dst_factor_alpha = blend.dstfactoralpha;
- ps->blend_subtract = blend.subtract;
- ps->blend_subtract_alpha = blend.subtractAlpha;
- blend.blendenable = false;
- }
- }
- }
- // force dual src off if we can't support it
- if (!g_ActiveConfig.backend_info.bSupportsDualSourceBlend)
- {
- ps->no_dual_src = true;
- blend.usedualsrc = false;
- }
- if (ps->ztest == EmulatedZ::ForcedEarly && !g_ActiveConfig.backend_info.bSupportsEarlyZ)
- {
- // These things should be false
- ASSERT(!ps->zfreeze);
- // ZCOMPLOC HACK:
- // The only way to emulate alpha test + early-z is to force early-z in the shader.
- // As this isn't available on all drivers and as we can't emulate this feature otherwise,
- // we are only able to choose which one we want to respect more.
- // Tests seem to have proven that writing depth even when the alpha test fails is more
- // important that a reliable alpha test, so we just force the alpha test to always succeed.
- // At least this seems to be less buggy.
- ps->ztest = EmulatedZ::EarlyWithZComplocHack;
- }
- if (g_ActiveConfig.UseVSForLinePointExpand() &&
- (out.rasterization_state.primitive == PrimitiveType::Points ||
- out.rasterization_state.primitive == PrimitiveType::Lines))
- {
- // All primitives are expanded to triangles in the vertex shader
- vertex_shader_uid_data* vs = out.vs_uid.GetUidData();
- const PortableVertexDeclaration& decl = out.vertex_format->GetVertexDeclaration();
- vs->position_has_3_elems = decl.position.components >= 3;
- vs->texcoord_elem_count = 0;
- for (int i = 0; i < 8; i++)
- {
- if (decl.texcoords[i].enable)
- {
- ASSERT(decl.texcoords[i].components <= 3);
- vs->texcoord_elem_count |= decl.texcoords[i].components << (i * 2);
- }
- }
- out.vertex_format = nullptr;
- if (out.rasterization_state.primitive == PrimitiveType::Points)
- vs->vs_expand = VSExpand::Point;
- else
- vs->vs_expand = VSExpand::Line;
- PrimitiveType prim = g_ActiveConfig.backend_info.bSupportsPrimitiveRestart ?
- PrimitiveType::TriangleStrip :
- PrimitiveType::Triangles;
- out.rasterization_state.primitive = prim;
- out.gs_uid.GetUidData()->primitive_type = static_cast<u32>(prim);
- }
- return out;
- }
- std::optional<AbstractPipelineConfig>
- ShaderCache::GetGXPipelineConfig(const GXPipelineUid& config_in)
- {
- GXPipelineUid config = ApplyDriverBugs(config_in);
- const AbstractShader* vs;
- auto vs_iter = m_vs_cache.shader_map.find(config.vs_uid);
- if (vs_iter != m_vs_cache.shader_map.end() && !vs_iter->second.pending)
- vs = vs_iter->second.shader.get();
- else
- vs = InsertVertexShader(config.vs_uid, CompileVertexShader(config.vs_uid));
- PixelShaderUid ps_uid = config.ps_uid;
- ClearUnusedPixelShaderUidBits(m_api_type, m_host_config, &ps_uid);
- const AbstractShader* ps;
- auto ps_iter = m_ps_cache.shader_map.find(ps_uid);
- if (ps_iter != m_ps_cache.shader_map.end() && !ps_iter->second.pending)
- ps = ps_iter->second.shader.get();
- else
- ps = InsertPixelShader(ps_uid, CompilePixelShader(ps_uid));
- if (!vs || !ps)
- return {};
- const AbstractShader* gs = nullptr;
- if (NeedsGeometryShader(config.gs_uid))
- {
- auto gs_iter = m_gs_cache.shader_map.find(config.gs_uid);
- if (gs_iter != m_gs_cache.shader_map.end() && !gs_iter->second.pending)
- gs = gs_iter->second.shader.get();
- else
- gs = CreateGeometryShader(config.gs_uid);
- if (!gs)
- return {};
- }
- return GetGXPipelineConfig(config.vertex_format, vs, gs, ps, config.rasterization_state,
- config.depth_state, config.blending_state, AbstractPipelineUsage::GX);
- }
- /// Edits the UID based on driver bugs and other special configurations
- static GXUberPipelineUid ApplyDriverBugs(const GXUberPipelineUid& in)
- {
- GXUberPipelineUid out;
- // TODO: static_assert(std::is_trivially_copyable_v<GXUberPipelineUid>);
- // GXUberPipelineUid is not trivially copyable because RasterizationState and BlendingState aren't
- // either, but we can pretend it is for now. This will be improved after PR #10848 is finished.
- memcpy(static_cast<void*>(&out), static_cast<const void*>(&in), sizeof(out)); // Copy padding
- if (g_ActiveConfig.backend_info.bSupportsDynamicVertexLoader)
- out.vertex_format = nullptr;
- // If framebuffer fetch is available, we can emulate logic ops in the fragment shader
- // and don't need the below blend approximation
- if (out.blending_state.logicopenable && !g_ActiveConfig.backend_info.bSupportsLogicOp &&
- !g_ActiveConfig.backend_info.bSupportsFramebufferFetch)
- {
- if (!out.blending_state.LogicOpApproximationIsExact())
- WARN_LOG_FMT(VIDEO,
- "Approximating logic op with blending, this will produce incorrect rendering.");
- out.blending_state.ApproximateLogicOpWithBlending();
- }
- if (g_ActiveConfig.backend_info.bSupportsFramebufferFetch)
- {
- // Always blend in shader
- out.blending_state.hex = 0;
- out.blending_state.colorupdate = in.blending_state.colorupdate.Value();
- out.blending_state.alphaupdate = in.blending_state.alphaupdate.Value();
- out.ps_uid.GetUidData()->no_dual_src = true;
- }
- else if (!g_ActiveConfig.backend_info.bSupportsDualSourceBlend ||
- (DriverDetails::HasBug(DriverDetails::BUG_BROKEN_DUAL_SOURCE_BLENDING) &&
- !out.blending_state.RequiresDualSrc()))
- {
- out.blending_state.usedualsrc = false;
- out.ps_uid.GetUidData()->no_dual_src = true;
- }
- if (g_ActiveConfig.UseVSForLinePointExpand())
- {
- // All primitives are expanded to triangles in the vertex shader
- PrimitiveType prim = g_ActiveConfig.backend_info.bSupportsPrimitiveRestart ?
- PrimitiveType::TriangleStrip :
- PrimitiveType::Triangles;
- out.rasterization_state.primitive = prim;
- out.gs_uid.GetUidData()->primitive_type = static_cast<u32>(prim);
- }
- return out;
- }
- std::optional<AbstractPipelineConfig>
- ShaderCache::GetGXPipelineConfig(const GXUberPipelineUid& config_in)
- {
- GXUberPipelineUid config = ApplyDriverBugs(config_in);
- const AbstractShader* vs;
- auto vs_iter = m_uber_vs_cache.shader_map.find(config.vs_uid);
- if (vs_iter != m_uber_vs_cache.shader_map.end() && !vs_iter->second.pending)
- vs = vs_iter->second.shader.get();
- else
- vs = InsertVertexUberShader(config.vs_uid, CompileVertexUberShader(config.vs_uid));
- UberShader::PixelShaderUid ps_uid = config.ps_uid;
- UberShader::ClearUnusedPixelShaderUidBits(m_api_type, m_host_config, &ps_uid);
- const AbstractShader* ps;
- auto ps_iter = m_uber_ps_cache.shader_map.find(ps_uid);
- if (ps_iter != m_uber_ps_cache.shader_map.end() && !ps_iter->second.pending)
- ps = ps_iter->second.shader.get();
- else
- ps = InsertPixelUberShader(ps_uid, CompilePixelUberShader(ps_uid));
- if (!vs || !ps)
- return {};
- const AbstractShader* gs = nullptr;
- if (NeedsGeometryShader(config.gs_uid))
- {
- auto gs_iter = m_gs_cache.shader_map.find(config.gs_uid);
- if (gs_iter != m_gs_cache.shader_map.end() && !gs_iter->second.pending)
- gs = gs_iter->second.shader.get();
- else
- gs = CreateGeometryShader(config.gs_uid);
- if (!gs)
- return {};
- }
- return GetGXPipelineConfig(config.vertex_format, vs, gs, ps, config.rasterization_state,
- config.depth_state, config.blending_state,
- AbstractPipelineUsage::GXUber);
- }
- const AbstractPipeline* ShaderCache::InsertGXPipeline(const GXPipelineUid& config,
- std::unique_ptr<AbstractPipeline> pipeline)
- {
- auto& entry = m_gx_pipeline_cache[config];
- entry.second = false;
- if (!entry.first && pipeline)
- {
- entry.first = std::move(pipeline);
- if (g_ActiveConfig.bShaderCache)
- {
- auto cache_data = entry.first->GetCacheData();
- if (!cache_data.empty())
- {
- SerializedGXPipelineUid disk_uid;
- SerializePipelineUid(config, disk_uid);
- m_gx_pipeline_disk_cache.Append(disk_uid, cache_data.data(),
- static_cast<u32>(cache_data.size()));
- }
- }
- }
- return entry.first.get();
- }
- const AbstractPipeline*
- ShaderCache::InsertGXUberPipeline(const GXUberPipelineUid& config,
- std::unique_ptr<AbstractPipeline> pipeline)
- {
- auto& entry = m_gx_uber_pipeline_cache[config];
- entry.second = false;
- if (!entry.first && pipeline)
- {
- entry.first = std::move(pipeline);
- if (g_ActiveConfig.bShaderCache)
- {
- auto cache_data = entry.first->GetCacheData();
- if (!cache_data.empty())
- {
- SerializedGXUberPipelineUid disk_uid;
- SerializePipelineUid(config, disk_uid);
- m_gx_uber_pipeline_disk_cache.Append(disk_uid, cache_data.data(),
- static_cast<u32>(cache_data.size()));
- }
- }
- }
- return entry.first.get();
- }
- void ShaderCache::LoadPipelineUIDCache()
- {
- constexpr u32 CACHE_FILE_MAGIC = 0x44495550; // PUID
- constexpr size_t CACHE_HEADER_SIZE = sizeof(u32) + sizeof(u32);
- std::string filename =
- File::GetUserPath(D_CACHE_IDX) + SConfig::GetInstance().GetGameID() + ".uidcache";
- if (m_gx_pipeline_uid_cache_file.Open(filename, "rb+"))
- {
- // If an existing case exists, validate the version before reading entries.
- u32 existing_magic;
- u32 existing_version;
- bool uid_file_valid = false;
- if (m_gx_pipeline_uid_cache_file.ReadBytes(&existing_magic, sizeof(existing_magic)) &&
- m_gx_pipeline_uid_cache_file.ReadBytes(&existing_version, sizeof(existing_version)) &&
- existing_magic == CACHE_FILE_MAGIC && existing_version == GX_PIPELINE_UID_VERSION)
- {
- // Ensure the expected size matches the actual size of the file. If it doesn't, it means
- // the cache file may be corrupted, and we should not proceed with loading potentially
- // garbage or invalid UIDs.
- const u64 file_size = m_gx_pipeline_uid_cache_file.GetSize();
- const size_t uid_count =
- static_cast<size_t>(file_size - CACHE_HEADER_SIZE) / sizeof(SerializedGXPipelineUid);
- const size_t expected_size = uid_count * sizeof(SerializedGXPipelineUid) + CACHE_HEADER_SIZE;
- uid_file_valid = file_size == expected_size;
- if (uid_file_valid)
- {
- for (size_t i = 0; i < uid_count; i++)
- {
- SerializedGXPipelineUid serialized_uid;
- if (m_gx_pipeline_uid_cache_file.ReadBytes(&serialized_uid, sizeof(serialized_uid)))
- {
- // This just adds the pipeline to the map, it is compiled later.
- AddSerializedGXPipelineUID(serialized_uid);
- }
- else
- {
- uid_file_valid = false;
- break;
- }
- }
- }
- // We open the file for reading and writing, so we must seek to the end before writing.
- if (uid_file_valid)
- uid_file_valid = m_gx_pipeline_uid_cache_file.Seek(expected_size, File::SeekOrigin::Begin);
- }
- // If the file is invalid, close it. We re-open and truncate it below.
- if (!uid_file_valid)
- m_gx_pipeline_uid_cache_file.Close();
- }
- // If the file is not open, it means it was either corrupted or didn't exist.
- if (!m_gx_pipeline_uid_cache_file.IsOpen())
- {
- if (m_gx_pipeline_uid_cache_file.Open(filename, "wb"))
- {
- // Write the version identifier.
- m_gx_pipeline_uid_cache_file.WriteBytes(&CACHE_FILE_MAGIC, sizeof(GX_PIPELINE_UID_VERSION));
- m_gx_pipeline_uid_cache_file.WriteBytes(&GX_PIPELINE_UID_VERSION,
- sizeof(GX_PIPELINE_UID_VERSION));
- // Write any current UIDs out to the file.
- // This way, if we load a UID cache where the data was incomplete (e.g. Dolphin crashed),
- // we don't lose the existing UIDs which were previously at the beginning.
- for (const auto& it : m_gx_pipeline_cache)
- AppendGXPipelineUID(it.first);
- }
- }
- INFO_LOG_FMT(VIDEO, "Read {} pipeline UIDs from {}", m_gx_pipeline_cache.size(), filename);
- }
- void ShaderCache::ClosePipelineUIDCache()
- {
- // This is left as a method in case we need to append extra data to the file in the future.
- m_gx_pipeline_uid_cache_file.Close();
- }
- void ShaderCache::AddSerializedGXPipelineUID(const SerializedGXPipelineUid& uid)
- {
- GXPipelineUid real_uid;
- UnserializePipelineUid(uid, real_uid);
- auto iter = m_gx_pipeline_cache.find(real_uid);
- if (iter != m_gx_pipeline_cache.end())
- return;
- // Flag it as empty with a null pipeline object, for later compilation.
- auto& entry = m_gx_pipeline_cache[real_uid];
- entry.second = false;
- }
- void ShaderCache::AppendGXPipelineUID(const GXPipelineUid& config)
- {
- if (!m_gx_pipeline_uid_cache_file.IsOpen())
- return;
- SerializedGXPipelineUid disk_uid;
- SerializePipelineUid(config, disk_uid);
- if (!m_gx_pipeline_uid_cache_file.WriteBytes(&disk_uid, sizeof(disk_uid)))
- {
- WARN_LOG_FMT(VIDEO, "Writing pipeline UID to cache failed, closing file.");
- m_gx_pipeline_uid_cache_file.Close();
- }
- }
- void ShaderCache::QueueVertexShaderCompile(const VertexShaderUid& uid, u32 priority)
- {
- class VertexShaderWorkItem final : public AsyncShaderCompiler::WorkItem
- {
- public:
- VertexShaderWorkItem(ShaderCache* shader_cache_, const VertexShaderUid& uid_)
- : shader_cache(shader_cache_), uid(uid_)
- {
- }
- bool Compile() override
- {
- shader = shader_cache->CompileVertexShader(uid);
- return true;
- }
- void Retrieve() override { shader_cache->InsertVertexShader(uid, std::move(shader)); }
- private:
- ShaderCache* shader_cache;
- std::unique_ptr<AbstractShader> shader;
- VertexShaderUid uid;
- };
- m_vs_cache.shader_map[uid].pending = true;
- auto wi = m_async_shader_compiler->CreateWorkItem<VertexShaderWorkItem>(this, uid);
- m_async_shader_compiler->QueueWorkItem(std::move(wi), priority);
- }
- void ShaderCache::QueueVertexUberShaderCompile(const UberShader::VertexShaderUid& uid, u32 priority)
- {
- class VertexUberShaderWorkItem final : public AsyncShaderCompiler::WorkItem
- {
- public:
- VertexUberShaderWorkItem(ShaderCache* shader_cache_, const UberShader::VertexShaderUid& uid_)
- : shader_cache(shader_cache_), uid(uid_)
- {
- }
- bool Compile() override
- {
- shader = shader_cache->CompileVertexUberShader(uid);
- return true;
- }
- void Retrieve() override { shader_cache->InsertVertexUberShader(uid, std::move(shader)); }
- private:
- ShaderCache* shader_cache;
- std::unique_ptr<AbstractShader> shader;
- UberShader::VertexShaderUid uid;
- };
- m_uber_vs_cache.shader_map[uid].pending = true;
- auto wi = m_async_shader_compiler->CreateWorkItem<VertexUberShaderWorkItem>(this, uid);
- m_async_shader_compiler->QueueWorkItem(std::move(wi), priority);
- }
- void ShaderCache::QueuePixelShaderCompile(const PixelShaderUid& uid, u32 priority)
- {
- class PixelShaderWorkItem final : public AsyncShaderCompiler::WorkItem
- {
- public:
- PixelShaderWorkItem(ShaderCache* shader_cache_, const PixelShaderUid& uid_)
- : shader_cache(shader_cache_), uid(uid_)
- {
- }
- bool Compile() override
- {
- shader = shader_cache->CompilePixelShader(uid);
- return true;
- }
- void Retrieve() override { shader_cache->InsertPixelShader(uid, std::move(shader)); }
- private:
- ShaderCache* shader_cache;
- std::unique_ptr<AbstractShader> shader;
- PixelShaderUid uid;
- };
- m_ps_cache.shader_map[uid].pending = true;
- auto wi = m_async_shader_compiler->CreateWorkItem<PixelShaderWorkItem>(this, uid);
- m_async_shader_compiler->QueueWorkItem(std::move(wi), priority);
- }
- void ShaderCache::QueuePixelUberShaderCompile(const UberShader::PixelShaderUid& uid, u32 priority)
- {
- class PixelUberShaderWorkItem final : public AsyncShaderCompiler::WorkItem
- {
- public:
- PixelUberShaderWorkItem(ShaderCache* shader_cache_, const UberShader::PixelShaderUid& uid_)
- : shader_cache(shader_cache_), uid(uid_)
- {
- }
- bool Compile() override
- {
- shader = shader_cache->CompilePixelUberShader(uid);
- return true;
- }
- void Retrieve() override { shader_cache->InsertPixelUberShader(uid, std::move(shader)); }
- private:
- ShaderCache* shader_cache;
- std::unique_ptr<AbstractShader> shader;
- UberShader::PixelShaderUid uid;
- };
- m_uber_ps_cache.shader_map[uid].pending = true;
- auto wi = m_async_shader_compiler->CreateWorkItem<PixelUberShaderWorkItem>(this, uid);
- m_async_shader_compiler->QueueWorkItem(std::move(wi), priority);
- }
- void ShaderCache::QueuePipelineCompile(const GXPipelineUid& uid, u32 priority)
- {
- class PipelineWorkItem final : public AsyncShaderCompiler::WorkItem
- {
- public:
- PipelineWorkItem(ShaderCache* shader_cache_, const GXPipelineUid& uid_, u32 priority_)
- : shader_cache(shader_cache_), uid(uid_), priority(priority_)
- {
- // 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 (SetStagesReady())
- config = shader_cache->GetGXPipelineConfig(uid);
- }
- bool SetStagesReady()
- {
- stages_ready = true;
- GXPipelineUid actual_uid = ApplyDriverBugs(uid);
- auto vs_it = shader_cache->m_vs_cache.shader_map.find(actual_uid.vs_uid);
- 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())
- shader_cache->QueueVertexShaderCompile(actual_uid.vs_uid, priority);
- PixelShaderUid ps_uid = actual_uid.ps_uid;
- ClearUnusedPixelShaderUidBits(shader_cache->m_api_type, shader_cache->m_host_config, &ps_uid);
- auto ps_it = shader_cache->m_ps_cache.shader_map.find(ps_uid);
- 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())
- shader_cache->QueuePixelShaderCompile(ps_uid, priority);
- return stages_ready;
- }
- bool Compile() override
- {
- if (config)
- pipeline = g_gfx->CreatePipeline(*config);
- return true;
- }
- void Retrieve() override
- {
- if (stages_ready)
- {
- shader_cache->InsertGXPipeline(uid, std::move(pipeline));
- }
- else
- {
- // Re-queue for next frame.
- auto wi = shader_cache->m_async_shader_compiler->CreateWorkItem<PipelineWorkItem>(
- shader_cache, uid, priority);
- shader_cache->m_async_shader_compiler->QueueWorkItem(std::move(wi), priority);
- }
- }
- private:
- ShaderCache* shader_cache;
- std::unique_ptr<AbstractPipeline> pipeline;
- GXPipelineUid uid;
- u32 priority;
- std::optional<AbstractPipelineConfig> config;
- bool stages_ready;
- };
- auto wi = m_async_shader_compiler->CreateWorkItem<PipelineWorkItem>(this, uid, priority);
- m_async_shader_compiler->QueueWorkItem(std::move(wi), priority);
- m_gx_pipeline_cache[uid].second = true;
- }
- void ShaderCache::QueueUberPipelineCompile(const GXUberPipelineUid& uid, u32 priority)
- {
- class UberPipelineWorkItem final : public AsyncShaderCompiler::WorkItem
- {
- public:
- UberPipelineWorkItem(ShaderCache* shader_cache_, const GXUberPipelineUid& uid_, u32 priority_)
- : shader_cache(shader_cache_), uid(uid_), priority(priority_)
- {
- // 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 (SetStagesReady())
- config = shader_cache->GetGXPipelineConfig(uid);
- }
- bool SetStagesReady()
- {
- stages_ready = true;
- GXUberPipelineUid actual_uid = ApplyDriverBugs(uid);
- auto vs_it = shader_cache->m_uber_vs_cache.shader_map.find(actual_uid.vs_uid);
- stages_ready &=
- 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())
- shader_cache->QueueVertexUberShaderCompile(actual_uid.vs_uid, priority);
- UberShader::PixelShaderUid ps_uid = actual_uid.ps_uid;
- UberShader::ClearUnusedPixelShaderUidBits(shader_cache->m_api_type,
- shader_cache->m_host_config, &ps_uid);
- auto ps_it = shader_cache->m_uber_ps_cache.shader_map.find(ps_uid);
- stages_ready &=
- 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())
- shader_cache->QueuePixelUberShaderCompile(ps_uid, priority);
- return stages_ready;
- }
- bool Compile() override
- {
- if (config)
- UberPipeline = g_gfx->CreatePipeline(*config);
- return true;
- }
- void Retrieve() override
- {
- if (stages_ready)
- {
- shader_cache->InsertGXUberPipeline(uid, std::move(UberPipeline));
- }
- else
- {
- // Re-queue for next frame.
- auto wi = shader_cache->m_async_shader_compiler->CreateWorkItem<UberPipelineWorkItem>(
- shader_cache, uid, priority);
- shader_cache->m_async_shader_compiler->QueueWorkItem(std::move(wi), priority);
- }
- }
- private:
- ShaderCache* shader_cache;
- std::unique_ptr<AbstractPipeline> UberPipeline;
- GXUberPipelineUid uid;
- u32 priority;
- std::optional<AbstractPipelineConfig> config;
- bool stages_ready;
- };
- auto wi = m_async_shader_compiler->CreateWorkItem<UberPipelineWorkItem>(this, uid, priority);
- m_async_shader_compiler->QueueWorkItem(std::move(wi), priority);
- m_gx_uber_pipeline_cache[uid].second = true;
- }
- void ShaderCache::QueueUberShaderPipelines()
- {
- // Create a dummy vertex format with no attributes.
- // All attributes will be enabled in GetUberVertexFormat.
- PortableVertexDeclaration dummy_vertex_decl = {};
- dummy_vertex_decl.position.components = 4;
- dummy_vertex_decl.position.type = ComponentFormat::Float;
- dummy_vertex_decl.position.enable = true;
- dummy_vertex_decl.stride = sizeof(float) * 4;
- NativeVertexFormat* dummy_vertex_format =
- VertexLoaderManager::GetUberVertexFormat(dummy_vertex_decl);
- auto QueueDummyPipeline =
- [&](const UberShader::VertexShaderUid& vs_uid, const GeometryShaderUid& gs_uid,
- const UberShader::PixelShaderUid& ps_uid, const BlendingState& blend) {
- GXUberPipelineUid config;
- config.vertex_format = dummy_vertex_format;
- config.vs_uid = vs_uid;
- config.gs_uid = gs_uid;
- config.ps_uid = ps_uid;
- config.rasterization_state = RenderState::GetCullBackFaceRasterizationState(
- static_cast<PrimitiveType>(gs_uid.GetUidData()->primitive_type));
- config.depth_state = RenderState::GetNoDepthTestingDepthState();
- config.blending_state = blend;
- if (ps_uid.GetUidData()->uint_output)
- {
- // uint_output is only ever enabled when logic ops are enabled.
- config.blending_state.logicopenable = true;
- config.blending_state.logicmode = LogicOp::And;
- }
- auto iter = m_gx_uber_pipeline_cache.find(config);
- if (iter != m_gx_uber_pipeline_cache.end())
- return;
- auto& entry = m_gx_uber_pipeline_cache[config];
- entry.second = false;
- };
- // Populate the pipeline configs with empty entries, these will be compiled afterwards.
- UberShader::EnumerateVertexShaderUids([&](const UberShader::VertexShaderUid& vuid) {
- UberShader::EnumeratePixelShaderUids([&](const UberShader::PixelShaderUid& puid) {
- // UIDs must have compatible texgens, a mismatching combination will never be queried.
- if (vuid.GetUidData()->num_texgens != puid.GetUidData()->num_texgens)
- return;
- UberShader::PixelShaderUid cleared_puid = puid;
- UberShader::ClearUnusedPixelShaderUidBits(m_api_type, m_host_config, &cleared_puid);
- EnumerateGeometryShaderUids([&](const GeometryShaderUid& guid) {
- if (guid.GetUidData()->numTexGens != vuid.GetUidData()->num_texgens ||
- (!guid.GetUidData()->IsPassthrough() && !m_host_config.backend_geometry_shaders))
- {
- return;
- }
- BlendingState blend = RenderState::GetNoBlendingBlendState();
- QueueDummyPipeline(vuid, guid, cleared_puid, blend);
- if (g_ActiveConfig.backend_info.bSupportsDynamicVertexLoader)
- {
- // Not all GPUs need all the pipeline state compiled into shaders, so they tend to key
- // compiled shaders based on some subset of the pipeline state.
- // Some test results:
- // (GPUs tested: AMD Radeon Pro 5600M, Nvidia GT 750M, Intel UHD 630,
- // Intel Iris Pro 5200, Apple M1)
- // MacOS Metal:
- // - AMD, Nvidia, Intel GPUs: Shaders are keyed on vertex layout and whether or not
- // dual source blend is enabled. That's it.
- // - Apple GPUs: Shaders are keyed on vertex layout and all blending settings. We use
- // framebuffer fetch here, so the only blending settings used by ubershaders are the
- // alphaupdate and colorupdate ones. Also keyed on primitive type, but Metal supports
- // setting it to "unknown" and we do for ubershaders (but MoltenVK won't).
- // Windows Vulkan:
- // - AMD, Nvidia: Definitely keyed on dual source blend, but the others seem more random
- // Changing a setting on one shader will require a recompile, but changing the same
- // setting on another won't. Compiling a copy with alphaupdate off, colorupdate off,
- // and one with DSB on seems to get pretty good coverage though.
- // Windows D3D12:
- // - AMD: Keyed on dual source blend and vertex layout
- // - Nvidia Kepler: No recompiles for changes to vertex layout or blend
- blend.alphaupdate = false;
- QueueDummyPipeline(vuid, guid, cleared_puid, blend);
- blend.alphaupdate = true;
- blend.colorupdate = false;
- QueueDummyPipeline(vuid, guid, cleared_puid, blend);
- blend.colorupdate = true;
- if (!cleared_puid.GetUidData()->no_dual_src && !cleared_puid.GetUidData()->uint_output)
- {
- blend.blendenable = true;
- blend.usedualsrc = true;
- blend.srcfactor = SrcBlendFactor::SrcAlpha;
- blend.dstfactor = DstBlendFactor::InvSrcAlpha;
- QueueDummyPipeline(vuid, guid, cleared_puid, blend);
- }
- }
- });
- });
- });
- }
- const AbstractPipeline*
- ShaderCache::GetEFBCopyToVRAMPipeline(const TextureConversionShaderGen::TCShaderUid& uid)
- {
- auto iter = m_efb_copy_to_vram_pipelines.find(uid);
- if (iter != m_efb_copy_to_vram_pipelines.end())
- return iter->second.get();
- auto shader_code = TextureConversionShaderGen::GeneratePixelShader(m_api_type, uid.GetUidData());
- auto shader = g_gfx->CreateShaderFromSource(
- ShaderStage::Pixel, shader_code.GetBuffer(),
- fmt::format("EFB copy to VRAM pixel shader: {}", *uid.GetUidData()));
- if (!shader)
- {
- m_efb_copy_to_vram_pipelines.emplace(uid, nullptr);
- return nullptr;
- }
- AbstractPipelineConfig config = {};
- config.vertex_format = nullptr;
- config.vertex_shader = m_efb_copy_vertex_shader.get();
- config.geometry_shader =
- UseGeometryShaderForEFBCopies() ? m_texcoord_geometry_shader.get() : nullptr;
- config.pixel_shader = shader.get();
- config.rasterization_state = RenderState::GetNoCullRasterizationState(PrimitiveType::Triangles);
- config.depth_state = RenderState::GetNoDepthTestingDepthState();
- config.blending_state = RenderState::GetNoBlendingBlendState();
- config.framebuffer_state = RenderState::GetRGBA8FramebufferState();
- config.usage = AbstractPipelineUsage::Utility;
- auto iiter = m_efb_copy_to_vram_pipelines.emplace(uid, g_gfx->CreatePipeline(config));
- return iiter.first->second.get();
- }
- const AbstractPipeline* ShaderCache::GetEFBCopyToRAMPipeline(const EFBCopyParams& uid)
- {
- auto iter = m_efb_copy_to_ram_pipelines.find(uid);
- if (iter != m_efb_copy_to_ram_pipelines.end())
- return iter->second.get();
- const std::string shader_code =
- TextureConversionShaderTiled::GenerateEncodingShader(uid, m_api_type);
- const auto shader = g_gfx->CreateShaderFromSource(
- ShaderStage::Pixel, shader_code, fmt::format("EFB copy to RAM pixel shader: {}", uid));
- if (!shader)
- {
- m_efb_copy_to_ram_pipelines.emplace(uid, nullptr);
- return nullptr;
- }
- AbstractPipelineConfig config = {};
- config.vertex_shader = m_screen_quad_vertex_shader.get();
- config.pixel_shader = shader.get();
- config.rasterization_state = RenderState::GetNoCullRasterizationState(PrimitiveType::Triangles);
- config.depth_state = RenderState::GetNoDepthTestingDepthState();
- config.blending_state = RenderState::GetNoBlendingBlendState();
- config.framebuffer_state = RenderState::GetColorFramebufferState(AbstractTextureFormat::BGRA8);
- config.usage = AbstractPipelineUsage::Utility;
- auto iiter = m_efb_copy_to_ram_pipelines.emplace(uid, g_gfx->CreatePipeline(config));
- return iiter.first->second.get();
- }
- bool ShaderCache::CompileSharedPipelines()
- {
- m_screen_quad_vertex_shader = g_gfx->CreateShaderFromSource(
- ShaderStage::Vertex, FramebufferShaderGen::GenerateScreenQuadVertexShader(),
- "Screen quad vertex shader");
- m_texture_copy_vertex_shader = g_gfx->CreateShaderFromSource(
- ShaderStage::Vertex, FramebufferShaderGen::GenerateTextureCopyVertexShader(),
- "Texture copy vertex shader");
- m_efb_copy_vertex_shader = g_gfx->CreateShaderFromSource(
- ShaderStage::Vertex, TextureConversionShaderGen::GenerateVertexShader(m_api_type).GetBuffer(),
- "EFB copy vertex shader");
- if (!m_screen_quad_vertex_shader || !m_texture_copy_vertex_shader || !m_efb_copy_vertex_shader)
- return false;
- if (UseGeometryShaderForEFBCopies())
- {
- m_texcoord_geometry_shader = g_gfx->CreateShaderFromSource(
- ShaderStage::Geometry, FramebufferShaderGen::GeneratePassthroughGeometryShader(1, 0),
- "Texcoord passthrough geometry shader");
- m_color_geometry_shader = g_gfx->CreateShaderFromSource(
- ShaderStage::Geometry, FramebufferShaderGen::GeneratePassthroughGeometryShader(0, 1),
- "Color passthrough geometry shader");
- if (!m_texcoord_geometry_shader || !m_color_geometry_shader)
- return false;
- }
- m_texture_copy_pixel_shader = g_gfx->CreateShaderFromSource(
- ShaderStage::Pixel, FramebufferShaderGen::GenerateTextureCopyPixelShader(),
- "Texture copy pixel shader");
- m_color_pixel_shader = g_gfx->CreateShaderFromSource(
- ShaderStage::Pixel, FramebufferShaderGen::GenerateColorPixelShader(), "Color pixel shader");
- if (!m_texture_copy_pixel_shader || !m_color_pixel_shader)
- return false;
- AbstractPipelineConfig config;
- config.vertex_format = nullptr;
- config.vertex_shader = m_texture_copy_vertex_shader.get();
- config.geometry_shader = nullptr;
- config.pixel_shader = m_texture_copy_pixel_shader.get();
- config.rasterization_state = RenderState::GetNoCullRasterizationState(PrimitiveType::Triangles);
- config.depth_state = RenderState::GetNoDepthTestingDepthState();
- config.blending_state = RenderState::GetNoBlendingBlendState();
- config.framebuffer_state = RenderState::GetRGBA8FramebufferState();
- config.usage = AbstractPipelineUsage::Utility;
- m_copy_rgba8_pipeline = g_gfx->CreatePipeline(config);
- if (!m_copy_rgba8_pipeline)
- return false;
- if (UseGeometryShaderForEFBCopies())
- {
- config.geometry_shader = m_texcoord_geometry_shader.get();
- m_rgba8_stereo_copy_pipeline = g_gfx->CreatePipeline(config);
- if (!m_rgba8_stereo_copy_pipeline)
- return false;
- }
- if (m_host_config.backend_palette_conversion)
- {
- config.vertex_shader = m_screen_quad_vertex_shader.get();
- config.geometry_shader = nullptr;
- for (size_t i = 0; i < NUM_PALETTE_CONVERSION_SHADERS; i++)
- {
- TLUTFormat format = static_cast<TLUTFormat>(i);
- auto shader = g_gfx->CreateShaderFromSource(
- ShaderStage::Pixel,
- TextureConversionShaderTiled::GeneratePaletteConversionShader(format, m_api_type),
- fmt::format("Palette conversion pixel shader: {}", format));
- if (!shader)
- return false;
- config.pixel_shader = shader.get();
- m_palette_conversion_pipelines[i] = g_gfx->CreatePipeline(config);
- if (!m_palette_conversion_pipelines[i])
- return false;
- }
- }
- return true;
- }
- const AbstractPipeline* ShaderCache::GetPaletteConversionPipeline(TLUTFormat format)
- {
- ASSERT(static_cast<size_t>(format) < NUM_PALETTE_CONVERSION_SHADERS);
- return m_palette_conversion_pipelines[static_cast<size_t>(format)].get();
- }
- const AbstractPipeline* ShaderCache::GetTextureReinterpretPipeline(TextureFormat from_format,
- TextureFormat to_format)
- {
- const auto key = std::make_pair(from_format, to_format);
- auto iter = m_texture_reinterpret_pipelines.find(key);
- if (iter != m_texture_reinterpret_pipelines.end())
- return iter->second.get();
- std::string shader_source =
- FramebufferShaderGen::GenerateTextureReinterpretShader(from_format, to_format);
- if (shader_source.empty())
- {
- m_texture_reinterpret_pipelines.emplace(key, nullptr);
- return nullptr;
- }
- std::unique_ptr<AbstractShader> shader = g_gfx->CreateShaderFromSource(
- ShaderStage::Pixel, shader_source,
- fmt::format("Texture reinterpret pixel shader: {} to {}", from_format, to_format));
- if (!shader)
- {
- m_texture_reinterpret_pipelines.emplace(key, nullptr);
- return nullptr;
- }
- AbstractPipelineConfig config;
- config.vertex_format = nullptr;
- config.vertex_shader = m_screen_quad_vertex_shader.get();
- config.geometry_shader = nullptr;
- config.pixel_shader = shader.get();
- config.rasterization_state = RenderState::GetNoCullRasterizationState(PrimitiveType::Triangles);
- config.depth_state = RenderState::GetNoDepthTestingDepthState();
- config.blending_state = RenderState::GetNoBlendingBlendState();
- config.framebuffer_state = RenderState::GetRGBA8FramebufferState();
- config.usage = AbstractPipelineUsage::Utility;
- auto iiter = m_texture_reinterpret_pipelines.emplace(key, g_gfx->CreatePipeline(config));
- return iiter.first->second.get();
- }
- const AbstractShader*
- ShaderCache::GetTextureDecodingShader(TextureFormat format,
- std::optional<TLUTFormat> palette_format)
- {
- const auto key = std::make_pair(static_cast<u32>(format),
- static_cast<u32>(palette_format.value_or(TLUTFormat::IA8)));
- const auto iter = m_texture_decoding_shaders.find(key);
- if (iter != m_texture_decoding_shaders.end())
- return iter->second.get();
- const std::string shader_source =
- TextureConversionShaderTiled::GenerateDecodingShader(format, palette_format, APIType::OpenGL);
- if (shader_source.empty())
- {
- m_texture_decoding_shaders.emplace(key, nullptr);
- return nullptr;
- }
- const std::string name =
- palette_format.has_value() ?
- fmt::format("Texture decoding compute shader: {}, {}", format, *palette_format) :
- fmt::format("Texture decoding compute shader: {}", format);
- std::unique_ptr<AbstractShader> shader =
- g_gfx->CreateShaderFromSource(ShaderStage::Compute, shader_source, name);
- if (!shader)
- {
- m_texture_decoding_shaders.emplace(key, nullptr);
- return nullptr;
- }
- const auto iiter = m_texture_decoding_shaders.emplace(key, std::move(shader));
- return iiter.first->second.get();
- }
- } // namespace VideoCommon
|