Files
parallel-gx1/Source/Core/VideoCommon/ShaderCache.h
T
LibretroAdmin e4bac5f75c Shaders: ubershaders by default, seeded per game
Shader compilation defaults to Async (UberShaders): a draw whose own
pipeline is not built yet is drawn with an ubershader one, exact
rendering, while its own builds on the compiler threads, which now
number as many as the machine has to spare (cores - 3, 1 to 4) instead
of one.

What is left to stutter is an ubershader pipeline itself, built on the
spot the first time a draw needs one: one per vertex layout and fixed
function state. Every ubershader pipeline a draw uses is now recorded,
in <game id>.uberuidcache next to the specialized UID cache, and those
are built before the game starts the next time it boots; every other
ubershader pipeline there can be, and the specialized pipelines known,
build in the background while it runs instead of before it starts,
which took 21 s on lavapipe. The record is written when the cache
closes, into room reserved when it opens, so recording allocates
nothing in the middle of a game.

Without compiler threads (OpenGL here, with no shared context for
them), asking for a pipeline in the background builds it on the spot
and an ubershader one besides: the asynchronous modes draw as
Synchronous does then, and nothing extra is built before the game
starts.

Booting a FIFO log no longer hangs: the CPU thread clears the EFB, which
waits on the GPU, before it reaches its run loop, and the frontend's
thread waited for that run loop without running the GPU. It runs the
GPU until then.
2026-11-06 23:07:17 +01:00

285 lines
12 KiB
C++

// Copyright 2018 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <cstddef>
#include <cstring>
#include <map>
#include <set>
#include <memory>
#include <optional>
#include <string>
#include <unordered_map>
#include <utility>
#ifdef __LIBRETRO__
#include <vector>
#endif
#include "Common/CommonTypes.h"
#include "Common/IOFile.h"
#include "Common/LinearDiskCache.h"
#include "VideoCommon/AbstractPipeline.h"
#include "VideoCommon/AbstractShader.h"
#include "VideoCommon/AsyncShaderCompiler.h"
#include "VideoCommon/GXPipelineTypes.h"
#include "VideoCommon/GeometryShaderGen.h"
#include "VideoCommon/PixelShaderGen.h"
#include "VideoCommon/RenderState.h"
#include "VideoCommon/TextureCacheBase.h"
#include "VideoCommon/TextureConversionShader.h"
#include "VideoCommon/TextureConverterShaderGen.h"
#include "VideoCommon/UberShaderPixel.h"
#include "VideoCommon/UberShaderVertex.h"
#include "VideoCommon/VertexShaderGen.h"
#include "VideoCommon/VideoEvents.h"
class NativeVertexFormat;
enum class AbstractTextureFormat : u32;
enum class APIType;
enum class TextureFormat;
enum class TLUTFormat;
namespace VideoCommon
{
class ShaderCache final
{
public:
/* Whether pipelines can build on threads of their own: without, asking
* for one in the background builds it on the spot all the same. */
bool CanCompileInBackground() const;
ShaderCache();
~ShaderCache();
// Perform at startup, create descriptor layouts, compiles all static shaders.
bool Initialize();
void Shutdown();
// Compiles/loads cached shaders.
void InitializeShaderCache();
// Changes the shader host config. Shaders should be reloaded afterwards.
void SetHostConfig(const ShaderHostConfig& host_config) { m_host_config.bits = host_config.bits; }
// Reloads/recreates all shaders and pipelines.
void Reload();
// Retrieves all pending shaders/pipelines from the async compiler.
void RetrieveAsyncShaders();
// Accesses ShaderGen shader caches
const AbstractPipeline* GetPipelineForUid(const GXPipelineUid& uid);
const AbstractPipeline* GetUberPipelineForUid(const GXUberPipelineUid& uid);
// Accesses ShaderGen shader caches asynchronously.
// The optional will be empty if this pipeline is now background compiling.
std::optional<const AbstractPipeline*> GetPipelineForUidAsync(const GXPipelineUid& uid);
// Shared shaders
const AbstractShader* GetScreenQuadVertexShader() const
{
return m_screen_quad_vertex_shader.get();
}
const AbstractShader* GetTextureCopyVertexShader() const
{
return m_texture_copy_vertex_shader.get();
}
const AbstractShader* GetEFBCopyVertexShader() const { return m_efb_copy_vertex_shader.get(); }
const AbstractShader* GetTexcoordGeometryShader() const
{
return m_texcoord_geometry_shader.get();
}
const AbstractShader* GetTextureCopyPixelShader() const
{
return m_texture_copy_pixel_shader.get();
}
const AbstractShader* GetColorGeometryShader() const { return m_color_geometry_shader.get(); }
const AbstractShader* GetColorPixelShader() const { return m_color_pixel_shader.get(); }
// EFB copy to RAM/VRAM pipelines
const AbstractPipeline*
GetEFBCopyToVRAMPipeline(const TextureConversionShaderGen::TCShaderUid& uid);
const AbstractPipeline* GetEFBCopyToRAMPipeline(const EFBCopyParams& uid);
// RGBA8 framebuffer copy pipelines
const AbstractPipeline* GetRGBA8CopyPipeline() const { return m_copy_rgba8_pipeline.get(); }
const AbstractPipeline* GetRGBA8StereoCopyPipeline() const
{
return m_rgba8_stereo_copy_pipeline.get();
}
// Palette texture conversion pipelines
const AbstractPipeline* GetPaletteConversionPipeline(TLUTFormat format);
// Texture reinterpret pipelines
const AbstractPipeline* GetTextureReinterpretPipeline(TextureFormat from_format,
TextureFormat to_format);
// Texture decoding compute shaders
const AbstractShader* GetTextureDecodingShader(TextureFormat format,
std::optional<TLUTFormat> palette_format);
private:
static constexpr size_t NUM_PALETTE_CONVERSION_SHADERS = 3;
void WaitForAsyncCompiler();
void LoadCaches();
void ClearCaches();
void LoadPipelineUIDCache();
void ClosePipelineUIDCache();
void CompileMissingPipelines();
void QueueUberShaderPipelines();
bool CompileSharedPipelines();
// GX shader compiler methods
std::unique_ptr<AbstractShader> CompileVertexShader(const VertexShaderUid& uid) const;
std::unique_ptr<AbstractShader>
CompileVertexUberShader(const UberShader::VertexShaderUid& uid) const;
std::unique_ptr<AbstractShader> CompilePixelShader(const PixelShaderUid& uid) const;
std::unique_ptr<AbstractShader>
CompilePixelUberShader(const UberShader::PixelShaderUid& uid) const;
const AbstractShader* InsertVertexShader(const VertexShaderUid& uid,
std::unique_ptr<AbstractShader> shader);
const AbstractShader* InsertVertexUberShader(const UberShader::VertexShaderUid& uid,
std::unique_ptr<AbstractShader> shader);
const AbstractShader* InsertPixelShader(const PixelShaderUid& uid,
std::unique_ptr<AbstractShader> shader);
const AbstractShader* InsertPixelUberShader(const UberShader::PixelShaderUid& uid,
std::unique_ptr<AbstractShader> shader);
const AbstractShader* CreateGeometryShader(const GeometryShaderUid& uid);
bool NeedsGeometryShader(const GeometryShaderUid& uid) const;
// Should we use geometry shaders for EFB copies?
bool UseGeometryShaderForEFBCopies() const;
// GX pipeline compiler methods
AbstractPipelineConfig
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);
std::optional<AbstractPipelineConfig> GetGXPipelineConfig(const GXPipelineUid& uid);
std::optional<AbstractPipelineConfig> GetGXPipelineConfig(const GXUberPipelineUid& uid);
const AbstractPipeline* InsertGXPipeline(const GXPipelineUid& config,
std::unique_ptr<AbstractPipeline> pipeline);
const AbstractPipeline* InsertGXUberPipeline(const GXUberPipelineUid& config,
std::unique_ptr<AbstractPipeline> pipeline);
void AddSerializedGXPipelineUID(const SerializedGXPipelineUid& uid);
void AppendGXPipelineUID(const GXPipelineUid& config);
#ifdef __LIBRETRO__
void LoadUberPipelineUIDCache();
void AppendGXUberPipelineUID(const GXUberPipelineUid& config);
/* queues the ubershader pipelines known and not built yet */
void CompileMissingUberPipelines();
#endif
// ASync Compiler Methods
void QueueVertexShaderCompile(const VertexShaderUid& uid, u32 priority);
void QueueVertexUberShaderCompile(const UberShader::VertexShaderUid& uid, u32 priority);
void QueuePixelShaderCompile(const PixelShaderUid& uid, u32 priority);
void QueuePixelUberShaderCompile(const UberShader::PixelShaderUid& uid, u32 priority);
void QueuePipelineCompile(const GXPipelineUid& uid, u32 priority);
void QueueUberPipelineCompile(const GXUberPipelineUid& uid, u32 priority);
// Populating various caches.
template <ShaderStage stage, typename K, typename T>
void LoadShaderCache(T& cache, APIType api_type, const char* type, bool include_gameid);
template <typename T>
void ClearShaderCache(T& cache);
template <typename KeyType, typename DiskKeyType, typename T>
void LoadPipelineCache(T& cache, Common::LinearDiskCache<DiskKeyType, u8>& disk_cache,
APIType api_type, const char* type, bool include_gameid);
template <typename T, typename Y>
void ClearPipelineCache(T& cache, Y& disk_cache);
// Priorities for compiling. The lower the value, the sooner the pipeline is compiled.
// The shader cache is compiled last, as it is the least likely to be required. On demand
// shaders are always compiled before pending ubershaders, as we want to use the ubershader
// for as few frames as possible, otherwise we risk framerate drops.
enum : u32
{
COMPILE_PRIORITY_ONDEMAND_PIPELINE = 100,
COMPILE_PRIORITY_UBERSHADER_PIPELINE = 200,
COMPILE_PRIORITY_SHADERCACHE_PIPELINE = 300
};
// Configuration bits.
APIType m_api_type;
ShaderHostConfig m_host_config = {};
std::unique_ptr<AsyncShaderCompiler> m_async_shader_compiler;
// Shared shaders
std::unique_ptr<AbstractShader> m_screen_quad_vertex_shader;
std::unique_ptr<AbstractShader> m_texture_copy_vertex_shader;
std::unique_ptr<AbstractShader> m_efb_copy_vertex_shader;
std::unique_ptr<AbstractShader> m_texcoord_geometry_shader;
std::unique_ptr<AbstractShader> m_color_geometry_shader;
std::unique_ptr<AbstractShader> m_texture_copy_pixel_shader;
std::unique_ptr<AbstractShader> m_color_pixel_shader;
// GX Shader Caches
template <typename Uid>
struct ShaderModuleCache
{
struct Shader
{
std::unique_ptr<AbstractShader> shader;
bool pending = false;
};
std::map<Uid, Shader> shader_map;
Common::LinearDiskCache<Uid, u8> disk_cache;
};
ShaderModuleCache<VertexShaderUid> m_vs_cache;
ShaderModuleCache<GeometryShaderUid> m_gs_cache;
ShaderModuleCache<PixelShaderUid> m_ps_cache;
ShaderModuleCache<UberShader::VertexShaderUid> m_uber_vs_cache;
ShaderModuleCache<UberShader::PixelShaderUid> m_uber_ps_cache;
// GX Pipeline Caches - .first - pipeline, .second - pending
std::map<GXPipelineUid, std::pair<std::unique_ptr<AbstractPipeline>, bool>> m_gx_pipeline_cache;
std::map<GXUberPipelineUid, std::pair<std::unique_ptr<AbstractPipeline>, bool>>
m_gx_uber_pipeline_cache;
File::IOFile m_gx_pipeline_uid_cache_file;
#ifdef __LIBRETRO__
std::vector<SerializedGXPipelineUid> m_pending_gx_pipeline_uids;
/* The ubershader pipelines a game drew with, as for the specialized
* ones: compiled while the game boots next time, so a hybrid draw
* finds its ubershader ready instead of building it on the spot. */
File::IOFile m_gx_uber_pipeline_uid_cache_file;
std::vector<SerializedGXUberPipelineUid> m_pending_gx_uber_pipeline_uids;
/* those already in the file or written to it this session */
std::set<GXUberPipelineUid> m_recorded_uber_pipeline_uids;
#endif
Common::LinearDiskCache<SerializedGXPipelineUid, u8> m_gx_pipeline_disk_cache;
Common::LinearDiskCache<SerializedGXUberPipelineUid, u8> m_gx_uber_pipeline_disk_cache;
// EFB copy to VRAM/RAM pipelines
std::map<TextureConversionShaderGen::TCShaderUid, std::unique_ptr<AbstractPipeline>>
m_efb_copy_to_vram_pipelines;
std::map<EFBCopyParams, std::unique_ptr<AbstractPipeline>> m_efb_copy_to_ram_pipelines;
// Copy pipeline for RGBA8 textures
std::unique_ptr<AbstractPipeline> m_copy_rgba8_pipeline;
std::unique_ptr<AbstractPipeline> m_rgba8_stereo_copy_pipeline;
// Palette conversion pipelines
std::array<std::unique_ptr<AbstractPipeline>, NUM_PALETTE_CONVERSION_SHADERS>
m_palette_conversion_pipelines;
// Texture reinterpreting pipeline
std::map<std::pair<TextureFormat, TextureFormat>, std::unique_ptr<AbstractPipeline>>
m_texture_reinterpret_pipelines;
// Texture decoding shaders
std::map<std::pair<u32, u32>, std::unique_ptr<AbstractShader>> m_texture_decoding_shaders;
Common::EventHook m_frame_end_handler;
};
} // namespace VideoCommon
extern std::unique_ptr<VideoCommon::ShaderCache> g_shader_cache;