diff --git a/Source/Core/VideoBackends/D3D/Render.cpp b/Source/Core/VideoBackends/D3D/Render.cpp index a132efadf3..9e6c20e314 100644 --- a/Source/Core/VideoBackends/D3D/Render.cpp +++ b/Source/Core/VideoBackends/D3D/Render.cpp @@ -127,7 +127,9 @@ std::unique_ptr Renderer::CreateShaderFromBinary(ShaderStage sta return DXShader::CreateFromBytecode(stage, DXShader::CreateByteCode(data, length)); } -std::unique_ptr Renderer::CreatePipeline(const AbstractPipelineConfig& config) +std::unique_ptr Renderer::CreatePipeline(const AbstractPipelineConfig& config, + const void* cache_data, + size_t cache_data_length) { return DXPipeline::Create(config); } diff --git a/Source/Core/VideoBackends/D3D/Render.h b/Source/Core/VideoBackends/D3D/Render.h index 551295a9d5..e6b658ae9e 100644 --- a/Source/Core/VideoBackends/D3D/Render.h +++ b/Source/Core/VideoBackends/D3D/Render.h @@ -34,7 +34,9 @@ public: size_t length) override; std::unique_ptr CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) override; - std::unique_ptr CreatePipeline(const AbstractPipelineConfig& config) override; + std::unique_ptr CreatePipeline(const AbstractPipelineConfig& config, + const void* cache_data = nullptr, + size_t cache_data_length = 0) override; std::unique_ptr CreateFramebuffer(AbstractTexture* color_attachment, AbstractTexture* depth_attachment) override; diff --git a/Source/Core/VideoBackends/D3D/main.cpp b/Source/Core/VideoBackends/D3D/main.cpp index a3c56dbc7e..d22b34c99a 100644 --- a/Source/Core/VideoBackends/D3D/main.cpp +++ b/Source/Core/VideoBackends/D3D/main.cpp @@ -82,6 +82,8 @@ void VideoBackend::FillBackendInfo() g_Config.backend_info.bSupportsFragmentStoresAndAtomics = true; g_Config.backend_info.bSupportsGSInstancing = true; g_Config.backend_info.bSupportsSSAA = true; + g_Config.backend_info.bSupportsShaderBinaries = true; + g_Config.backend_info.bSupportsPipelineCacheData = false; g_Config.backend_info.Adapters = D3DCommon::GetAdapterNames(); g_Config.backend_info.AAModes = D3D::GetAAModes(g_Config.iAdapter); diff --git a/Source/Core/VideoBackends/D3D12/DXPipeline.cpp b/Source/Core/VideoBackends/D3D12/DXPipeline.cpp index 0c896bb425..c32739f803 100644 --- a/Source/Core/VideoBackends/D3D12/DXPipeline.cpp +++ b/Source/Core/VideoBackends/D3D12/DXPipeline.cpp @@ -156,7 +156,8 @@ static void GetD3DBlendDesc(D3D12_BLEND_DESC* desc, const BlendingState& state) } } -std::unique_ptr DXPipeline::Create(const AbstractPipelineConfig& config) +std::unique_ptr DXPipeline::Create(const AbstractPipelineConfig& config, + const void* cache_data, size_t cache_data_size) { DEBUG_ASSERT(config.vertex_shader && config.pixel_shader); @@ -202,16 +203,36 @@ std::unique_ptr DXPipeline::Create(const AbstractPipelineConfig& con D3DCommon::GetDSVFormatForAbstractFormat(config.framebuffer_state.depth_texture_format); desc.SampleDesc.Count = config.framebuffer_state.samples; desc.NodeMask = 1; + desc.CachedPSO.pCachedBlob = cache_data; + desc.CachedPSO.CachedBlobSizeInBytes = cache_data_size; ID3D12PipelineState* pso; HRESULT hr = g_dx_context->GetDevice()->CreateGraphicsPipelineState(&desc, IID_PPV_ARGS(&pso)); - CHECK(SUCCEEDED(hr), "Create PSO"); if (FAILED(hr)) + { + WARN_LOG(VIDEO, "CreateGraphicsPipelineState() %sfailed with HRESULT %08X", + cache_data ? "with cache data " : "", hr); return nullptr; + } const bool use_integer_rtv = !config.blending_state.blendenable && config.blending_state.logicopenable; return std::make_unique(pso, desc.pRootSignature, config.usage, GetD3DTopology(config.rasterization_state), use_integer_rtv); } + +AbstractPipeline::CacheData DXPipeline::GetCacheData() const +{ + ComPtr blob; + HRESULT hr = m_pipeline->GetCachedBlob(&blob); + if (FAILED(hr)) + { + WARN_LOG(VIDEO, "ID3D12Pipeline::GetCachedBlob() failed with HRESULT %08X", hr); + return {}; + } + + CacheData data(blob->GetBufferSize()); + std::memcpy(data.data(), blob->GetBufferPointer(), blob->GetBufferSize()); + return data; +} } // namespace DX12 diff --git a/Source/Core/VideoBackends/D3D12/DXPipeline.h b/Source/Core/VideoBackends/D3D12/DXPipeline.h index 04608cae1f..d0327a0c0e 100644 --- a/Source/Core/VideoBackends/D3D12/DXPipeline.h +++ b/Source/Core/VideoBackends/D3D12/DXPipeline.h @@ -19,7 +19,8 @@ public: bool use_integer_rtv); ~DXPipeline() override; - static std::unique_ptr Create(const AbstractPipelineConfig& config); + static std::unique_ptr Create(const AbstractPipelineConfig& config, + const void* cache_data, size_t cache_data_size); ID3D12PipelineState* GetPipeline() const { return m_pipeline; } ID3D12RootSignature* GetRootSignature() const { return m_root_signature; } @@ -27,6 +28,8 @@ public: D3D12_PRIMITIVE_TOPOLOGY GetPrimitiveTopology() const { return m_primitive_topology; } bool UseIntegerRTV() const { return m_use_integer_rtv; } + CacheData GetCacheData() const override; + private: ID3D12PipelineState* m_pipeline; ID3D12RootSignature* m_root_signature; diff --git a/Source/Core/VideoBackends/D3D12/Renderer.cpp b/Source/Core/VideoBackends/D3D12/Renderer.cpp index 49de5344e6..b834ba33e1 100644 --- a/Source/Core/VideoBackends/D3D12/Renderer.cpp +++ b/Source/Core/VideoBackends/D3D12/Renderer.cpp @@ -99,9 +99,11 @@ Renderer::CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) return std::make_unique(vtx_decl); } -std::unique_ptr Renderer::CreatePipeline(const AbstractPipelineConfig& config) +std::unique_ptr Renderer::CreatePipeline(const AbstractPipelineConfig& config, + const void* cache_data, + size_t cache_data_length) { - return DXPipeline::Create(config); + return DXPipeline::Create(config, cache_data, cache_data_length); } u16 Renderer::BBoxRead(int index) diff --git a/Source/Core/VideoBackends/D3D12/Renderer.h b/Source/Core/VideoBackends/D3D12/Renderer.h index 1979833048..992e12aa8a 100644 --- a/Source/Core/VideoBackends/D3D12/Renderer.h +++ b/Source/Core/VideoBackends/D3D12/Renderer.h @@ -41,7 +41,9 @@ public: size_t length) override; std::unique_ptr CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) override; - std::unique_ptr CreatePipeline(const AbstractPipelineConfig& config) override; + std::unique_ptr CreatePipeline(const AbstractPipelineConfig& config, + const void* cache_data = nullptr, + size_t cache_data_length = 0) override; u16 BBoxRead(int index) override; void BBoxWrite(int index, u16 value) override; diff --git a/Source/Core/VideoBackends/D3D12/VideoBackend.cpp b/Source/Core/VideoBackends/D3D12/VideoBackend.cpp index 0ba778471b..8bfc68a5e3 100644 --- a/Source/Core/VideoBackends/D3D12/VideoBackend.cpp +++ b/Source/Core/VideoBackends/D3D12/VideoBackend.cpp @@ -80,6 +80,8 @@ void VideoBackend::FillBackendInfo() g_Config.backend_info.bSupportsPartialDepthCopies = false; g_Config.backend_info.Adapters = D3DCommon::GetAdapterNames(); g_Config.backend_info.AAModes = DXContext::GetAAModes(g_Config.iAdapter); + g_Config.backend_info.bSupportsShaderBinaries = true; + g_Config.backend_info.bSupportsPipelineCacheData = true; // We can only check texture support once we have a device. if (g_dx_context) diff --git a/Source/Core/VideoBackends/D3DCommon/Shader.cpp b/Source/Core/VideoBackends/D3DCommon/Shader.cpp index d53c5ab2e4..12091806e1 100644 --- a/Source/Core/VideoBackends/D3DCommon/Shader.cpp +++ b/Source/Core/VideoBackends/D3DCommon/Shader.cpp @@ -23,11 +23,6 @@ Shader::Shader(ShaderStage stage, BinaryData bytecode) Shader::~Shader() = default; -bool Shader::HasBinary() const -{ - return true; -} - AbstractShader::BinaryData Shader::GetBinary() const { return m_bytecode; diff --git a/Source/Core/VideoBackends/D3DCommon/Shader.h b/Source/Core/VideoBackends/D3DCommon/Shader.h index 6513cd11fe..c3dd94b279 100644 --- a/Source/Core/VideoBackends/D3DCommon/Shader.h +++ b/Source/Core/VideoBackends/D3DCommon/Shader.h @@ -16,7 +16,6 @@ public: const BinaryData& GetByteCode() const { return m_bytecode; } - bool HasBinary() const override; BinaryData GetBinary() const override; static bool CompileShader(D3D_FEATURE_LEVEL feature_level, BinaryData* out_bytecode, diff --git a/Source/Core/VideoBackends/Null/NullBackend.cpp b/Source/Core/VideoBackends/Null/NullBackend.cpp index 6959c820ed..2f353d1fe3 100644 --- a/Source/Core/VideoBackends/Null/NullBackend.cpp +++ b/Source/Core/VideoBackends/Null/NullBackend.cpp @@ -52,6 +52,8 @@ void VideoBackend::InitBackendInfo() g_Config.backend_info.bSupportsLogicOp = false; g_Config.backend_info.bSupportsLargePoints = false; g_Config.backend_info.bSupportsPartialDepthCopies = false; + g_Config.backend_info.bSupportsShaderBinaries = false; + g_Config.backend_info.bSupportsPipelineCacheData = false; // aamodes: We only support 1 sample, so no MSAA g_Config.backend_info.Adapters.clear(); diff --git a/Source/Core/VideoBackends/Null/Render.cpp b/Source/Core/VideoBackends/Null/Render.cpp index ca57a0bdeb..6be3ee4753 100644 --- a/Source/Core/VideoBackends/Null/Render.cpp +++ b/Source/Core/VideoBackends/Null/Render.cpp @@ -46,9 +46,6 @@ class NullShader final : public AbstractShader public: explicit NullShader(ShaderStage stage) : AbstractShader(stage) {} ~NullShader() = default; - - bool HasBinary() const override { return false; } - BinaryData GetBinary() const override { return {}; } }; std::unique_ptr Renderer::CreateShaderFromSource(ShaderStage stage, @@ -70,7 +67,9 @@ public: ~NullPipeline() override = default; }; -std::unique_ptr Renderer::CreatePipeline(const AbstractPipelineConfig& config) +std::unique_ptr Renderer::CreatePipeline(const AbstractPipelineConfig& config, + const void* cache_data, + size_t cache_data_length) { return std::make_unique(); } diff --git a/Source/Core/VideoBackends/Null/Render.h b/Source/Core/VideoBackends/Null/Render.h index 5ad4d8028e..339e21d637 100644 --- a/Source/Core/VideoBackends/Null/Render.h +++ b/Source/Core/VideoBackends/Null/Render.h @@ -28,7 +28,9 @@ public: size_t length) override; std::unique_ptr CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) override; - std::unique_ptr CreatePipeline(const AbstractPipelineConfig& config) override; + std::unique_ptr CreatePipeline(const AbstractPipelineConfig& config, + const void* cache_data = nullptr, + size_t cache_data_length = 0) override; u32 AccessEFB(EFBAccessType type, u32 x, u32 y, u32 poke_data) override { return 0; } void PokeEFB(EFBAccessType type, const EfbPokeData* points, size_t num_points) override {} diff --git a/Source/Core/VideoBackends/OGL/OGLPipeline.cpp b/Source/Core/VideoBackends/OGL/OGLPipeline.cpp index bd439898db..00ae8aa873 100644 --- a/Source/Core/VideoBackends/OGL/OGLPipeline.cpp +++ b/Source/Core/VideoBackends/OGL/OGLPipeline.cpp @@ -9,6 +9,7 @@ #include "VideoBackends/OGL/ProgramShaderCache.h" #include "VideoBackends/OGL/Render.h" #include "VideoBackends/OGL/VertexManager.h" +#include "VideoCommon/VideoConfig.h" namespace OGL { @@ -31,7 +32,7 @@ static GLenum MapToGLPrimitive(PrimitiveType primitive_type) OGLPipeline::OGLPipeline(const GLVertexFormat* vertex_format, const RasterizationState& rasterization_state, const DepthState& depth_state, const BlendingState& blending_state, - const PipelineProgram* program, GLuint gl_primitive) + PipelineProgram* program, GLuint gl_primitive) : m_vertex_format(vertex_format), m_rasterization_state(rasterization_state), m_depth_state(depth_state), m_blending_state(blending_state), m_program(program), m_gl_primitive(gl_primitive) @@ -44,13 +45,47 @@ OGLPipeline::~OGLPipeline() ProgramShaderCache::ReleasePipelineProgram(m_program); } -std::unique_ptr OGLPipeline::Create(const AbstractPipelineConfig& config) +AbstractPipeline::CacheData OGLPipeline::GetCacheData() const { - const PipelineProgram* program = ProgramShaderCache::GetPipelineProgram( + // More than one pipeline can share the same shaders. To avoid bloating the cache with multiple + // copies of the same program combination, we set a flag on the program object so that it can't + // be retrieved again. When booting, the pipeline cache is loaded in-order, so the additional + // pipelines which use the program combination will re-use the already-created object. + if (!g_ActiveConfig.backend_info.bSupportsPipelineCacheData || m_program->binary_retrieved) + return {}; + + GLint program_size = 0; + glGetProgramiv(m_program->shader.glprogid, GL_PROGRAM_BINARY_LENGTH, &program_size); + if (program_size == 0) + return {}; + + // Clear any existing error. + glGetError(); + + // We pack the format at the start of the buffer. + CacheData data(program_size + sizeof(u32)); + GLsizei data_size = 0; + GLenum program_format = 0; + glGetProgramBinary(m_program->shader.glprogid, program_size, &data_size, &program_format, + &data[sizeof(u32)]); + if (glGetError() != GL_NO_ERROR || data_size == 0) + return {}; + + u32 program_format_u32 = static_cast(program_format); + std::memcpy(&data[0], &program_format_u32, sizeof(u32)); + data.resize(data_size + sizeof(u32)); + m_program->binary_retrieved = true; + return data; +} + +std::unique_ptr OGLPipeline::Create(const AbstractPipelineConfig& config, + const void* cache_data, size_t cache_data_size) +{ + PipelineProgram* program = ProgramShaderCache::GetPipelineProgram( static_cast(config.vertex_format), static_cast(config.vertex_shader), static_cast(config.geometry_shader), - static_cast(config.pixel_shader)); + static_cast(config.pixel_shader), cache_data, cache_data_size); if (!program) return nullptr; diff --git a/Source/Core/VideoBackends/OGL/OGLPipeline.h b/Source/Core/VideoBackends/OGL/OGLPipeline.h index 42371ad927..9d74842df5 100644 --- a/Source/Core/VideoBackends/OGL/OGLPipeline.h +++ b/Source/Core/VideoBackends/OGL/OGLPipeline.h @@ -18,7 +18,7 @@ class OGLPipeline final : public AbstractPipeline public: explicit OGLPipeline(const GLVertexFormat* vertex_format, const RasterizationState& rasterization_state, const DepthState& depth_state, - const BlendingState& blending_state, const PipelineProgram* program, + const BlendingState& blending_state, PipelineProgram* program, GLenum gl_primitive); ~OGLPipeline() override; @@ -29,14 +29,16 @@ public: const PipelineProgram* GetProgram() const { return m_program; } bool HasVertexInput() const { return m_vertex_format != nullptr; } GLenum GetGLPrimitive() const { return m_gl_primitive; } - static std::unique_ptr Create(const AbstractPipelineConfig& config); + CacheData GetCacheData() const override; + static std::unique_ptr Create(const AbstractPipelineConfig& config, + const void* cache_data, size_t cache_data_size); private: const GLVertexFormat* m_vertex_format; RasterizationState m_rasterization_state; DepthState m_depth_state; BlendingState m_blending_state; - const PipelineProgram* m_program; + PipelineProgram* m_program; GLenum m_gl_primitive; }; diff --git a/Source/Core/VideoBackends/OGL/OGLShader.cpp b/Source/Core/VideoBackends/OGL/OGLShader.cpp index 340d79f244..fc13b5b975 100644 --- a/Source/Core/VideoBackends/OGL/OGLShader.cpp +++ b/Source/Core/VideoBackends/OGL/OGLShader.cpp @@ -44,17 +44,6 @@ OGLShader::~OGLShader() glDeleteProgram(m_gl_compute_program_id); } -bool OGLShader::HasBinary() const -{ - // NOTE: GL shaders do not have binaries, programs do. - return false; -} - -AbstractShader::BinaryData OGLShader::GetBinary() const -{ - return {}; -} - std::unique_ptr OGLShader::CreateFromSource(ShaderStage stage, const char* source, size_t length) { diff --git a/Source/Core/VideoBackends/OGL/OGLShader.h b/Source/Core/VideoBackends/OGL/OGLShader.h index a703d60696..ad0432ada4 100644 --- a/Source/Core/VideoBackends/OGL/OGLShader.h +++ b/Source/Core/VideoBackends/OGL/OGLShader.h @@ -24,8 +24,6 @@ public: GLenum GetGLShaderType() const { return m_type; } GLuint GetGLShaderID() const { return m_gl_id; } GLuint GetGLComputeProgramID() const { return m_gl_compute_program_id; } - bool HasBinary() const override; - BinaryData GetBinary() const override; static std::unique_ptr CreateFromSource(ShaderStage stage, const char* source, size_t length); diff --git a/Source/Core/VideoBackends/OGL/ProgramShaderCache.cpp b/Source/Core/VideoBackends/OGL/ProgramShaderCache.cpp index 61cd953b69..414f7dfa57 100644 --- a/Source/Core/VideoBackends/OGL/ProgramShaderCache.cpp +++ b/Source/Core/VideoBackends/OGL/ProgramShaderCache.cpp @@ -312,7 +312,7 @@ bool ProgramShaderCache::CompileShader(SHADER& shader, const std::string& vcode, if (shader.gsid) glAttachShader(shader.glprogid, shader.gsid); - if (g_ogl_config.bSupportsGLSLCache) + if (g_ActiveConfig.backend_info.bSupportsPipelineCacheData) glProgramParameteri(shader.glprogid, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE); shader.SetProgramBindings(false); @@ -562,10 +562,12 @@ void ProgramShaderCache::InvalidateLastProgram() CurrentProgram = 0; } -const PipelineProgram* ProgramShaderCache::GetPipelineProgram(const GLVertexFormat* vertex_format, - const OGLShader* vertex_shader, - const OGLShader* geometry_shader, - const OGLShader* pixel_shader) +PipelineProgram* ProgramShaderCache::GetPipelineProgram(const GLVertexFormat* vertex_format, + const OGLShader* vertex_shader, + const OGLShader* geometry_shader, + const OGLShader* pixel_shader, + const void* cache_data, + size_t cache_data_size) { PipelineProgramKey key = {vertex_shader ? vertex_shader->GetID() : 0, geometry_shader ? geometry_shader->GetID() : 0, @@ -580,39 +582,69 @@ const PipelineProgram* ProgramShaderCache::GetPipelineProgram(const GLVertexForm } } - // We temporarily change the vertex array to the pipeline's vertex format. - // This can prevent the NVIDIA OpenGL driver from recompiling on first use. - GLuint vao = vertex_format ? vertex_format->VAO : s_attributeless_VAO; - if (s_is_shared_context || vao != s_last_VAO) - glBindVertexArray(vao); - std::unique_ptr prog = std::make_unique(); prog->key = key; - - // Attach shaders. - ASSERT(vertex_shader && vertex_shader->GetStage() == ShaderStage::Vertex); - ASSERT(pixel_shader && pixel_shader->GetStage() == ShaderStage::Pixel); prog->shader.glprogid = glCreateProgram(); - glAttachShader(prog->shader.glprogid, vertex_shader->GetGLShaderID()); - glAttachShader(prog->shader.glprogid, pixel_shader->GetGLShaderID()); - if (geometry_shader) + + // Use the cache data, if present. If this fails, we want to return an error, so the shader cache + // doesn't attempt to use the same binary data in the future. + if (cache_data_size >= sizeof(u32)) { - ASSERT(geometry_shader->GetStage() == ShaderStage::Geometry); - glAttachShader(prog->shader.glprogid, geometry_shader->GetGLShaderID()); + u32 program_binary_type; + std::memcpy(&program_binary_type, cache_data, sizeof(u32)); + glProgramBinary(prog->shader.glprogid, static_cast(program_binary_type), + static_cast(cache_data) + sizeof(u32), + static_cast(cache_data_size - sizeof(u32))); + + // Check the link status. If this fails, it means the binary was invalid. + GLint link_status; + glGetProgramiv(prog->shader.glprogid, GL_LINK_STATUS, &link_status); + if (link_status != GL_TRUE) + { + WARN_LOG(VIDEO, "Failed to create GL program from program binary."); + prog->shader.Destroy(); + return nullptr; + } + + // We don't want to retrieve this binary and duplicate entries in the cache again. + // See the explanation in OGLPipeline.cpp. + prog->binary_retrieved = true; } - - // Link program. - prog->shader.SetProgramBindings(false); - glLinkProgram(prog->shader.glprogid); - - // Restore VAO binding after linking. - if (!s_is_shared_context && vao != s_last_VAO) - glBindVertexArray(s_last_VAO); - - if (!ProgramShaderCache::CheckProgramLinkResult(prog->shader.glprogid, {}, {}, {})) + else { - prog->shader.Destroy(); - return nullptr; + // We temporarily change the vertex array to the pipeline's vertex format. + // This can prevent the NVIDIA OpenGL driver from recompiling on first use. + GLuint vao = vertex_format ? vertex_format->VAO : s_attributeless_VAO; + if (s_is_shared_context || vao != s_last_VAO) + glBindVertexArray(vao); + + // Attach shaders. + ASSERT(vertex_shader && vertex_shader->GetStage() == ShaderStage::Vertex); + ASSERT(pixel_shader && pixel_shader->GetStage() == ShaderStage::Pixel); + glAttachShader(prog->shader.glprogid, vertex_shader->GetGLShaderID()); + glAttachShader(prog->shader.glprogid, pixel_shader->GetGLShaderID()); + if (geometry_shader) + { + ASSERT(geometry_shader->GetStage() == ShaderStage::Geometry); + glAttachShader(prog->shader.glprogid, geometry_shader->GetGLShaderID()); + } + + if (g_ActiveConfig.backend_info.bSupportsPipelineCacheData) + glProgramParameteri(prog->shader.glprogid, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE); + + // Link program. + prog->shader.SetProgramBindings(false); + glLinkProgram(prog->shader.glprogid); + + // Restore VAO binding after linking. + if (!s_is_shared_context && vao != s_last_VAO) + glBindVertexArray(s_last_VAO); + + if (!ProgramShaderCache::CheckProgramLinkResult(prog->shader.glprogid, {}, {}, {})) + { + prog->shader.Destroy(); + return nullptr; + } } // Lock to insert. A duplicate program may have been created in the meantime. @@ -639,16 +671,17 @@ const PipelineProgram* ProgramShaderCache::GetPipelineProgram(const GLVertexForm return ip.first->second.get(); } -void ProgramShaderCache::ReleasePipelineProgram(const PipelineProgram* prog) +void ProgramShaderCache::ReleasePipelineProgram(PipelineProgram* prog) { + if (--prog->reference_count > 0) + return; + + prog->shader.Destroy(); + + std::lock_guard guard(s_pipeline_program_lock); auto iter = s_pipeline_programs.find(prog->key); ASSERT(iter != s_pipeline_programs.end() && prog == iter->second.get()); - - if (--iter->second->reference_count == 0) - { - iter->second->shader.Destroy(); - s_pipeline_programs.erase(iter); - } + s_pipeline_programs.erase(iter); } void ProgramShaderCache::CreateHeader() diff --git a/Source/Core/VideoBackends/OGL/ProgramShaderCache.h b/Source/Core/VideoBackends/OGL/ProgramShaderCache.h index 7fc267dc5d..b7e4dd6d00 100644 --- a/Source/Core/VideoBackends/OGL/ProgramShaderCache.h +++ b/Source/Core/VideoBackends/OGL/ProgramShaderCache.h @@ -63,6 +63,7 @@ struct PipelineProgram PipelineProgramKey key; SHADER shader; std::atomic_size_t reference_count{1}; + bool binary_retrieved = false; }; class ProgramShaderCache @@ -97,11 +98,12 @@ public: // pipeline do not match the pipeline configuration. static u64 GenerateShaderID(); - static const PipelineProgram* GetPipelineProgram(const GLVertexFormat* vertex_format, - const OGLShader* vertex_shader, - const OGLShader* geometry_shader, - const OGLShader* pixel_shader); - static void ReleasePipelineProgram(const PipelineProgram* prog); + static PipelineProgram* GetPipelineProgram(const GLVertexFormat* vertex_format, + const OGLShader* vertex_shader, + const OGLShader* geometry_shader, + const OGLShader* pixel_shader, const void* cache_data, + size_t cache_data_size); + static void ReleasePipelineProgram(PipelineProgram* prog); private: typedef std::unordered_map, diff --git a/Source/Core/VideoBackends/OGL/Render.cpp b/Source/Core/VideoBackends/OGL/Render.cpp index 06e5135191..1e263b6422 100644 --- a/Source/Core/VideoBackends/OGL/Render.cpp +++ b/Source/Core/VideoBackends/OGL/Render.cpp @@ -350,6 +350,7 @@ Renderer::Renderer(std::unique_ptr main_gl_context, float backbuffer_ } bool bSuccess = true; + bool supports_glsl_cache = false; g_ogl_config.gl_vendor = (const char*)glGetString(GL_VENDOR); g_ogl_config.gl_renderer = (const char*)glGetString(GL_RENDERER); @@ -466,7 +467,7 @@ Renderer::Renderer(std::unique_ptr main_gl_context, float backbuffer_ GLExtensions::Supports("GL_ARB_gpu_shader5"); g_ogl_config.bIsES = m_main_gl_context->IsGLES(); - g_ogl_config.bSupportsGLSLCache = GLExtensions::Supports("GL_ARB_get_program_binary"); + supports_glsl_cache = GLExtensions::Supports("GL_ARB_get_program_binary"); g_ogl_config.bSupportsGLPinnedMemory = GLExtensions::Supports("GL_AMD_pinned_memory"); g_ogl_config.bSupportsGLSync = GLExtensions::Supports("GL_ARB_sync"); g_ogl_config.bSupportsGLBaseVertex = GLExtensions::Supports("GL_ARB_draw_elements_base_vertex") || @@ -507,7 +508,7 @@ Renderer::Renderer(std::unique_ptr main_gl_context, float backbuffer_ EsTexbufType::TexbufExt : EsTexbufType::TexbufNone; - g_ogl_config.bSupportsGLSLCache = true; + supports_glsl_cache = true; g_ogl_config.bSupportsGLSync = true; // TODO: Implement support for GL_EXT_clip_cull_distance when there is an extension for @@ -675,6 +676,16 @@ Renderer::Renderer(std::unique_ptr main_gl_context, float backbuffer_ g_Config.backend_info.bSupportsBackgroundCompiling = !DriverDetails::HasBug(DriverDetails::BUG_SHARED_CONTEXT_SHADER_COMPILATION); + // Program binaries are supported on GL4.1+, ARB_get_program_binary, or ES3. + if (supports_glsl_cache) + { + // We need to check the number of formats supported. If zero, don't bother getting the binaries. + GLint num_formats = 0; + glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &num_formats); + supports_glsl_cache = num_formats > 0; + } + g_Config.backend_info.bSupportsPipelineCacheData = supports_glsl_cache; + if (g_ogl_config.bSupportsDebug) { if (GLExtensions::Supports("GL_KHR_debug")) @@ -739,7 +750,7 @@ Renderer::Renderer(std::unique_ptr main_gl_context, float backbuffer_ g_ActiveConfig.backend_info.bSupportsPrimitiveRestart ? "" : "PrimitiveRestart ", g_ActiveConfig.backend_info.bSupportsEarlyZ ? "" : "EarlyZ ", g_ogl_config.bSupportsGLPinnedMemory ? "" : "PinnedMemory ", - g_ogl_config.bSupportsGLSLCache ? "" : "ShaderCache ", + supports_glsl_cache ? "" : "ShaderCache ", g_ogl_config.bSupportsGLBaseVertex ? "" : "BaseVertex ", g_ogl_config.bSupportsGLBufferStorage ? "" : "BufferStorage ", g_ogl_config.bSupportsGLSync ? "" : "Sync ", g_ogl_config.bSupportsMSAA ? "" : "MSAA ", @@ -828,9 +839,11 @@ std::unique_ptr Renderer::CreateShaderFromBinary(ShaderStage sta return nullptr; } -std::unique_ptr Renderer::CreatePipeline(const AbstractPipelineConfig& config) +std::unique_ptr Renderer::CreatePipeline(const AbstractPipelineConfig& config, + const void* cache_data, + size_t cache_data_length) { - return OGLPipeline::Create(config); + return OGLPipeline::Create(config, cache_data, cache_data_length); } void Renderer::SetScissorRect(const MathUtil::Rectangle& rc) diff --git a/Source/Core/VideoBackends/OGL/Render.h b/Source/Core/VideoBackends/OGL/Render.h index 0fb6c4e93e..842a6d9570 100644 --- a/Source/Core/VideoBackends/OGL/Render.h +++ b/Source/Core/VideoBackends/OGL/Render.h @@ -48,7 +48,6 @@ enum class EsFbFetchType struct VideoConfig { bool bIsES; - bool bSupportsGLSLCache; bool bSupportsGLPinnedMemory; bool bSupportsGLSync; bool bSupportsGLBaseVertex; @@ -102,7 +101,9 @@ public: size_t length) override; std::unique_ptr CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) override; - std::unique_ptr CreatePipeline(const AbstractPipelineConfig& config) override; + std::unique_ptr CreatePipeline(const AbstractPipelineConfig& config, + const void* cache_data = nullptr, + size_t cache_data_length = 0) override; std::unique_ptr CreateFramebuffer(AbstractTexture* color_attachment, AbstractTexture* depth_attachment) override; diff --git a/Source/Core/VideoBackends/OGL/main.cpp b/Source/Core/VideoBackends/OGL/main.cpp index 5157f015e4..c2cf0af192 100644 --- a/Source/Core/VideoBackends/OGL/main.cpp +++ b/Source/Core/VideoBackends/OGL/main.cpp @@ -90,6 +90,8 @@ void VideoBackend::InitBackendInfo() g_Config.backend_info.bSupportsCopyToVram = true; g_Config.backend_info.bSupportsLargePoints = true; g_Config.backend_info.bSupportsPartialDepthCopies = true; + g_Config.backend_info.bSupportsShaderBinaries = false; + g_Config.backend_info.bSupportsPipelineCacheData = false; // TODO: There is a bug here, if texel buffers or SSBOs/atomics are not supported the graphics // options will show the option when it is not supported. The only way around this would be diff --git a/Source/Core/VideoBackends/Software/SWRenderer.cpp b/Source/Core/VideoBackends/Software/SWRenderer.cpp index 4b57b9b551..3893320c26 100644 --- a/Source/Core/VideoBackends/Software/SWRenderer.cpp +++ b/Source/Core/VideoBackends/Software/SWRenderer.cpp @@ -64,7 +64,6 @@ public: explicit SWShader(ShaderStage stage) : AbstractShader(stage) {} ~SWShader() = default; - bool HasBinary() const override { return false; } BinaryData GetBinary() const override { return {}; } }; @@ -87,7 +86,9 @@ public: ~SWPipeline() override = default; }; -std::unique_ptr SWRenderer::CreatePipeline(const AbstractPipelineConfig& config) +std::unique_ptr SWRenderer::CreatePipeline(const AbstractPipelineConfig& config, + const void* cache_data, + size_t cache_data_length) { return std::make_unique(); } diff --git a/Source/Core/VideoBackends/Software/SWRenderer.h b/Source/Core/VideoBackends/Software/SWRenderer.h index a06ccaecd2..21ebf2dea7 100644 --- a/Source/Core/VideoBackends/Software/SWRenderer.h +++ b/Source/Core/VideoBackends/Software/SWRenderer.h @@ -33,7 +33,9 @@ public: size_t length) override; std::unique_ptr CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) override; - std::unique_ptr CreatePipeline(const AbstractPipelineConfig& config) override; + std::unique_ptr CreatePipeline(const AbstractPipelineConfig& config, + const void* cache_data = nullptr, + size_t cache_data_length = 0) override; u32 AccessEFB(EFBAccessType type, u32 x, u32 y, u32 poke_data) override; void PokeEFB(EFBAccessType type, const EfbPokeData* points, size_t num_points) override {} diff --git a/Source/Core/VideoBackends/Software/SWmain.cpp b/Source/Core/VideoBackends/Software/SWmain.cpp index b1c465c4a2..1d02660cf7 100644 --- a/Source/Core/VideoBackends/Software/SWmain.cpp +++ b/Source/Core/VideoBackends/Software/SWmain.cpp @@ -74,6 +74,8 @@ void VideoSoftware::InitBackendInfo() g_Config.backend_info.bSupportsFramebufferFetch = false; g_Config.backend_info.bSupportsBackgroundCompiling = false; g_Config.backend_info.bSupportsLogicOp = true; + g_Config.backend_info.bSupportsShaderBinaries = false; + g_Config.backend_info.bSupportsPipelineCacheData = false; // aamodes g_Config.backend_info.AAModes = {1}; diff --git a/Source/Core/VideoBackends/Vulkan/Renderer.cpp b/Source/Core/VideoBackends/Vulkan/Renderer.cpp index fc07c4ce41..5d918eda96 100644 --- a/Source/Core/VideoBackends/Vulkan/Renderer.cpp +++ b/Source/Core/VideoBackends/Vulkan/Renderer.cpp @@ -112,7 +112,9 @@ Renderer::CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) return std::make_unique(vtx_decl); } -std::unique_ptr Renderer::CreatePipeline(const AbstractPipelineConfig& config) +std::unique_ptr Renderer::CreatePipeline(const AbstractPipelineConfig& config, + const void* cache_data, + size_t cache_data_length) { return VKPipeline::Create(config); } diff --git a/Source/Core/VideoBackends/Vulkan/Renderer.h b/Source/Core/VideoBackends/Vulkan/Renderer.h index 2b3390b263..f28b4f94c1 100644 --- a/Source/Core/VideoBackends/Vulkan/Renderer.h +++ b/Source/Core/VideoBackends/Vulkan/Renderer.h @@ -48,7 +48,9 @@ public: size_t length) override; std::unique_ptr CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) override; - std::unique_ptr CreatePipeline(const AbstractPipelineConfig& config) override; + std::unique_ptr CreatePipeline(const AbstractPipelineConfig& config, + const void* cache_data = nullptr, + size_t cache_data_length = 0) override; SwapChain* GetSwapChain() const { return m_swap_chain.get(); } BoundingBox* GetBoundingBox() const { return m_bounding_box.get(); } diff --git a/Source/Core/VideoBackends/Vulkan/VKShader.cpp b/Source/Core/VideoBackends/Vulkan/VKShader.cpp index 1a95a9c1f8..4308660ae6 100644 --- a/Source/Core/VideoBackends/Vulkan/VKShader.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKShader.cpp @@ -32,12 +32,6 @@ VKShader::~VKShader() vkDestroyPipeline(g_vulkan_context->GetDevice(), m_compute_pipeline, nullptr); } -bool VKShader::HasBinary() const -{ - ASSERT(!m_spv.empty()); - return true; -} - AbstractShader::BinaryData VKShader::GetBinary() const { BinaryData ret(sizeof(u32) * m_spv.size()); diff --git a/Source/Core/VideoBackends/Vulkan/VKShader.h b/Source/Core/VideoBackends/Vulkan/VKShader.h index a97007adda..21194127a7 100644 --- a/Source/Core/VideoBackends/Vulkan/VKShader.h +++ b/Source/Core/VideoBackends/Vulkan/VKShader.h @@ -23,7 +23,6 @@ public: VkShaderModule GetShaderModule() const { return m_module; } VkPipeline GetComputePipeline() const { return m_compute_pipeline; } - bool HasBinary() const override; BinaryData GetBinary() const override; static std::unique_ptr CreateFromSource(ShaderStage stage, const char* source, diff --git a/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp b/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp index 72f3cf79b0..1f0955c921 100644 --- a/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp +++ b/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp @@ -266,6 +266,8 @@ void VulkanContext::PopulateBackendInfo(VideoConfig* config) config->backend_info.bSupportsGPUTextureDecoding = true; // Assumed support. config->backend_info.bSupportsBitfield = true; // Assumed support. config->backend_info.bSupportsPartialDepthCopies = true; // Assumed support. + config->backend_info.bSupportsShaderBinaries = true; // Assumed support. + config->backend_info.bSupportsPipelineCacheData = false; // Handled via pipeline caches. config->backend_info.bSupportsDynamicSamplerIndexing = true; // Assumed support. config->backend_info.bSupportsPostProcessing = true; // Assumed support. config->backend_info.bSupportsBackgroundCompiling = true; // Assumed support. diff --git a/Source/Core/VideoCommon/AbstractPipeline.h b/Source/Core/VideoCommon/AbstractPipeline.h index 8c7d7482de..8375eda557 100644 --- a/Source/Core/VideoCommon/AbstractPipeline.h +++ b/Source/Core/VideoCommon/AbstractPipeline.h @@ -75,4 +75,10 @@ class AbstractPipeline public: AbstractPipeline() = default; virtual ~AbstractPipeline() = default; + + // "Cache data" can be used to assist a driver with creating pipelines by using previously + // compiled shader ISA. The abstract shaders and creation struct are still required to create + // pipeline objects, the cache is optionally used by the driver to speed up compilation. + using CacheData = std::vector; + virtual CacheData GetCacheData() const { return {}; } }; diff --git a/Source/Core/VideoCommon/AbstractShader.h b/Source/Core/VideoCommon/AbstractShader.h index 4e765d60d9..52855fd76e 100644 --- a/Source/Core/VideoCommon/AbstractShader.h +++ b/Source/Core/VideoCommon/AbstractShader.h @@ -25,9 +25,11 @@ public: virtual ~AbstractShader() = default; ShaderStage GetStage() const { return m_stage; } + + // Shader binaries represent the input source code in a lower-level form. e.g. SPIR-V or DXBC. + // The shader source code is not required to create a shader object from the binary. using BinaryData = std::vector; - virtual bool HasBinary() const = 0; - virtual BinaryData GetBinary() const = 0; + virtual BinaryData GetBinary() const { return {}; } protected: ShaderStage m_stage; diff --git a/Source/Core/VideoCommon/GXPipelineTypes.h b/Source/Core/VideoCommon/GXPipelineTypes.h index 8414b1af7f..f20b8edac6 100644 --- a/Source/Core/VideoCommon/GXPipelineTypes.h +++ b/Source/Core/VideoCommon/GXPipelineTypes.h @@ -99,6 +99,16 @@ struct SerializedGXPipelineUid u32 depth_state_bits; u32 blending_state_bits; }; +struct SerializedGXUberPipelineUid +{ + PortableVertexDeclaration vertex_decl; + UberShader::VertexShaderUid vs_uid; + GeometryShaderUid gs_uid; + UberShader::PixelShaderUid ps_uid; + u32 rasterization_state_bits; + u32 depth_state_bits; + u32 blending_state_bits; +}; #pragma pack(pop) } // namespace VideoCommon diff --git a/Source/Core/VideoCommon/RenderBase.h b/Source/Core/VideoCommon/RenderBase.h index af9c1a8972..31a0e2c15b 100644 --- a/Source/Core/VideoCommon/RenderBase.h +++ b/Source/Core/VideoCommon/RenderBase.h @@ -130,8 +130,9 @@ public: CreateShaderFromBinary(ShaderStage stage, const void* data, size_t length) = 0; virtual std::unique_ptr CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) = 0; - virtual std::unique_ptr - CreatePipeline(const AbstractPipelineConfig& config) = 0; + virtual std::unique_ptr CreatePipeline(const AbstractPipelineConfig& config, + const void* cache_data = nullptr, + size_t cache_data_length = 0) = 0; std::unique_ptr CreateShaderFromSource(ShaderStage stage, const std::string& source); diff --git a/Source/Core/VideoCommon/ShaderCache.cpp b/Source/Core/VideoCommon/ShaderCache.cpp index 8f9c99d39d..e1712ec8f3 100644 --- a/Source/Core/VideoCommon/ShaderCache.cpp +++ b/Source/Core/VideoCommon/ShaderCache.cpp @@ -25,8 +25,7 @@ namespace VideoCommon ShaderCache::ShaderCache() = default; ShaderCache::~ShaderCache() { - ClearShaderCaches(); - ClearPipelineCaches(); + ClearCaches(); } bool ShaderCache::Initialize() @@ -48,7 +47,7 @@ void ShaderCache::InitializeShaderCache() // Load shader and UID caches. if (g_ActiveConfig.bShaderCache && m_api_type != APIType::Nothing) { - LoadShaderCaches(); + LoadCaches(); LoadPipelineUIDCache(); } @@ -69,11 +68,10 @@ void ShaderCache::Reload() { WaitForAsyncCompiler(); ClosePipelineUIDCache(); - InvalidateCachedPipelines(); - ClearShaderCaches(); + ClearCaches(); if (g_ActiveConfig.bShaderCache) - LoadShaderCaches(); + LoadCaches(); // Switch to the precompiling shader configuration while we rebuild. m_async_shader_compiler->ResizeWorkerThreads(g_ActiveConfig.GetShaderPrecompilerThreads()); @@ -140,7 +138,7 @@ const AbstractPipeline* ShaderCache::GetUberPipelineForUid(const GXUberPipelineU return it->second.first.get(); std::unique_ptr pipeline; - std::optional pipeline_config = GetGXUberPipelineConfig(uid); + std::optional pipeline_config = GetGXPipelineConfig(uid); if (pipeline_config) pipeline = g_renderer->CreatePipeline(*pipeline_config); return InsertGXUberPipeline(uid, std::move(pipeline)); @@ -176,8 +174,34 @@ void ShaderCache::WaitForAsyncCompiler() } } +template +static void SerializePipelineUid(const UidType& uid, SerializedUidType& serialized_uid) +{ + // Convert to disk format. Ensure all padding bytes are zero. + std::memset(&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 +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 -static void LoadShaderCache(T& cache, APIType api_type, const char* type, bool include_gameid) +void ShaderCache::LoadShaderCache(T& cache, APIType api_type, const char* type, bool include_gameid) { class CacheReader : public LinearDiskCacheReader { @@ -219,38 +243,127 @@ static void LoadShaderCache(T& cache, APIType api_type, const char* type, bool i } template -static void ClearShaderCache(T& cache) +void ShaderCache::ClearShaderCache(T& cache) { cache.disk_cache.Sync(); cache.disk_cache.Close(); cache.shader_map.clear(); } -void ShaderCache::LoadShaderCaches() +template +void ShaderCache::LoadPipelineCache(T& cache, LinearDiskCache& disk_cache, + APIType api_type, const char* type, bool include_gameid) { - // Ubershader caches, if present. - LoadShaderCache(m_uber_vs_cache, m_api_type, - "uber-vs", false); - LoadShaderCache(m_uber_ps_cache, m_api_type, - "uber-ps", false); + class CacheReader : public LinearDiskCacheReader + { + 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) + { + KeyType real_uid; + UnserializePipelineUid(key, real_uid); - // We also share geometry shaders, as there aren't many variants. - if (m_host_config.backend_geometry_shaders) - LoadShaderCache(m_gs_cache, m_api_type, "gs", false); + // Skip those which are already compiled. + if (failed || cache.find(real_uid) != cache.end()) + return; - // Specialized shaders, gameid-specific. - LoadShaderCache(m_vs_cache, m_api_type, "specialized-vs", - true); - LoadShaderCache(m_ps_cache, m_api_type, "specialized-ps", - true); + auto config = this_ptr->GetGXPipelineConfig(real_uid); + if (!config) + return; + + auto pipeline = g_renderer->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); + u32 count = disk_cache.OpenAndRead(filename, reader); + INFO_LOG(VIDEO, "Loaded %u cached pipelines from %s", count, filename.c_str()); + + // 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(VIDEO, "Failed to load one or more pipelines from cache '%s'. Discarding.", + filename.c_str()); + disk_cache.Close(); + File::Delete(filename); + disk_cache.OpenAndRead(filename, reader); + } } -void ShaderCache::ClearShaderCaches() +template +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(m_uber_vs_cache, m_api_type, + "uber-vs", false); + LoadShaderCache(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(m_gs_cache, m_api_type, "gs", + false); + + // Specialized shaders, gameid-specific. + LoadShaderCache(m_vs_cache, m_api_type, "specialized-vs", + true); + LoadShaderCache(m_ps_cache, m_api_type, "specialized-ps", + true); + } + + if (g_ActiveConfig.backend_info.bSupportsPipelineCacheData) + { + LoadPipelineCache( + m_gx_pipeline_cache, m_gx_pipeline_disk_cache, m_api_type, "specialized-pipeline", true); + LoadPipelineCache( + 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); @@ -265,37 +378,16 @@ void ShaderCache::CompileMissingPipelines() // Queue all uids with a null pipeline for compilation. for (auto& it : m_gx_pipeline_cache) { - if (!it.second.second) + if (!it.second.first) QueuePipelineCompile(it.first, COMPILE_PRIORITY_SHADERCACHE_PIPELINE); } for (auto& it : m_gx_uber_pipeline_cache) { - if (!it.second.second) + if (!it.second.first) QueueUberPipelineCompile(it.first, COMPILE_PRIORITY_UBERSHADER_PIPELINE); } } -void ShaderCache::InvalidateCachedPipelines() -{ - // Set the pending flag to false, and destroy the pipeline. - for (auto& it : m_gx_pipeline_cache) - { - it.second.first.reset(); - it.second.second = false; - } - for (auto& it : m_gx_uber_pipeline_cache) - { - it.second.first.reset(); - it.second.second = false; - } -} - -void ShaderCache::ClearPipelineCaches() -{ - m_gx_pipeline_cache.clear(); - m_gx_uber_pipeline_cache.clear(); -} - std::unique_ptr ShaderCache::CompileVertexShader(const VertexShaderUid& uid) const { ShaderCode source_code = GenerateVertexShaderCode(m_api_type, m_host_config, uid.GetUidData()); @@ -334,7 +426,7 @@ const AbstractShader* ShaderCache::InsertVertexShader(const VertexShaderUid& uid if (shader && !entry.shader) { - if (g_ActiveConfig.bShaderCache && shader->HasBinary()) + if (g_ActiveConfig.bShaderCache && g_ActiveConfig.backend_info.bSupportsShaderBinaries) { auto binary = shader->GetBinary(); if (!binary.empty()) @@ -356,7 +448,7 @@ const AbstractShader* ShaderCache::InsertVertexUberShader(const UberShader::Vert if (shader && !entry.shader) { - if (g_ActiveConfig.bShaderCache && shader->HasBinary()) + if (g_ActiveConfig.bShaderCache && g_ActiveConfig.backend_info.bSupportsShaderBinaries) { auto binary = shader->GetBinary(); if (!binary.empty()) @@ -378,7 +470,7 @@ const AbstractShader* ShaderCache::InsertPixelShader(const PixelShaderUid& uid, if (shader && !entry.shader) { - if (g_ActiveConfig.bShaderCache && shader->HasBinary()) + if (g_ActiveConfig.bShaderCache && g_ActiveConfig.backend_info.bSupportsShaderBinaries) { auto binary = shader->GetBinary(); if (!binary.empty()) @@ -400,7 +492,7 @@ const AbstractShader* ShaderCache::InsertPixelUberShader(const UberShader::Pixel if (shader && !entry.shader) { - if (g_ActiveConfig.bShaderCache && shader->HasBinary()) + if (g_ActiveConfig.bShaderCache && g_ActiveConfig.backend_info.bSupportsShaderBinaries) { auto binary = shader->GetBinary(); if (!binary.empty()) @@ -425,7 +517,7 @@ const AbstractShader* ShaderCache::CreateGeometryShader(const GeometryShaderUid& if (shader && !entry.shader) { - if (g_ActiveConfig.bShaderCache && shader->HasBinary()) + if (g_ActiveConfig.bShaderCache && g_ActiveConfig.backend_info.bSupportsShaderBinaries) { auto binary = shader->GetBinary(); if (!binary.empty()) @@ -505,7 +597,7 @@ std::optional ShaderCache::GetGXPipelineConfig(const GXP } std::optional -ShaderCache::GetGXUberPipelineConfig(const GXUberPipelineUid& config) +ShaderCache::GetGXPipelineConfig(const GXUberPipelineUid& config) { const AbstractShader* vs; auto vs_iter = m_uber_vs_cache.shader_map.find(config.vs_uid); @@ -551,6 +643,18 @@ const AbstractPipeline* ShaderCache::InsertGXPipeline(const GXPipelineUid& confi 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(cache_data.size())); + } + } + return entry.first.get(); } @@ -563,6 +667,18 @@ ShaderCache::InsertGXUberPipeline(const GXUberPipelineUid& config, 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(cache_data.size())); + } + } + return entry.first.get(); } @@ -648,14 +764,8 @@ void ShaderCache::ClosePipelineUIDCache() void ShaderCache::AddSerializedGXPipelineUID(const SerializedGXPipelineUid& uid) { - GXPipelineUid 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; + GXPipelineUid real_uid; + UnserializePipelineUid(uid, real_uid); auto iter = m_gx_pipeline_cache.find(real_uid); if (iter != m_gx_pipeline_cache.end()) @@ -671,16 +781,8 @@ void ShaderCache::AppendGXPipelineUID(const GXPipelineUid& config) if (!m_gx_pipeline_uid_cache_file.IsOpen()) return; - // Convert to disk format. Ensure all padding bytes are zero. SerializedGXPipelineUid disk_uid; - std::memset(&disk_uid, 0, sizeof(disk_uid)); - disk_uid.vertex_decl = config.vertex_format->GetVertexDeclaration(); - disk_uid.vs_uid = config.vs_uid; - disk_uid.gs_uid = config.gs_uid; - disk_uid.ps_uid = config.ps_uid; - disk_uid.rasterization_state_bits = config.rasterization_state.hex; - disk_uid.depth_state_bits = config.depth_state.hex; - disk_uid.blending_state_bits = config.blending_state.hex; + SerializePipelineUid(config, disk_uid); if (!m_gx_pipeline_uid_cache_file.WriteBytes(&disk_uid, sizeof(disk_uid))) { WARN_LOG(VIDEO, "Writing pipeline UID to cache failed, closing file."); @@ -885,7 +987,7 @@ void ShaderCache::QueueUberPipelineCompile(const GXUberPipelineUid& uid, u32 pri // 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->GetGXUberPipelineConfig(uid); + config = shader_cache->GetGXPipelineConfig(uid); } bool SetStagesReady() diff --git a/Source/Core/VideoCommon/ShaderCache.h b/Source/Core/VideoCommon/ShaderCache.h index 66caad93ac..16f6ca6f4a 100644 --- a/Source/Core/VideoCommon/ShaderCache.h +++ b/Source/Core/VideoCommon/ShaderCache.h @@ -111,13 +111,11 @@ private: static constexpr size_t NUM_PALETTE_CONVERSION_SHADERS = 3; void WaitForAsyncCompiler(); - void LoadShaderCaches(); - void ClearShaderCaches(); + void LoadCaches(); + void ClearCaches(); void LoadPipelineUIDCache(); void ClosePipelineUIDCache(); void CompileMissingPipelines(); - void InvalidateCachedPipelines(); - void ClearPipelineCaches(); void QueueUberShaderPipelines(); bool CompileSharedPipelines(); @@ -149,7 +147,7 @@ private: const RasterizationState& rasterization_state, const DepthState& depth_state, const BlendingState& blending_state); std::optional GetGXPipelineConfig(const GXPipelineUid& uid); - std::optional GetGXUberPipelineConfig(const GXUberPipelineUid& uid); + std::optional GetGXPipelineConfig(const GXUberPipelineUid& uid); const AbstractPipeline* InsertGXPipeline(const GXPipelineUid& config, std::unique_ptr pipeline); const AbstractPipeline* InsertGXUberPipeline(const GXUberPipelineUid& config, @@ -165,6 +163,17 @@ private: void QueuePipelineCompile(const GXPipelineUid& uid, u32 priority); void QueueUberPipelineCompile(const GXUberPipelineUid& uid, u32 priority); + // Populating various caches. + template + void LoadShaderCache(T& cache, APIType api_type, const char* type, bool include_gameid); + template + void ClearShaderCache(T& cache); + template + void LoadPipelineCache(T& cache, LinearDiskCache& disk_cache, APIType api_type, + const char* type, bool include_gameid); + template + 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 @@ -213,6 +222,8 @@ private: std::map, bool>> m_gx_uber_pipeline_cache; File::IOFile m_gx_pipeline_uid_cache_file; + LinearDiskCache m_gx_pipeline_disk_cache; + LinearDiskCache m_gx_uber_pipeline_disk_cache; // EFB copy to VRAM/RAM pipelines std::map> diff --git a/Source/Core/VideoCommon/VideoConfig.h b/Source/Core/VideoCommon/VideoConfig.h index 5be2a06639..b77ee1c584 100644 --- a/Source/Core/VideoCommon/VideoConfig.h +++ b/Source/Core/VideoCommon/VideoConfig.h @@ -219,6 +219,8 @@ struct VideoConfig final bool bSupportsBackgroundCompiling; bool bSupportsLargePoints; bool bSupportsPartialDepthCopies; + bool bSupportsShaderBinaries; + bool bSupportsPipelineCacheData; } backend_info; // Utility