mirror of
https://github.com/libretro/parallel-gx1.git
synced 2026-11-07 04:26:01 -10:00
Where the driver compiles on threads of its own (GL_KHR_parallel_shader_compile or the ARB one), the asynchronous shader modes on OpenGL compile in the background on the frontend's context alone: a missing program's compile and link are started, a couple of work items a frame, and its draws use the ubershaders, or are skipped, until GL_COMPLETION_STATUS says the link is done; its link is checked, its variables set and its binary written to the cache then. A program drawn with before it is done is waited for there. A software rasterizer, which compiles when it first draws, compiles on the spot as before. The shader compiler gets a deferred mode for it, without threads, compiling the items waiting a few at a time, and AbstractPipeline tells whether a pipeline's compile is still running and whether it can be drawn with. With the driver's threads alone, every ubershader pipeline there can be is not built in the background.
1612 lines
58 KiB
C++
1612 lines
58 KiB
C++
// Copyright 2018 Dolphin Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include "VideoCommon/ShaderCache.h"
|
|
|
|
#include <utility>
|
|
|
|
#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/PipelineUtils.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"
|
|
|
|
|
|
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_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 = GetVideoEvents().after_frame_event.Register(
|
|
[this](Core::System&) { 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();
|
|
#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. Where the driver compiles on
|
|
* threads of its own instead, a few a frame (AsyncShaderCompiler
|
|
* deferred), every ubershader pipeline there can be is not built: each
|
|
* still costs the frontend's thread the start of its compile. */
|
|
if (g_ActiveConfig.UsingUberShaders() && CanCompileInBackground())
|
|
{
|
|
CompileMissingUberPipelines();
|
|
WaitForAsyncCompiler();
|
|
if (m_async_shader_compiler->HasWorkerThreads())
|
|
QueueUberShaderPipelines();
|
|
}
|
|
#else
|
|
// Queue ubershader precompiling if required.
|
|
if (g_ActiveConfig.UsingUberShaders())
|
|
QueueUberShaderPipelines();
|
|
#endif
|
|
|
|
// 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());
|
|
}
|
|
|
|
bool ShaderCache::CanCompileInBackground() const
|
|
{
|
|
return m_async_shader_compiler &&
|
|
(m_async_shader_compiler->HasWorkerThreads() || m_async_shader_compiler->IsDeferred());
|
|
}
|
|
|
|
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();
|
|
|
|
/* the cache data of those the driver has finished */
|
|
size_t i = 0;
|
|
while (i < m_unwritten_gx_pipelines.size())
|
|
{
|
|
auto it = m_gx_pipeline_cache.find(m_unwritten_gx_pipelines[i]);
|
|
if (it != m_gx_pipeline_cache.end() && it->second.first && it->second.first->IsBuilding())
|
|
{
|
|
i++;
|
|
continue;
|
|
}
|
|
if (it != m_gx_pipeline_cache.end() && it->second.first && it->second.first->IsReady())
|
|
WriteGXPipelineCacheData(it->first, it->second.first.get());
|
|
m_unwritten_gx_pipelines[i] = m_unwritten_gx_pipelines.back();
|
|
m_unwritten_gx_pipelines.pop_back();
|
|
}
|
|
}
|
|
|
|
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();
|
|
m_async_shader_compiler->ClearAllWork();
|
|
}
|
|
|
|
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);
|
|
#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();
|
|
|
|
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;
|
|
|
|
// Compiled while the frontend waits; nothing is drawn meanwhile.
|
|
constexpr auto no_progress = [](size_t, size_t) {};
|
|
|
|
|
|
while (running &&
|
|
(m_async_shader_compiler->HasPendingWork() || m_async_shader_compiler->HasCompletedWork()))
|
|
{
|
|
running = m_async_shader_compiler->WaitUntilCompletion(no_progress);
|
|
|
|
m_async_shader_compiler->RetrieveWorkItems();
|
|
}
|
|
}
|
|
|
|
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_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_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()
|
|
{
|
|
m_unwritten_gx_pipelines.clear();
|
|
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_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_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_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_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_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;
|
|
}
|
|
|
|
std::optional<AbstractPipelineConfig>
|
|
ShaderCache::GetGXPipelineConfig(const GXPipelineUid& config_in)
|
|
{
|
|
GXPipelineUid config = VideoCommon::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_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.logic_op_enable && !g_backend_info.bSupportsLogicOp &&
|
|
!g_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_backend_info.bSupportsFramebufferFetch)
|
|
{
|
|
// Always blend in shader
|
|
out.blending_state.hex = 0;
|
|
out.blending_state.color_update = in.blending_state.color_update.Value();
|
|
out.blending_state.alpha_update = in.blending_state.alpha_update.Value();
|
|
out.ps_uid.GetUidData()->no_dual_src = true;
|
|
}
|
|
else if (!g_backend_info.bSupportsDualSourceBlend ||
|
|
(DriverDetails::HasBug(DriverDetails::BUG_BROKEN_DUAL_SOURCE_BLENDING) &&
|
|
!out.blending_state.RequiresDualSrc()))
|
|
{
|
|
out.blending_state.use_dual_src = 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_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)
|
|
{
|
|
/* its data is there to read once the driver is done with it */
|
|
if (entry.first->IsBuilding())
|
|
m_unwritten_gx_pipelines.push_back(config);
|
|
else
|
|
WriteGXPipelineCacheData(config, entry.first.get());
|
|
}
|
|
}
|
|
|
|
return entry.first.get();
|
|
}
|
|
|
|
void ShaderCache::WriteGXPipelineCacheData(const GXPipelineUid& config,
|
|
const AbstractPipeline* pipeline)
|
|
{
|
|
auto cache_data = pipeline->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()));
|
|
}
|
|
}
|
|
|
|
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.
|
|
#ifdef __LIBRETRO__
|
|
if (m_gx_pipeline_uid_cache_file.IsOpen() && !m_pending_gx_pipeline_uids.empty() &&
|
|
!m_gx_pipeline_uid_cache_file.WriteArray(m_pending_gx_pipeline_uids.data(),
|
|
m_pending_gx_pipeline_uids.size()))
|
|
{
|
|
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);
|
|
m_unwritten_gx_pipelines.reserve(256);
|
|
}
|
|
|
|
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;
|
|
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);
|
|
#ifdef __LIBRETRO__
|
|
// Keep filesystem writes off the draw path. Save on reload or shutdown.
|
|
m_pending_gx_pipeline_uids.push_back(disk_uid);
|
|
#else
|
|
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();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
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_, GXPipelineUid uid_, u32 priority_)
|
|
: shader_cache(shader_cache_), uid(std::move(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_, GXUberPipelineUid uid_, u32 priority_)
|
|
: shader_cache(shader_cache_), uid(std::move(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.logic_op_enable = true;
|
|
config.blending_state.logic_mode = 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_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
|
|
// alpha_update and color_update 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 alpha_update off, color_update 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.alpha_update = false;
|
|
QueueDummyPipeline(vuid, guid, cleared_puid, blend);
|
|
blend.alpha_update = true;
|
|
blend.color_update = false;
|
|
QueueDummyPipeline(vuid, guid, cleared_puid, blend);
|
|
blend.color_update = true;
|
|
if (!cleared_puid.GetUidData()->no_dual_src && !cleared_puid.GetUidData()->uint_output)
|
|
{
|
|
blend.blend_enable = true;
|
|
blend.use_dual_src = true;
|
|
blend.src_factor = SrcBlendFactor::SrcAlpha;
|
|
blend.dst_factor = 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);
|
|
const auto [iter, inserted] = m_texture_reinterpret_pipelines.emplace(key, nullptr);
|
|
|
|
if (!inserted)
|
|
return iter->second.get();
|
|
|
|
std::string shader_source =
|
|
FramebufferShaderGen::GenerateTextureReinterpretShader(from_format, to_format);
|
|
if (shader_source.empty())
|
|
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)
|
|
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;
|
|
iter->second = g_gfx->CreatePipeline(config);
|
|
return iter->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, inserted] = m_texture_decoding_shaders.emplace(key, nullptr);
|
|
if (!inserted)
|
|
return iter->second.get();
|
|
|
|
const std::string shader_source =
|
|
TextureConversionShaderTiled::GenerateDecodingShader(format, palette_format, APIType::OpenGL);
|
|
if (shader_source.empty())
|
|
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)
|
|
return nullptr;
|
|
|
|
iter->second = std::move(shader);
|
|
return iter->second.get();
|
|
}
|
|
} // namespace VideoCommon
|