mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40:04 +00:00 
			
		
		
		
	Add shader disk caching
This commit is contained in:
		
							parent
							
								
									ce3f8bf94e
								
							
						
					
					
						commit
						4e9ec4efd0
					
				
					 17 changed files with 1097 additions and 62 deletions
				
			
		|  | @ -31,6 +31,8 @@ add_library(video_core STATIC | ||||||
|     renderer_opengl/gl_resource_manager.h |     renderer_opengl/gl_resource_manager.h | ||||||
|     renderer_opengl/gl_shader_decompiler.cpp |     renderer_opengl/gl_shader_decompiler.cpp | ||||||
|     renderer_opengl/gl_shader_decompiler.h |     renderer_opengl/gl_shader_decompiler.h | ||||||
|  |     renderer_opengl/gl_shader_disk_cache.cpp | ||||||
|  |     renderer_opengl/gl_shader_disk_cache.h | ||||||
|     renderer_opengl/gl_shader_gen.cpp |     renderer_opengl/gl_shader_gen.cpp | ||||||
|     renderer_opengl/gl_shader_gen.h |     renderer_opengl/gl_shader_gen.h | ||||||
|     renderer_opengl/gl_shader_manager.cpp |     renderer_opengl/gl_shader_manager.cpp | ||||||
|  |  | ||||||
|  | @ -4,6 +4,8 @@ | ||||||
| 
 | 
 | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
|  | #include <atomic> | ||||||
|  | #include <functional> | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "core/hw/gpu.h" | #include "core/hw/gpu.h" | ||||||
| 
 | 
 | ||||||
|  | @ -17,6 +19,14 @@ struct OutputVertex; | ||||||
| 
 | 
 | ||||||
| namespace VideoCore { | namespace VideoCore { | ||||||
| 
 | 
 | ||||||
|  | enum class LoadCallbackStage { | ||||||
|  |     Prepare, | ||||||
|  |     Decompile, | ||||||
|  |     Build, | ||||||
|  |     Complete, | ||||||
|  | }; | ||||||
|  | using DiskResourceLoadCallback = std::function<void(LoadCallbackStage, std::size_t, std::size_t)>; | ||||||
|  | 
 | ||||||
| class RasterizerInterface { | class RasterizerInterface { | ||||||
| public: | public: | ||||||
|     virtual ~RasterizerInterface() {} |     virtual ~RasterizerInterface() {} | ||||||
|  | @ -71,5 +81,8 @@ public: | ||||||
|     virtual bool AccelerateDrawBatch(bool is_indexed) { |     virtual bool AccelerateDrawBatch(bool is_indexed) { | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     virtual void LoadDiskResources(const std::atomic_bool& stop_loading, | ||||||
|  |                                    const DiskResourceLoadCallback& callback) {} | ||||||
| }; | }; | ||||||
| } // namespace VideoCore
 | } // namespace VideoCore
 | ||||||
|  |  | ||||||
|  | @ -6,8 +6,9 @@ | ||||||
| 
 | 
 | ||||||
| #include <memory> | #include <memory> | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "core/core.h" | #include "core/frontend/emu_window.h" | ||||||
| #include "video_core/rasterizer_interface.h" | #include "video_core/rasterizer_interface.h" | ||||||
|  | #include "video_core/video_core.h" | ||||||
| 
 | 
 | ||||||
| namespace Frontend { | namespace Frontend { | ||||||
| class EmuWindow; | class EmuWindow; | ||||||
|  | @ -23,7 +24,7 @@ public: | ||||||
|     virtual ~RendererBase(); |     virtual ~RendererBase(); | ||||||
| 
 | 
 | ||||||
|     /// Initialize the renderer
 |     /// Initialize the renderer
 | ||||||
|     virtual Core::System::ResultStatus Init() = 0; |     virtual VideoCore::ResultStatus Init() = 0; | ||||||
| 
 | 
 | ||||||
|     /// Shutdown the renderer
 |     /// Shutdown the renderer
 | ||||||
|     virtual void ShutDown() = 0; |     virtual void ShutDown() = 0; | ||||||
|  |  | ||||||
|  | @ -15,6 +15,7 @@ | ||||||
| #include "common/microprofile.h" | #include "common/microprofile.h" | ||||||
| #include "common/scope_exit.h" | #include "common/scope_exit.h" | ||||||
| #include "common/vector_math.h" | #include "common/vector_math.h" | ||||||
|  | #include "core/core.h" | ||||||
| #include "core/hw/gpu.h" | #include "core/hw/gpu.h" | ||||||
| #include "video_core/pica_state.h" | #include "video_core/pica_state.h" | ||||||
| #include "video_core/regs_framebuffer.h" | #include "video_core/regs_framebuffer.h" | ||||||
|  | @ -171,6 +172,11 @@ RasterizerOpenGL::RasterizerOpenGL(Frontend::EmuWindow& window) | ||||||
| 
 | 
 | ||||||
| RasterizerOpenGL::~RasterizerOpenGL() {} | RasterizerOpenGL::~RasterizerOpenGL() {} | ||||||
| 
 | 
 | ||||||
|  | void RasterizerOpenGL::LoadDiskResources(const std::atomic_bool& stop_loading, | ||||||
|  |                                          const VideoCore::DiskResourceLoadCallback& callback) { | ||||||
|  |     shader_program_manager->LoadDiskCache(stop_loading, callback); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void RasterizerOpenGL::SyncEntireState() { | void RasterizerOpenGL::SyncEntireState() { | ||||||
|     // Sync fixed function OpenGL state
 |     // Sync fixed function OpenGL state
 | ||||||
|     SyncClipEnabled(); |     SyncClipEnabled(); | ||||||
|  | @ -378,16 +384,15 @@ void RasterizerOpenGL::SetupVertexArray(u8* array_ptr, GLintptr buffer_offset, | ||||||
| 
 | 
 | ||||||
| bool RasterizerOpenGL::SetupVertexShader() { | bool RasterizerOpenGL::SetupVertexShader() { | ||||||
|     MICROPROFILE_SCOPE(OpenGL_VS); |     MICROPROFILE_SCOPE(OpenGL_VS); | ||||||
|     PicaVSConfig vs_config(Pica::g_state.regs, Pica::g_state.vs); |     return shader_program_manager->UseProgrammableVertexShader(Pica::g_state.regs, | ||||||
|     return shader_program_manager->UseProgrammableVertexShader(vs_config, Pica::g_state.vs); |                                                                Pica::g_state.vs); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool RasterizerOpenGL::SetupGeometryShader() { | bool RasterizerOpenGL::SetupGeometryShader() { | ||||||
|     MICROPROFILE_SCOPE(OpenGL_GS); |     MICROPROFILE_SCOPE(OpenGL_GS); | ||||||
|     const auto& regs = Pica::g_state.regs; |     const auto& regs = Pica::g_state.regs; | ||||||
|     if (regs.pipeline.use_gs == Pica::PipelineRegs::UseGS::No) { |     if (regs.pipeline.use_gs == Pica::PipelineRegs::UseGS::No) { | ||||||
|         PicaFixedGSConfig gs_config(regs); |         shader_program_manager->UseFixedGeometryShader(regs); | ||||||
|         shader_program_manager->UseFixedGeometryShader(gs_config); |  | ||||||
|         return true; |         return true; | ||||||
|     } else { |     } else { | ||||||
|         LOG_ERROR(Render_OpenGL, "Accelerate draw doesn't support geometry shader"); |         LOG_ERROR(Render_OpenGL, "Accelerate draw doesn't support geometry shader"); | ||||||
|  | @ -1622,8 +1627,7 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig( | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void RasterizerOpenGL::SetShader() { | void RasterizerOpenGL::SetShader() { | ||||||
|     auto config = PicaFSConfig::BuildFromRegs(Pica::g_state.regs); |     shader_program_manager->UseFragmentShader(Pica::g_state.regs); | ||||||
|     shader_program_manager->UseFragmentShader(config); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void RasterizerOpenGL::SyncClipEnabled() { | void RasterizerOpenGL::SyncClipEnabled() { | ||||||
|  |  | ||||||
|  | @ -42,6 +42,9 @@ public: | ||||||
|     explicit RasterizerOpenGL(Frontend::EmuWindow& renderer); |     explicit RasterizerOpenGL(Frontend::EmuWindow& renderer); | ||||||
|     ~RasterizerOpenGL() override; |     ~RasterizerOpenGL() override; | ||||||
| 
 | 
 | ||||||
|  |     void LoadDiskResources(const std::atomic_bool& stop_loading, | ||||||
|  |                            const VideoCore::DiskResourceLoadCallback& callback) override; | ||||||
|  | 
 | ||||||
|     void AddTriangle(const Pica::Shader::OutputVertex& v0, const Pica::Shader::OutputVertex& v1, |     void AddTriangle(const Pica::Shader::OutputVertex& v0, const Pica::Shader::OutputVertex& v1, | ||||||
|                      const Pica::Shader::OutputVertex& v2) override; |                      const Pica::Shader::OutputVertex& v2) override; | ||||||
|     void DrawTriangles() override; |     void DrawTriangles() override; | ||||||
|  |  | ||||||
|  | @ -56,7 +56,7 @@ struct Subroutine { | ||||||
| /// Analyzes shader code and produces a set of subroutines.
 | /// Analyzes shader code and produces a set of subroutines.
 | ||||||
| class ControlFlowAnalyzer { | class ControlFlowAnalyzer { | ||||||
| public: | public: | ||||||
|     ControlFlowAnalyzer(const ProgramCode& program_code, u32 main_offset) |     ControlFlowAnalyzer(const Pica::Shader::ProgramCode& program_code, u32 main_offset) | ||||||
|         : program_code(program_code) { |         : program_code(program_code) { | ||||||
| 
 | 
 | ||||||
|         // Recursively finds all subroutines.
 |         // Recursively finds all subroutines.
 | ||||||
|  | @ -70,7 +70,7 @@ public: | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     const ProgramCode& program_code; |     const Pica::Shader::ProgramCode& program_code; | ||||||
|     std::set<Subroutine> subroutines; |     std::set<Subroutine> subroutines; | ||||||
|     std::map<std::pair<u32, u32>, ExitMethod> exit_method_map; |     std::map<std::pair<u32, u32>, ExitMethod> exit_method_map; | ||||||
| 
 | 
 | ||||||
|  | @ -246,8 +246,9 @@ constexpr auto GetSelectorSrc3 = GetSelectorSrc<&SwizzlePattern::GetSelectorSrc3 | ||||||
| 
 | 
 | ||||||
| class GLSLGenerator { | class GLSLGenerator { | ||||||
| public: | public: | ||||||
|     GLSLGenerator(const std::set<Subroutine>& subroutines, const ProgramCode& program_code, |     GLSLGenerator(const std::set<Subroutine>& subroutines, | ||||||
|                   const SwizzleData& swizzle_data, u32 main_offset, |                   const Pica::Shader::ProgramCode& program_code, | ||||||
|  |                   const Pica::Shader::SwizzleData& swizzle_data, u32 main_offset, | ||||||
|                   const RegGetter& inputreg_getter, const RegGetter& outputreg_getter, |                   const RegGetter& inputreg_getter, const RegGetter& outputreg_getter, | ||||||
|                   bool sanitize_mul) |                   bool sanitize_mul) | ||||||
|         : subroutines(subroutines), program_code(program_code), swizzle_data(swizzle_data), |         : subroutines(subroutines), program_code(program_code), swizzle_data(swizzle_data), | ||||||
|  | @ -865,8 +866,8 @@ private: | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     const std::set<Subroutine>& subroutines; |     const std::set<Subroutine>& subroutines; | ||||||
|     const ProgramCode& program_code; |     const Pica::Shader::ProgramCode& program_code; | ||||||
|     const SwizzleData& swizzle_data; |     const Pica::Shader::SwizzleData& swizzle_data; | ||||||
|     const u32 main_offset; |     const u32 main_offset; | ||||||
|     const RegGetter& inputreg_getter; |     const RegGetter& inputreg_getter; | ||||||
|     const RegGetter& outputreg_getter; |     const RegGetter& outputreg_getter; | ||||||
|  | @ -888,9 +889,9 @@ bool exec_shader(); | ||||||
| )"; | )"; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::optional<std::string> DecompileProgram(const ProgramCode& program_code, | std::optional<std::string> DecompileProgram(const Pica::Shader::ProgramCode& program_code, | ||||||
|                                             const SwizzleData& swizzle_data, u32 main_offset, |                                             const Pica::Shader::SwizzleData& swizzle_data, | ||||||
|                                             const RegGetter& inputreg_getter, |                                             u32 main_offset, const RegGetter& inputreg_getter, | ||||||
|                                             const RegGetter& outputreg_getter, bool sanitize_mul) { |                                             const RegGetter& outputreg_getter, bool sanitize_mul) { | ||||||
| 
 | 
 | ||||||
|     try { |     try { | ||||||
|  |  | ||||||
|  | @ -11,15 +11,14 @@ | ||||||
| 
 | 
 | ||||||
| namespace OpenGL::ShaderDecompiler { | namespace OpenGL::ShaderDecompiler { | ||||||
| 
 | 
 | ||||||
| using ProgramCode = std::array<u32, Pica::Shader::MAX_PROGRAM_CODE_LENGTH>; |  | ||||||
| using SwizzleData = std::array<u32, Pica::Shader::MAX_SWIZZLE_DATA_LENGTH>; |  | ||||||
| using RegGetter = std::function<std::string(u32)>; | using RegGetter = std::function<std::string(u32)>; | ||||||
|  | using ProgramResult = std::string; | ||||||
| 
 | 
 | ||||||
| std::string GetCommonDeclarations(); | std::string GetCommonDeclarations(); | ||||||
| 
 | 
 | ||||||
| std::optional<std::string> DecompileProgram(const ProgramCode& program_code, | std::optional<ProgramResult> DecompileProgram(const Pica::Shader::ProgramCode& program_code, | ||||||
|                                             const SwizzleData& swizzle_data, u32 main_offset, |                                               const Pica::Shader::SwizzleData& swizzle_data, | ||||||
|                                             const RegGetter& inputreg_getter, |                                               u32 main_offset, const RegGetter& inputreg_getter, | ||||||
|                                               const RegGetter& outputreg_getter, bool sanitize_mul); |                                               const RegGetter& outputreg_getter, bool sanitize_mul); | ||||||
| 
 | 
 | ||||||
| } // namespace OpenGL::ShaderDecompiler
 | } // namespace OpenGL::ShaderDecompiler
 | ||||||
|  |  | ||||||
							
								
								
									
										489
									
								
								src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										489
									
								
								src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,489 @@ | ||||||
|  | // Copyright 2019 yuzu Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #include <cstring> | ||||||
|  | #include <fmt/format.h> | ||||||
|  | 
 | ||||||
|  | #include "common/assert.h" | ||||||
|  | #include "common/common_paths.h" | ||||||
|  | #include "common/common_types.h" | ||||||
|  | #include "common/file_util.h" | ||||||
|  | #include "common/logging/log.h" | ||||||
|  | #include "common/scm_rev.h" | ||||||
|  | #include "common/zstd_compression.h" | ||||||
|  | #include "core/core.h" | ||||||
|  | #include "core/hle/kernel/process.h" | ||||||
|  | #include "core/settings.h" | ||||||
|  | #include "video_core/renderer_opengl/gl_shader_disk_cache.h" | ||||||
|  | 
 | ||||||
|  | namespace OpenGL { | ||||||
|  | 
 | ||||||
|  | using ShaderCacheVersionHash = std::array<u8, 64>; | ||||||
|  | 
 | ||||||
|  | enum class TransferableEntryKind : u32 { | ||||||
|  |     Raw, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | enum class PrecompiledEntryKind : u32 { | ||||||
|  |     Decompiled, | ||||||
|  |     Dump, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | constexpr u32 NativeVersion = 1; | ||||||
|  | 
 | ||||||
|  | ShaderCacheVersionHash GetShaderCacheVersionHash() { | ||||||
|  |     ShaderCacheVersionHash hash{}; | ||||||
|  |     const std::size_t length = std::min(std::strlen(Common::g_shader_cache_version), hash.size()); | ||||||
|  |     std::memcpy(hash.data(), Common::g_shader_cache_version, length); | ||||||
|  |     return hash; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ShaderDiskCacheRaw::ShaderDiskCacheRaw(u64 unique_identifier, ProgramType program_type, | ||||||
|  |                                        RawShaderConfig config, ProgramCode program_code) | ||||||
|  |     : unique_identifier{unique_identifier}, program_type{program_type}, config{config}, | ||||||
|  |       program_code{std::move(program_code)} {} | ||||||
|  | 
 | ||||||
|  | ShaderDiskCacheRaw::ShaderDiskCacheRaw() = default; | ||||||
|  | 
 | ||||||
|  | ShaderDiskCacheRaw::~ShaderDiskCacheRaw() = default; | ||||||
|  | 
 | ||||||
|  | bool ShaderDiskCacheRaw::Load(FileUtil::IOFile& file) { | ||||||
|  |     if (file.ReadBytes(&unique_identifier, sizeof(u64)) != sizeof(u64) || | ||||||
|  |         file.ReadBytes(&program_type, sizeof(u32)) != sizeof(u32)) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     u64 reg_array_len{}; | ||||||
|  |     if (file.ReadBytes(®_array_len, sizeof(u64)) != sizeof(u64)) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (file.ReadArray(config.reg_array.data(), reg_array_len) != reg_array_len) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Read in type specific configuration
 | ||||||
|  |     if (program_type == ProgramType::VS) { | ||||||
|  |         u64 code_len{}; | ||||||
|  |         if (file.ReadBytes(&code_len, sizeof(u64)) != sizeof(u64)) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         program_code.resize(code_len); | ||||||
|  |         if (file.ReadArray(program_code.data(), code_len) != code_len) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ShaderDiskCacheRaw::Save(FileUtil::IOFile& file) const { | ||||||
|  |     if (file.WriteObject(unique_identifier) != 1 || | ||||||
|  |         file.WriteObject(static_cast<u32>(program_type)) != 1) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Just for future proofing, save the sizes of the array to the file
 | ||||||
|  |     const std::size_t reg_array_len = Pica::Regs::NUM_REGS; | ||||||
|  |     if (file.WriteObject(static_cast<u64>(reg_array_len)) != 1) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     if (file.WriteArray(config.reg_array.data(), reg_array_len) != reg_array_len) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (program_type == ProgramType::VS) { | ||||||
|  |         const std::size_t code_len = program_code.size(); | ||||||
|  |         if (file.WriteObject(static_cast<u64>(code_len)) != 1) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         if (file.WriteArray(program_code.data(), code_len) != code_len) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ShaderDiskCache::ShaderDiskCache(bool separable) : separable{separable} {} | ||||||
|  | 
 | ||||||
|  | ShaderDiskCache::~ShaderDiskCache() = default; | ||||||
|  | 
 | ||||||
|  | std::optional<std::vector<ShaderDiskCacheRaw>> ShaderDiskCache::LoadTransferable() { | ||||||
|  |     const bool has_title_id = GetProgramID() != 0; | ||||||
|  |     if (!Settings::values.use_disk_shader_cache || !has_title_id) | ||||||
|  |         return {}; | ||||||
|  |     tried_to_load = true; | ||||||
|  | 
 | ||||||
|  |     FileUtil::IOFile file(GetTransferablePath(), "rb"); | ||||||
|  |     if (!file.IsOpen()) { | ||||||
|  |         LOG_INFO(Render_OpenGL, "No transferable shader cache found for game with title id={}", | ||||||
|  |                  GetTitleID()); | ||||||
|  |         return {}; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     u32 version{}; | ||||||
|  |     if (file.ReadBytes(&version, sizeof(version)) != sizeof(version)) { | ||||||
|  |         LOG_ERROR(Render_OpenGL, | ||||||
|  |                   "Failed to get transferable cache version for title id={} - skipping", | ||||||
|  |                   GetTitleID()); | ||||||
|  |         return {}; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (version < NativeVersion) { | ||||||
|  |         LOG_INFO(Render_OpenGL, "Transferable shader cache is old - removing"); | ||||||
|  |         file.Close(); | ||||||
|  |         InvalidateTransferable(); | ||||||
|  |         return {}; | ||||||
|  |     } | ||||||
|  |     if (version > NativeVersion) { | ||||||
|  |         LOG_WARNING(Render_OpenGL, "Transferable shader cache was generated with a newer version " | ||||||
|  |                                    "of the emulator - skipping"); | ||||||
|  |         return {}; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Version is valid, load the shaders
 | ||||||
|  |     std::vector<ShaderDiskCacheRaw> raws; | ||||||
|  |     while (file.Tell() < file.GetSize()) { | ||||||
|  |         TransferableEntryKind kind{}; | ||||||
|  |         if (file.ReadBytes(&kind, sizeof(u32)) != sizeof(u32)) { | ||||||
|  |             LOG_ERROR(Render_OpenGL, "Failed to read transferable file - skipping"); | ||||||
|  |             return {}; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         switch (kind) { | ||||||
|  |         case TransferableEntryKind::Raw: { | ||||||
|  |             ShaderDiskCacheRaw entry; | ||||||
|  |             if (!entry.Load(file)) { | ||||||
|  |                 LOG_ERROR(Render_OpenGL, "Failed to load transferable raw entry - skipping"); | ||||||
|  |                 return {}; | ||||||
|  |             } | ||||||
|  |             transferable.insert({entry.GetUniqueIdentifier(), {}}); | ||||||
|  |             raws.push_back(std::move(entry)); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         default: | ||||||
|  |             LOG_ERROR(Render_OpenGL, "Unknown transferable shader cache entry kind={} - skipping", | ||||||
|  |                       static_cast<u32>(kind)); | ||||||
|  |             return {}; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return {raws}; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::pair<std::unordered_map<u64, ShaderDiskCacheDecompiled>, ShaderDumpsMap> | ||||||
|  | ShaderDiskCache::LoadPrecompiled() { | ||||||
|  |     if (!IsUsable()) | ||||||
|  |         return {}; | ||||||
|  | 
 | ||||||
|  |     FileUtil::IOFile file(GetPrecompiledPath(), "rb"); | ||||||
|  |     if (!file.IsOpen()) { | ||||||
|  |         LOG_INFO(Render_OpenGL, "No precompiled shader cache found for game with title id={}", | ||||||
|  |                  GetTitleID()); | ||||||
|  |         return {}; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const auto result = LoadPrecompiledFile(file); | ||||||
|  |     if (!result) { | ||||||
|  |         LOG_INFO(Render_OpenGL, | ||||||
|  |                  "Failed to load precompiled cache for game with title id={} - removing", | ||||||
|  |                  GetTitleID()); | ||||||
|  |         file.Close(); | ||||||
|  |         InvalidatePrecompiled(); | ||||||
|  |         return {}; | ||||||
|  |     } | ||||||
|  |     return *result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::optional<std::pair<std::unordered_map<u64, ShaderDiskCacheDecompiled>, ShaderDumpsMap>> | ||||||
|  | ShaderDiskCache::LoadPrecompiledFile(FileUtil::IOFile& file) { | ||||||
|  |     // Read compressed file from disk and decompress to virtual precompiled cache file
 | ||||||
|  |     std::vector<u8> compressed(file.GetSize()); | ||||||
|  |     file.ReadBytes(compressed.data(), compressed.size()); | ||||||
|  |     const std::vector<u8> decompressed = Common::Compression::DecompressDataZSTD(compressed); | ||||||
|  |     SaveArrayToPrecompiled(decompressed.data(), decompressed.size()); | ||||||
|  |     precompiled_cache_virtual_file_offset = 0; | ||||||
|  | 
 | ||||||
|  |     ShaderCacheVersionHash file_hash{}; | ||||||
|  |     if (!LoadArrayFromPrecompiled(file_hash.data(), file_hash.size())) { | ||||||
|  |         precompiled_cache_virtual_file_offset = 0; | ||||||
|  |         return {}; | ||||||
|  |     } | ||||||
|  |     if (GetShaderCacheVersionHash() != file_hash) { | ||||||
|  |         LOG_INFO(Render_OpenGL, "Precompiled cache is from another version of the emulator"); | ||||||
|  |         precompiled_cache_virtual_file_offset = 0; | ||||||
|  |         return {}; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::unordered_map<u64, ShaderDiskCacheDecompiled> decompiled; | ||||||
|  |     ShaderDumpsMap dumps; | ||||||
|  |     while (precompiled_cache_virtual_file_offset < precompiled_cache_virtual_file.GetSize()) { | ||||||
|  |         PrecompiledEntryKind kind{}; | ||||||
|  |         if (!LoadObjectFromPrecompiled(kind)) { | ||||||
|  |             return {}; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         switch (kind) { | ||||||
|  |         case PrecompiledEntryKind::Decompiled: { | ||||||
|  |             u64 unique_identifier{}; | ||||||
|  |             if (!LoadObjectFromPrecompiled(unique_identifier)) { | ||||||
|  |                 return {}; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             auto entry = LoadDecompiledEntry(); | ||||||
|  |             if (!entry) { | ||||||
|  |                 return {}; | ||||||
|  |             } | ||||||
|  |             decompiled.insert({unique_identifier, std::move(*entry)}); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         case PrecompiledEntryKind::Dump: { | ||||||
|  |             u64 unique_identifier; | ||||||
|  |             if (!LoadObjectFromPrecompiled(unique_identifier)) { | ||||||
|  |                 return {}; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             ShaderDiskCacheDump dump; | ||||||
|  |             if (!LoadObjectFromPrecompiled(dump.binary_format)) { | ||||||
|  |                 return {}; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             u32 binary_length{}; | ||||||
|  |             if (!LoadObjectFromPrecompiled(binary_length)) { | ||||||
|  |                 return {}; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             dump.binary.resize(binary_length); | ||||||
|  |             if (!LoadArrayFromPrecompiled(dump.binary.data(), dump.binary.size())) { | ||||||
|  |                 return {}; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             dumps.insert({unique_identifier, dump}); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         default: | ||||||
|  |             return {}; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return {{decompiled, dumps}}; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::optional<ShaderDiskCacheDecompiled> ShaderDiskCache::LoadDecompiledEntry() { | ||||||
|  |     u32 code_size{}; | ||||||
|  |     if (!LoadObjectFromPrecompiled(code_size)) { | ||||||
|  |         return {}; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::string code(code_size, '\0'); | ||||||
|  |     if (!LoadArrayFromPrecompiled(code.data(), code.size())) { | ||||||
|  |         return {}; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     ShaderDiskCacheDecompiled entry; | ||||||
|  |     entry.code = std::move(code); | ||||||
|  | 
 | ||||||
|  |     return entry; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ShaderDiskCache::SaveDecompiledFile(u64 unique_identifier, | ||||||
|  |                                          const ShaderDecompiler::ProgramResult& code) { | ||||||
|  |     if (!SaveObjectToPrecompiled(static_cast<u32>(PrecompiledEntryKind::Decompiled)) || | ||||||
|  |         !SaveObjectToPrecompiled(unique_identifier) || | ||||||
|  |         !SaveObjectToPrecompiled(static_cast<u32>(code.size())) || | ||||||
|  |         !SaveArrayToPrecompiled(code.data(), code.size())) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void ShaderDiskCache::InvalidateTransferable() { | ||||||
|  |     if (!FileUtil::Delete(GetTransferablePath())) { | ||||||
|  |         LOG_ERROR(Render_OpenGL, "Failed to invalidate transferable file={}", | ||||||
|  |                   GetTransferablePath()); | ||||||
|  |     } | ||||||
|  |     InvalidatePrecompiled(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void ShaderDiskCache::InvalidatePrecompiled() { | ||||||
|  |     // Clear virtaul precompiled cache file
 | ||||||
|  |     precompiled_cache_virtual_file.Resize(0); | ||||||
|  | 
 | ||||||
|  |     if (!FileUtil::Delete(GetPrecompiledPath())) { | ||||||
|  |         LOG_ERROR(Render_OpenGL, "Failed to invalidate precompiled file={}", GetPrecompiledPath()); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void ShaderDiskCache::SaveRaw(const ShaderDiskCacheRaw& entry) { | ||||||
|  |     if (!IsUsable()) | ||||||
|  |         return; | ||||||
|  | 
 | ||||||
|  |     const u64 id = entry.GetUniqueIdentifier(); | ||||||
|  |     if (transferable.find(id) != transferable.end()) { | ||||||
|  |         // The shader already exists
 | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     FileUtil::IOFile file = AppendTransferableFile(); | ||||||
|  |     if (!file.IsOpen()) | ||||||
|  |         return; | ||||||
|  |     if (file.WriteObject(TransferableEntryKind::Raw) != 1 || !entry.Save(file)) { | ||||||
|  |         LOG_ERROR(Render_OpenGL, "Failed to save raw transferable cache entry - removing"); | ||||||
|  |         file.Close(); | ||||||
|  |         InvalidateTransferable(); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     transferable.insert({id, entry}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void ShaderDiskCache::SaveDecompiled(u64 unique_identifier, | ||||||
|  |                                      const ShaderDecompiler::ProgramResult& code) { | ||||||
|  |     if (!IsUsable()) | ||||||
|  |         return; | ||||||
|  | 
 | ||||||
|  |     if (precompiled_cache_virtual_file.GetSize() == 0) { | ||||||
|  |         SavePrecompiledHeaderToVirtualPrecompiledCache(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (!SaveDecompiledFile(unique_identifier, code)) { | ||||||
|  |         LOG_ERROR(Render_OpenGL, | ||||||
|  |                   "Failed to save decompiled entry to the precompiled file - removing"); | ||||||
|  |         InvalidatePrecompiled(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void ShaderDiskCache::SaveDump(u64 unique_identifier, GLuint program) { | ||||||
|  |     if (!IsUsable()) | ||||||
|  |         return; | ||||||
|  | 
 | ||||||
|  |     GLint binary_length{}; | ||||||
|  |     glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &binary_length); | ||||||
|  | 
 | ||||||
|  |     GLenum binary_format{}; | ||||||
|  |     std::vector<u8> binary(binary_length); | ||||||
|  |     glGetProgramBinary(program, binary_length, nullptr, &binary_format, binary.data()); | ||||||
|  | 
 | ||||||
|  |     if (!SaveObjectToPrecompiled(static_cast<u32>(PrecompiledEntryKind::Dump)) || | ||||||
|  |         !SaveObjectToPrecompiled(unique_identifier) || | ||||||
|  |         !SaveObjectToPrecompiled(static_cast<u32>(binary_format)) || | ||||||
|  |         !SaveObjectToPrecompiled(static_cast<u32>(binary_length)) || | ||||||
|  |         !SaveArrayToPrecompiled(binary.data(), binary.size())) { | ||||||
|  |         LOG_ERROR(Render_OpenGL, "Failed to save binary program file in shader={:016x} - removing", | ||||||
|  |                   unique_identifier); | ||||||
|  |         InvalidatePrecompiled(); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ShaderDiskCache::IsUsable() const { | ||||||
|  |     return tried_to_load && Settings::values.use_disk_shader_cache; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | FileUtil::IOFile ShaderDiskCache::AppendTransferableFile() { | ||||||
|  |     if (!EnsureDirectories()) | ||||||
|  |         return {}; | ||||||
|  | 
 | ||||||
|  |     const auto transferable_path{GetTransferablePath()}; | ||||||
|  |     const bool existed = FileUtil::Exists(transferable_path); | ||||||
|  | 
 | ||||||
|  |     FileUtil::IOFile file(transferable_path, "ab"); | ||||||
|  |     if (!file.IsOpen()) { | ||||||
|  |         LOG_ERROR(Render_OpenGL, "Failed to open transferable cache in path={}", transferable_path); | ||||||
|  |         return {}; | ||||||
|  |     } | ||||||
|  |     if (!existed || file.GetSize() == 0) { | ||||||
|  |         // If the file didn't exist, write its version
 | ||||||
|  |         if (file.WriteObject(NativeVersion) != 1) { | ||||||
|  |             LOG_ERROR(Render_OpenGL, "Failed to write transferable cache version in path={}", | ||||||
|  |                       transferable_path); | ||||||
|  |             return {}; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return file; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void ShaderDiskCache::SavePrecompiledHeaderToVirtualPrecompiledCache() { | ||||||
|  |     const auto hash{GetShaderCacheVersionHash()}; | ||||||
|  |     if (!SaveArrayToPrecompiled(hash.data(), hash.size())) { | ||||||
|  |         LOG_ERROR( | ||||||
|  |             Render_OpenGL, | ||||||
|  |             "Failed to write precompiled cache version hash to virtual precompiled cache file"); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void ShaderDiskCache::SaveVirtualPrecompiledFile() { | ||||||
|  |     precompiled_cache_virtual_file_offset = 0; | ||||||
|  |     const std::vector<u8>& uncompressed = precompiled_cache_virtual_file.ReadAllBytes(); | ||||||
|  |     const std::vector<u8>& compressed = | ||||||
|  |         Common::Compression::CompressDataZSTDDefault(uncompressed.data(), uncompressed.size()); | ||||||
|  | 
 | ||||||
|  |     const auto precompiled_path{GetPrecompiledPath()}; | ||||||
|  |     FileUtil::IOFile file(precompiled_path, "wb"); | ||||||
|  | 
 | ||||||
|  |     if (!file.IsOpen()) { | ||||||
|  |         LOG_ERROR(Render_OpenGL, "Failed to open precompiled cache in path={}", precompiled_path); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     if (file.WriteBytes(compressed.data(), compressed.size()) != compressed.size()) { | ||||||
|  |         LOG_ERROR(Render_OpenGL, "Failed to write precompiled cache version in path={}", | ||||||
|  |                   precompiled_path); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ShaderDiskCache::EnsureDirectories() const { | ||||||
|  |     const auto CreateDir = [](const std::string& dir) { | ||||||
|  |         if (!FileUtil::CreateDir(dir)) { | ||||||
|  |             LOG_ERROR(Render_OpenGL, "Failed to create directory={}", dir); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         return true; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     return CreateDir(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir)) && | ||||||
|  |            CreateDir(GetBaseDir()) && CreateDir(GetTransferableDir()) && | ||||||
|  |            CreateDir(GetPrecompiledDir()); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string ShaderDiskCache::GetTransferablePath() { | ||||||
|  |     return FileUtil::SanitizePath(GetTransferableDir() + DIR_SEP_CHR + GetTitleID() + ".bin"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string ShaderDiskCache::GetPrecompiledPath() { | ||||||
|  |     return FileUtil::SanitizePath(GetPrecompiledDir() + DIR_SEP_CHR + GetTitleID() + ".bin"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string ShaderDiskCache::GetTransferableDir() const { | ||||||
|  |     return GetBaseDir() + DIR_SEP "transferable"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string ShaderDiskCache::GetPrecompiledDir() const { | ||||||
|  |     return GetBaseDir() + DIR_SEP "precompiled"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string ShaderDiskCache::GetBaseDir() const { | ||||||
|  |     return FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir) + DIR_SEP "opengl"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | u64 ShaderDiskCache::GetProgramID() { | ||||||
|  |     // Skip games without title id
 | ||||||
|  |     if (program_id != 0) { | ||||||
|  |         return program_id; | ||||||
|  |     } | ||||||
|  |     if (Core::System::GetInstance().GetAppLoader().ReadProgramId(program_id) != | ||||||
|  |         Loader::ResultStatus::Success) { | ||||||
|  |         return 0; | ||||||
|  |     } | ||||||
|  |     return program_id; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string ShaderDiskCache::GetTitleID() { | ||||||
|  |     if (!title_id.empty()) { | ||||||
|  |         return title_id; | ||||||
|  |     } | ||||||
|  |     title_id = fmt::format("{:016X}", GetProgramID()); | ||||||
|  |     return title_id; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace OpenGL
 | ||||||
							
								
								
									
										213
									
								
								src/video_core/renderer_opengl/gl_shader_disk_cache.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										213
									
								
								src/video_core/renderer_opengl/gl_shader_disk_cache.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,213 @@ | ||||||
|  | // Copyright 2019 yuzu Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <array> | ||||||
|  | #include <bitset> | ||||||
|  | #include <optional> | ||||||
|  | #include <string> | ||||||
|  | #include <tuple> | ||||||
|  | #include <unordered_map> | ||||||
|  | #include <unordered_set> | ||||||
|  | #include <utility> | ||||||
|  | #include <vector> | ||||||
|  | 
 | ||||||
|  | #include <glad/glad.h> | ||||||
|  | 
 | ||||||
|  | #include "common/assert.h" | ||||||
|  | #include "common/common_types.h" | ||||||
|  | #include "common/vfs/vfs_vector.h" | ||||||
|  | #include "video_core/regs.h" | ||||||
|  | #include "video_core/renderer_opengl/gl_shader_decompiler.h" | ||||||
|  | #include "video_core/renderer_opengl/gl_shader_gen.h" | ||||||
|  | 
 | ||||||
|  | namespace Core { | ||||||
|  | class System; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | namespace FileUtil { | ||||||
|  | class IOFile; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | namespace OpenGL { | ||||||
|  | 
 | ||||||
|  | struct ShaderDiskCacheDecompiled; | ||||||
|  | struct ShaderDiskCacheDump; | ||||||
|  | 
 | ||||||
|  | using RawShaderConfig = Pica::Regs; | ||||||
|  | using ProgramCode = std::vector<u32>; | ||||||
|  | using ShaderDecompiledMap = std::unordered_map<u64, ShaderDiskCacheDecompiled>; | ||||||
|  | using ShaderDumpsMap = std::unordered_map<u64, ShaderDiskCacheDump>; | ||||||
|  | 
 | ||||||
|  | /// Describes a shader how it's used by the guest GPU
 | ||||||
|  | class ShaderDiskCacheRaw { | ||||||
|  | public: | ||||||
|  |     explicit ShaderDiskCacheRaw(u64 unique_identifier, ProgramType program_type, | ||||||
|  |                                 RawShaderConfig config, ProgramCode program_code); | ||||||
|  |     ShaderDiskCacheRaw(); | ||||||
|  |     ~ShaderDiskCacheRaw(); | ||||||
|  | 
 | ||||||
|  |     bool Load(FileUtil::IOFile& file); | ||||||
|  | 
 | ||||||
|  |     bool Save(FileUtil::IOFile& file) const; | ||||||
|  | 
 | ||||||
|  |     u64 GetUniqueIdentifier() const { | ||||||
|  |         return unique_identifier; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     ProgramType GetProgramType() const { | ||||||
|  |         return program_type; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const ProgramCode& GetProgramCode() const { | ||||||
|  |         return program_code; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const RawShaderConfig& GetRawShaderConfig() const { | ||||||
|  |         return config; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     u64 unique_identifier{}; | ||||||
|  |     ProgramType program_type{}; | ||||||
|  |     RawShaderConfig config{}; | ||||||
|  |     ProgramCode program_code{}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /// Contains decompiled data from a shader
 | ||||||
|  | struct ShaderDiskCacheDecompiled { | ||||||
|  |     ShaderDecompiler::ProgramResult code; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /// Contains an OpenGL dumped binary program
 | ||||||
|  | struct ShaderDiskCacheDump { | ||||||
|  |     GLenum binary_format; | ||||||
|  |     std::vector<u8> binary; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | class ShaderDiskCache { | ||||||
|  | public: | ||||||
|  |     explicit ShaderDiskCache(bool separable); | ||||||
|  |     ~ShaderDiskCache(); | ||||||
|  | 
 | ||||||
|  |     /// Loads transferable cache. If file has a old version or on failure, it deletes the file.
 | ||||||
|  |     std::optional<std::vector<ShaderDiskCacheRaw>> LoadTransferable(); | ||||||
|  | 
 | ||||||
|  |     /// Loads current game's precompiled cache. Invalidates on failure.
 | ||||||
|  |     std::pair<ShaderDecompiledMap, ShaderDumpsMap> LoadPrecompiled(); | ||||||
|  | 
 | ||||||
|  |     /// Removes the transferable (and precompiled) cache file.
 | ||||||
|  |     void InvalidateTransferable(); | ||||||
|  | 
 | ||||||
|  |     /// Removes the precompiled cache file and clears virtual precompiled cache file.
 | ||||||
|  |     void InvalidatePrecompiled(); | ||||||
|  | 
 | ||||||
|  |     /// Saves a raw dump to the transferable file. Checks for collisions.
 | ||||||
|  |     void SaveRaw(const ShaderDiskCacheRaw& entry); | ||||||
|  | 
 | ||||||
|  |     /// Saves a decompiled entry to the precompiled file. Does not check for collisions.
 | ||||||
|  |     void SaveDecompiled(u64 unique_identifier, const ShaderDecompiler::ProgramResult& code); | ||||||
|  | 
 | ||||||
|  |     /// Saves a dump entry to the precompiled file. Does not check for collisions.
 | ||||||
|  |     void SaveDump(u64 unique_identifier, GLuint program); | ||||||
|  | 
 | ||||||
|  |     /// Serializes virtual precompiled shader cache file to real file
 | ||||||
|  |     void SaveVirtualPrecompiledFile(); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     /// Loads the transferable cache. Returns empty on failure.
 | ||||||
|  |     std::optional<std::pair<ShaderDecompiledMap, ShaderDumpsMap>> LoadPrecompiledFile( | ||||||
|  |         FileUtil::IOFile& file); | ||||||
|  | 
 | ||||||
|  |     /// Loads a decompiled cache entry from m_precompiled_cache_virtual_file. Returns empty on
 | ||||||
|  |     /// failure.
 | ||||||
|  |     std::optional<ShaderDiskCacheDecompiled> LoadDecompiledEntry(); | ||||||
|  | 
 | ||||||
|  |     /// Saves a decompiled entry to the passed file. Returns true on success.
 | ||||||
|  |     bool SaveDecompiledFile(u64 unique_identifier, const ShaderDecompiler::ProgramResult& code); | ||||||
|  | 
 | ||||||
|  |     /// Returns if the cache can be used
 | ||||||
|  |     bool IsUsable() const; | ||||||
|  | 
 | ||||||
|  |     /// Opens current game's transferable file and write it's header if it doesn't exist
 | ||||||
|  |     FileUtil::IOFile AppendTransferableFile(); | ||||||
|  | 
 | ||||||
|  |     /// Save precompiled header to precompiled_cache_in_memory
 | ||||||
|  |     void SavePrecompiledHeaderToVirtualPrecompiledCache(); | ||||||
|  | 
 | ||||||
|  |     /// Create shader disk cache directories. Returns true on success.
 | ||||||
|  |     bool EnsureDirectories() const; | ||||||
|  | 
 | ||||||
|  |     /// Gets current game's transferable file path
 | ||||||
|  |     std::string GetTransferablePath(); | ||||||
|  | 
 | ||||||
|  |     /// Gets current game's precompiled file path
 | ||||||
|  |     std::string GetPrecompiledPath(); | ||||||
|  | 
 | ||||||
|  |     /// Get user's transferable directory path
 | ||||||
|  |     std::string GetTransferableDir() const; | ||||||
|  | 
 | ||||||
|  |     /// Get user's precompiled directory path
 | ||||||
|  |     std::string GetPrecompiledDir() const; | ||||||
|  | 
 | ||||||
|  |     /// Get user's shader directory path
 | ||||||
|  |     std::string GetBaseDir() const; | ||||||
|  | 
 | ||||||
|  |     /// Get current game's title id as u64
 | ||||||
|  |     u64 GetProgramID(); | ||||||
|  | 
 | ||||||
|  |     /// Get current game's title id
 | ||||||
|  |     std::string GetTitleID(); | ||||||
|  | 
 | ||||||
|  |     template <typename T> | ||||||
|  |     bool SaveArrayToPrecompiled(const T* data, std::size_t length) { | ||||||
|  |         const std::size_t write_length = precompiled_cache_virtual_file.WriteArray( | ||||||
|  |             data, length, precompiled_cache_virtual_file_offset); | ||||||
|  |         precompiled_cache_virtual_file_offset += write_length; | ||||||
|  |         return write_length == sizeof(T) * length; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     template <typename T> | ||||||
|  |     bool LoadArrayFromPrecompiled(T* data, std::size_t length) { | ||||||
|  |         const std::size_t read_length = precompiled_cache_virtual_file.ReadArray( | ||||||
|  |             data, length, precompiled_cache_virtual_file_offset); | ||||||
|  |         precompiled_cache_virtual_file_offset += read_length; | ||||||
|  |         return read_length == sizeof(T) * length; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     template <typename T> | ||||||
|  |     bool SaveObjectToPrecompiled(const T& object) { | ||||||
|  |         return SaveArrayToPrecompiled(&object, 1); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     bool SaveObjectToPrecompiled(bool object) { | ||||||
|  |         const auto value = static_cast<u8>(object); | ||||||
|  |         return SaveArrayToPrecompiled(&value, 1); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     template <typename T> | ||||||
|  |     bool LoadObjectFromPrecompiled(T& object) { | ||||||
|  |         return LoadArrayFromPrecompiled(&object, 1); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Stores whole precompiled cache which will be read from or saved to the precompiled chache
 | ||||||
|  |     // file
 | ||||||
|  |     Common::VectorVfsFile precompiled_cache_virtual_file; | ||||||
|  |     // Stores the current offset of the precompiled cache file for IO purposes
 | ||||||
|  |     std::size_t precompiled_cache_virtual_file_offset = 0; | ||||||
|  | 
 | ||||||
|  |     // Stored transferable shaders
 | ||||||
|  |     std::unordered_map<u64, ShaderDiskCacheRaw> transferable; | ||||||
|  | 
 | ||||||
|  |     // The cache has been loaded at boot
 | ||||||
|  |     bool tried_to_load{}; | ||||||
|  | 
 | ||||||
|  |     bool separable{}; | ||||||
|  | 
 | ||||||
|  |     u64 program_id{}; | ||||||
|  |     std::string title_id; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace OpenGL
 | ||||||
|  | @ -16,6 +16,8 @@ | ||||||
| 
 | 
 | ||||||
| namespace OpenGL { | namespace OpenGL { | ||||||
| 
 | 
 | ||||||
|  | enum class ProgramType : u32 { VS, GS, FS }; | ||||||
|  | 
 | ||||||
| enum Attributes { | enum Attributes { | ||||||
|     ATTRIBUTE_POSITION, |     ATTRIBUTE_POSITION, | ||||||
|     ATTRIBUTE_COLOR, |     ATTRIBUTE_COLOR, | ||||||
|  | @ -161,8 +163,11 @@ struct PicaShaderConfigCommon { | ||||||
|  * shader. |  * shader. | ||||||
|  */ |  */ | ||||||
| struct PicaVSConfig : Common::HashableStruct<PicaShaderConfigCommon> { | struct PicaVSConfig : Common::HashableStruct<PicaShaderConfigCommon> { | ||||||
|     explicit PicaVSConfig(const Pica::Regs& regs, Pica::Shader::ShaderSetup& setup) { |     explicit PicaVSConfig(const Pica::ShaderRegs& regs, Pica::Shader::ShaderSetup& setup) { | ||||||
|         state.Init(regs.vs, setup); |         state.Init(regs, setup); | ||||||
|  |     } | ||||||
|  |     explicit PicaVSConfig(PicaShaderConfigCommon& conf) { | ||||||
|  |         state = conf; | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,13 +3,79 @@ | ||||||
| // Refer to the license.txt file included.
 | // Refer to the license.txt file included.
 | ||||||
| 
 | 
 | ||||||
| #include <algorithm> | #include <algorithm> | ||||||
|  | #include <thread> | ||||||
| #include <unordered_map> | #include <unordered_map> | ||||||
| #include <boost/functional/hash.hpp> | #include <boost/functional/hash.hpp> | ||||||
| #include <boost/variant.hpp> | #include <boost/variant.hpp> | ||||||
|  | #include "core/core.h" | ||||||
|  | #include "video_core/renderer_opengl/gl_shader_disk_cache.h" | ||||||
| #include "video_core/renderer_opengl/gl_shader_manager.h" | #include "video_core/renderer_opengl/gl_shader_manager.h" | ||||||
| 
 | 
 | ||||||
| namespace OpenGL { | namespace OpenGL { | ||||||
| 
 | 
 | ||||||
|  | static u64 GetUniqueIdentifier(const Pica::Regs& regs, const ProgramCode& code) { | ||||||
|  |     u64 hash = 0; | ||||||
|  |     u64 regs_uid = Common::ComputeHash64(regs.reg_array.data(), Pica::Regs::NUM_REGS * sizeof(u32)); | ||||||
|  |     boost::hash_combine(hash, regs_uid); | ||||||
|  |     if (code.size() > 0) { | ||||||
|  |         u64 code_uid = Common::ComputeHash64(code.data(), code.size() * sizeof(u32)); | ||||||
|  |         boost::hash_combine(hash, code_uid); | ||||||
|  |     } | ||||||
|  |     return hash; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static OGLProgram GeneratePrecompiledProgram(const ShaderDiskCacheDump& dump, | ||||||
|  |                                              const std::set<GLenum>& supported_formats) { | ||||||
|  | 
 | ||||||
|  |     if (supported_formats.find(dump.binary_format) == supported_formats.end()) { | ||||||
|  |         LOG_INFO(Render_OpenGL, "Precompiled cache entry with unsupported format - removing"); | ||||||
|  |         return {}; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     auto shader = OGLProgram(); | ||||||
|  |     shader.handle = glCreateProgram(); | ||||||
|  |     glProgramParameteri(shader.handle, GL_PROGRAM_SEPARABLE, GL_TRUE); | ||||||
|  |     glProgramBinary(shader.handle, dump.binary_format, dump.binary.data(), | ||||||
|  |                     static_cast<GLsizei>(dump.binary.size())); | ||||||
|  | 
 | ||||||
|  |     GLint link_status{}; | ||||||
|  |     glGetProgramiv(shader.handle, GL_LINK_STATUS, &link_status); | ||||||
|  |     if (link_status == GL_FALSE) { | ||||||
|  |         LOG_INFO(Render_OpenGL, "Precompiled cache rejected by the driver - removing"); | ||||||
|  |         return {}; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return shader; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static std::set<GLenum> GetSupportedFormats() { | ||||||
|  |     std::set<GLenum> supported_formats; | ||||||
|  | 
 | ||||||
|  |     GLint num_formats{}; | ||||||
|  |     glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &num_formats); | ||||||
|  | 
 | ||||||
|  |     std::vector<GLint> formats(num_formats); | ||||||
|  |     glGetIntegerv(GL_PROGRAM_BINARY_FORMATS, formats.data()); | ||||||
|  | 
 | ||||||
|  |     for (const GLint format : formats) | ||||||
|  |         supported_formats.insert(static_cast<GLenum>(format)); | ||||||
|  |     return supported_formats; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static std::tuple<PicaVSConfig, Pica::Shader::ShaderSetup> BuildVSConfigFromRaw( | ||||||
|  |     const ShaderDiskCacheRaw& raw) { | ||||||
|  |     Pica::Shader::ProgramCode program_code{}; | ||||||
|  |     Pica::Shader::SwizzleData swizzle_data{}; | ||||||
|  |     std::copy_n(raw.GetProgramCode().begin(), Pica::Shader::MAX_PROGRAM_CODE_LENGTH, | ||||||
|  |                 program_code.begin()); | ||||||
|  |     std::copy_n(raw.GetProgramCode().begin() + Pica::Shader::MAX_PROGRAM_CODE_LENGTH, | ||||||
|  |                 Pica::Shader::MAX_SWIZZLE_DATA_LENGTH, swizzle_data.begin()); | ||||||
|  |     Pica::Shader::ShaderSetup setup; | ||||||
|  |     setup.program_code = program_code; | ||||||
|  |     setup.swizzle_data = swizzle_data; | ||||||
|  |     return {PicaVSConfig{raw.GetRawShaderConfig().vs, setup}, setup}; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static void SetShaderUniformBlockBinding(GLuint shader, const char* name, UniformBindings binding, | static void SetShaderUniformBlockBinding(GLuint shader, const char* name, UniformBindings binding, | ||||||
|                                          std::size_t expected_size) { |                                          std::size_t expected_size) { | ||||||
|     const GLuint ub_index = glGetUniformBlockIndex(shader, name); |     const GLuint ub_index = glGetUniformBlockIndex(shader, name); | ||||||
|  | @ -121,6 +187,10 @@ public: | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     void Inject(OGLProgram&& program) { | ||||||
|  |         shader_or_program = std::move(program); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| private: | private: | ||||||
|     boost::variant<OGLShader, OGLProgram> shader_or_program; |     boost::variant<OGLShader, OGLProgram> shader_or_program; | ||||||
| }; | }; | ||||||
|  | @ -143,13 +213,22 @@ template <typename KeyConfigType, std::string (*CodeGenerator)(const KeyConfigTy | ||||||
| class ShaderCache { | class ShaderCache { | ||||||
| public: | public: | ||||||
|     explicit ShaderCache(bool separable) : separable(separable) {} |     explicit ShaderCache(bool separable) : separable(separable) {} | ||||||
|     GLuint Get(const KeyConfigType& config) { |     std::tuple<GLuint, std::optional<ShaderDecompiler::ProgramResult>> Get( | ||||||
|  |         const KeyConfigType& config) { | ||||||
|         auto [iter, new_shader] = shaders.emplace(config, OGLShaderStage{separable}); |         auto [iter, new_shader] = shaders.emplace(config, OGLShaderStage{separable}); | ||||||
|         OGLShaderStage& cached_shader = iter->second; |         OGLShaderStage& cached_shader = iter->second; | ||||||
|  |         std::optional<ShaderDecompiler::ProgramResult> result{}; | ||||||
|         if (new_shader) { |         if (new_shader) { | ||||||
|             cached_shader.Create(CodeGenerator(config, separable).c_str(), ShaderType); |             result = CodeGenerator(config, separable); | ||||||
|  |             cached_shader.Create(result->c_str(), ShaderType); | ||||||
|         } |         } | ||||||
|         return cached_shader.GetHandle(); |         return {cached_shader.GetHandle(), result}; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void Inject(const KeyConfigType& key, std::string decomp, OGLProgram&& program) { | ||||||
|  |         OGLShaderStage stage{separable}; | ||||||
|  |         stage.Inject(std::move(program)); | ||||||
|  |         shaders.emplace(key, std::move(stage)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|  | @ -169,30 +248,41 @@ template <typename KeyConfigType, | ||||||
| class ShaderDoubleCache { | class ShaderDoubleCache { | ||||||
| public: | public: | ||||||
|     explicit ShaderDoubleCache(bool separable) : separable(separable) {} |     explicit ShaderDoubleCache(bool separable) : separable(separable) {} | ||||||
|     GLuint Get(const KeyConfigType& key, const Pica::Shader::ShaderSetup& setup) { |     std::tuple<GLuint, std::optional<ShaderDecompiler::ProgramResult>> Get( | ||||||
|  |         const KeyConfigType& key, const Pica::Shader::ShaderSetup& setup) { | ||||||
|  |         std::optional<ShaderDecompiler::ProgramResult> result{}; | ||||||
|         auto map_it = shader_map.find(key); |         auto map_it = shader_map.find(key); | ||||||
|         if (map_it == shader_map.end()) { |         if (map_it == shader_map.end()) { | ||||||
|             auto program_opt = CodeGenerator(setup, key, separable); |             auto program_opt = CodeGenerator(setup, key, separable); | ||||||
|             if (!program_opt) { |             if (!program_opt) { | ||||||
|                 shader_map[key] = nullptr; |                 shader_map[key] = nullptr; | ||||||
|                 return 0; |                 return {0, {}}; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             std::string& program = *program_opt; |             std::string& program = *program_opt; | ||||||
|             auto [iter, new_shader] = shader_cache.emplace(program, OGLShaderStage{separable}); |             auto [iter, new_shader] = shader_cache.emplace(program, OGLShaderStage{separable}); | ||||||
|             OGLShaderStage& cached_shader = iter->second; |             OGLShaderStage& cached_shader = iter->second; | ||||||
|             if (new_shader) { |             if (new_shader) { | ||||||
|  |                 result = program; | ||||||
|                 cached_shader.Create(program.c_str(), ShaderType); |                 cached_shader.Create(program.c_str(), ShaderType); | ||||||
|             } |             } | ||||||
|             shader_map[key] = &cached_shader; |             shader_map[key] = &cached_shader; | ||||||
|             return cached_shader.GetHandle(); |             return {cached_shader.GetHandle(), result}; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (map_it->second == nullptr) { |         if (map_it->second == nullptr) { | ||||||
|             return 0; |             return {0, {}}; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return map_it->second->GetHandle(); |         return {map_it->second->GetHandle(), {}}; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void Inject(const KeyConfigType& key, std::string decomp, OGLProgram&& program) { | ||||||
|  |         OGLShaderStage stage{separable}; | ||||||
|  |         stage.Inject(std::move(program)); | ||||||
|  |         auto [iter, new_shader] = shader_cache.emplace(decomp, std::move(stage)); | ||||||
|  |         OGLShaderStage& cached_shader = iter->second; | ||||||
|  |         shader_map[key] = &cached_shader; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|  | @ -214,7 +304,7 @@ public: | ||||||
|     explicit Impl(bool separable, bool is_amd) |     explicit Impl(bool separable, bool is_amd) | ||||||
|         : is_amd(is_amd), separable(separable), programmable_vertex_shaders(separable), |         : is_amd(is_amd), separable(separable), programmable_vertex_shaders(separable), | ||||||
|           trivial_vertex_shader(separable), fixed_geometry_shaders(separable), |           trivial_vertex_shader(separable), fixed_geometry_shaders(separable), | ||||||
|           fragment_shaders(separable) { |           fragment_shaders(separable), disk_cache(separable) { | ||||||
|         if (separable) |         if (separable) | ||||||
|             pipeline.Create(); |             pipeline.Create(); | ||||||
|     } |     } | ||||||
|  | @ -257,6 +347,7 @@ public: | ||||||
|     bool separable; |     bool separable; | ||||||
|     std::unordered_map<ShaderTuple, OGLProgram, ShaderTuple::Hash> program_cache; |     std::unordered_map<ShaderTuple, OGLProgram, ShaderTuple::Hash> program_cache; | ||||||
|     OGLPipeline pipeline; |     OGLPipeline pipeline; | ||||||
|  |     ShaderDiskCache disk_cache; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| ShaderProgramManager::ShaderProgramManager(bool separable, bool is_amd) | ShaderProgramManager::ShaderProgramManager(bool separable, bool is_amd) | ||||||
|  | @ -264,12 +355,23 @@ ShaderProgramManager::ShaderProgramManager(bool separable, bool is_amd) | ||||||
| 
 | 
 | ||||||
| ShaderProgramManager::~ShaderProgramManager() = default; | ShaderProgramManager::~ShaderProgramManager() = default; | ||||||
| 
 | 
 | ||||||
| bool ShaderProgramManager::UseProgrammableVertexShader(const PicaVSConfig& config, | bool ShaderProgramManager::UseProgrammableVertexShader(const Pica::Regs& regs, | ||||||
|                                                        const Pica::Shader::ShaderSetup setup) { |                                                        Pica::Shader::ShaderSetup& setup) { | ||||||
|     GLuint handle = impl->programmable_vertex_shaders.Get(config, setup); |     PicaVSConfig config{regs.vs, setup}; | ||||||
|  |     auto [handle, result] = impl->programmable_vertex_shaders.Get(config, setup); | ||||||
|     if (handle == 0) |     if (handle == 0) | ||||||
|         return false; |         return false; | ||||||
|     impl->current.vs = handle; |     impl->current.vs = handle; | ||||||
|  |     // Save VS to the disk cache if its a new shader
 | ||||||
|  |     if (result) { | ||||||
|  |         auto& disk_cache = impl->disk_cache; | ||||||
|  |         ProgramCode program_code{setup.program_code.begin(), setup.program_code.end()}; | ||||||
|  |         program_code.insert(program_code.end(), setup.swizzle_data.begin(), | ||||||
|  |                             setup.swizzle_data.end()); | ||||||
|  |         u64 unique_identifier = GetUniqueIdentifier(regs, program_code); | ||||||
|  |         ShaderDiskCacheRaw raw{unique_identifier, ProgramType::VS, regs, program_code}; | ||||||
|  |         disk_cache.SaveRaw(raw); | ||||||
|  |     } | ||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -277,25 +379,36 @@ void ShaderProgramManager::UseTrivialVertexShader() { | ||||||
|     impl->current.vs = impl->trivial_vertex_shader.Get(); |     impl->current.vs = impl->trivial_vertex_shader.Get(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void ShaderProgramManager::UseFixedGeometryShader(const PicaFixedGSConfig& config) { | void ShaderProgramManager::UseFixedGeometryShader(const Pica::Regs& regs) { | ||||||
|     impl->current.gs = impl->fixed_geometry_shaders.Get(config); |     PicaFixedGSConfig gs_config(regs); | ||||||
|  |     auto [handle, _] = impl->fixed_geometry_shaders.Get(gs_config); | ||||||
|  |     impl->current.gs = handle; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void ShaderProgramManager::UseTrivialGeometryShader() { | void ShaderProgramManager::UseTrivialGeometryShader() { | ||||||
|     impl->current.gs = 0; |     impl->current.gs = 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void ShaderProgramManager::UseFragmentShader(const PicaFSConfig& config) { | void ShaderProgramManager::UseFragmentShader(const Pica::Regs& regs) { | ||||||
|     impl->current.fs = impl->fragment_shaders.Get(config); |     PicaFSConfig config = PicaFSConfig::BuildFromRegs(regs); | ||||||
|  |     auto [handle, result] = impl->fragment_shaders.Get(config); | ||||||
|  |     impl->current.fs = handle; | ||||||
|  |     // Save FS to the disk cache if its a new shader
 | ||||||
|  |     if (result) { | ||||||
|  |         auto& disk_cache = impl->disk_cache; | ||||||
|  |         u64 unique_identifier = GetUniqueIdentifier(regs, {}); | ||||||
|  |         ShaderDiskCacheRaw raw{unique_identifier, ProgramType::FS, regs, {}}; | ||||||
|  |         disk_cache.SaveRaw(raw); | ||||||
|  |         disk_cache.SaveDecompiled(unique_identifier, *result); | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void ShaderProgramManager::ApplyTo(OpenGLState& state) { | void ShaderProgramManager::ApplyTo(OpenGLState& state) { | ||||||
|     if (impl->separable) { |     if (impl->separable) { | ||||||
|         if (impl->is_amd) { |         if (impl->is_amd) { | ||||||
|             // Without this reseting, AMD sometimes freezes when one stage is changed but not for
 |             // Without this reseting, AMD sometimes freezes when one stage is changed but not
 | ||||||
|             // the others.
 |             // for the others. On the other hand, including this reset seems to introduce memory
 | ||||||
|             // On the other hand, including this reset seems to introduce memory leak in Intel
 |             // leak in Intel Graphics.
 | ||||||
|             // Graphics.
 |  | ||||||
|             glUseProgramStages( |             glUseProgramStages( | ||||||
|                 impl->pipeline.handle, |                 impl->pipeline.handle, | ||||||
|                 GL_VERTEX_SHADER_BIT | GL_GEOMETRY_SHADER_BIT | GL_FRAGMENT_SHADER_BIT, 0); |                 GL_VERTEX_SHADER_BIT | GL_GEOMETRY_SHADER_BIT | GL_FRAGMENT_SHADER_BIT, 0); | ||||||
|  | @ -316,4 +429,178 @@ void ShaderProgramManager::ApplyTo(OpenGLState& state) { | ||||||
|         state.draw.shader_program = cached_program.handle; |         state.draw.shader_program = cached_program.handle; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | void ShaderProgramManager::LoadDiskCache(const std::atomic_bool& stop_loading, | ||||||
|  |                                          const VideoCore::DiskResourceLoadCallback& callback) { | ||||||
|  |     if (!impl->separable) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     auto& disk_cache = impl->disk_cache; | ||||||
|  |     const auto transferable = disk_cache.LoadTransferable(); | ||||||
|  |     if (!transferable) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     const auto raws = *transferable; | ||||||
|  | 
 | ||||||
|  |     auto [decompiled, dumps] = disk_cache.LoadPrecompiled(); | ||||||
|  | 
 | ||||||
|  |     if (stop_loading) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::set<GLenum> supported_formats = GetSupportedFormats(); | ||||||
|  | 
 | ||||||
|  |     // Track if precompiled cache was altered during loading to know if we have to serialize the
 | ||||||
|  |     // virtual precompiled cache file back to the hard drive
 | ||||||
|  |     bool precompiled_cache_altered = false; | ||||||
|  | 
 | ||||||
|  |     std::mutex mutex; | ||||||
|  |     std::size_t built_shaders = 0; // It doesn't have be atomic since it's used behind a mutex
 | ||||||
|  |     std::atomic_bool compilation_failed = false; | ||||||
|  |     if (callback) { | ||||||
|  |         callback(VideoCore::LoadCallbackStage::Decompile, 0, raws.size()); | ||||||
|  |     } | ||||||
|  |     std::vector<std::size_t> load_raws_index; | ||||||
|  |     // Loads both decompiled and precompiled shaders from the cache. If either one is missing for
 | ||||||
|  |     const auto LoadPrecompiledWorker = | ||||||
|  |         [&](std::size_t begin, std::size_t end, const std::vector<ShaderDiskCacheRaw>& raws, | ||||||
|  |             const ShaderDecompiledMap& decompiled, const ShaderDumpsMap& dumps) { | ||||||
|  |             for (std::size_t i = 0; i < end; ++i) { | ||||||
|  |                 if (stop_loading || compilation_failed) { | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |                 const auto& raw{raws[i]}; | ||||||
|  |                 const u64 unique_identifier{raw.GetUniqueIdentifier()}; | ||||||
|  | 
 | ||||||
|  |                 const u64 calculated_hash = | ||||||
|  |                     GetUniqueIdentifier(raw.GetRawShaderConfig(), raw.GetProgramCode()); | ||||||
|  |                 if (unique_identifier != calculated_hash) { | ||||||
|  |                     LOG_ERROR(Render_OpenGL, | ||||||
|  |                               "Invalid hash in entry={:016x} (obtained hash={:016x}) - removing " | ||||||
|  |                               "shader cache", | ||||||
|  |                               raw.GetUniqueIdentifier(), calculated_hash); | ||||||
|  |                     disk_cache.InvalidateTransferable(); | ||||||
|  |                     disk_cache.InvalidatePrecompiled(); | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 const auto dump{dumps.find(unique_identifier)}; | ||||||
|  |                 const auto decomp{decompiled.find(unique_identifier)}; | ||||||
|  |                 OGLProgram shader; | ||||||
|  | 
 | ||||||
|  |                 if (dump != dumps.end() && decomp != decompiled.end()) { | ||||||
|  |                     // If the shader is dumped, attempt to load it
 | ||||||
|  |                     shader = GeneratePrecompiledProgram(dump->second, supported_formats); | ||||||
|  |                     if (shader.handle == 0) { | ||||||
|  |                         // If any shader failed, stop trying to compile, delete the cache, and start
 | ||||||
|  |                         // loading from raws
 | ||||||
|  |                         compilation_failed = true; | ||||||
|  |                         return; | ||||||
|  |                     } | ||||||
|  |                     // we have both the binary shader and the decompiled, so inject it into the
 | ||||||
|  |                     // cache
 | ||||||
|  |                     if (raw.GetProgramType() == ProgramType::VS) { | ||||||
|  |                         auto [conf, setup] = BuildVSConfigFromRaw(raw); | ||||||
|  |                         std::scoped_lock lock(mutex); | ||||||
|  |                         impl->programmable_vertex_shaders.Inject(conf, decomp->second.code, | ||||||
|  |                                                                  std::move(shader)); | ||||||
|  |                     } else if (raw.GetProgramType() == ProgramType::FS) { | ||||||
|  |                         PicaFSConfig conf = PicaFSConfig::BuildFromRegs(raw.GetRawShaderConfig()); | ||||||
|  |                         std::scoped_lock lock(mutex); | ||||||
|  |                         impl->fragment_shaders.Inject(conf, decomp->second.code, std::move(shader)); | ||||||
|  |                     } else { | ||||||
|  |                         // Unsupported shader type got stored somehow so nuke the cache
 | ||||||
|  | 
 | ||||||
|  |                         LOG_CRITICAL(Frontend, "failed to load raw programtype {}", | ||||||
|  |                                      static_cast<u32>(raw.GetProgramType())); | ||||||
|  |                         compilation_failed = true; | ||||||
|  |                         return; | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     // Since precompiled didn't have the dump, we'll load them in the next phase
 | ||||||
|  |                     std::scoped_lock lock(mutex); | ||||||
|  |                     load_raws_index.push_back(i); | ||||||
|  |                 } | ||||||
|  |                 if (callback) { | ||||||
|  |                     callback(VideoCore::LoadCallbackStage::Decompile, i, raws.size()); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |     LoadPrecompiledWorker(0, raws.size(), raws, decompiled, dumps); | ||||||
|  | 
 | ||||||
|  |     if (compilation_failed) { | ||||||
|  |         // Invalidate the precompiled cache if a shader dumped shader was rejected
 | ||||||
|  |         disk_cache.InvalidatePrecompiled(); | ||||||
|  |         dumps.clear(); | ||||||
|  |         precompiled_cache_altered = true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (callback) { | ||||||
|  |         callback(VideoCore::LoadCallbackStage::Build, 0, raws.size()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     compilation_failed = false; | ||||||
|  | 
 | ||||||
|  |     const auto LoadTransferable = [&](std::size_t begin, std::size_t end, | ||||||
|  |                                       const std::vector<ShaderDiskCacheRaw>& raws) { | ||||||
|  |         for (std::size_t i = 0; i < end; ++i) { | ||||||
|  |             if (stop_loading || compilation_failed) { | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             const auto& raw{raws[i]}; | ||||||
|  |             const u64 unique_identifier{raw.GetUniqueIdentifier()}; | ||||||
|  | 
 | ||||||
|  |             GLuint handle{0}; | ||||||
|  |             std::optional<ShaderDecompiler::ProgramResult> result; | ||||||
|  |             // Otherwise decompile and build the shader at boot and save the result to the
 | ||||||
|  |             // precompiled file
 | ||||||
|  |             if (raw.GetProgramType() == ProgramType::VS) { | ||||||
|  |                 // TODO: This isn't the ideal place to lock, since we really only want to
 | ||||||
|  |                 // lock access to the shared cache
 | ||||||
|  |                 auto [conf, setup] = BuildVSConfigFromRaw(raw); | ||||||
|  |                 std::scoped_lock lock(mutex); | ||||||
|  |                 auto [h, r] = impl->programmable_vertex_shaders.Get(conf, setup); | ||||||
|  |                 handle = h; | ||||||
|  |                 result = r; | ||||||
|  |             } else if (raw.GetProgramType() == ProgramType::FS) { | ||||||
|  |                 PicaFSConfig conf = PicaFSConfig::BuildFromRegs(raw.GetRawShaderConfig()); | ||||||
|  |                 std::scoped_lock lock(mutex); | ||||||
|  |                 auto [h, r] = impl->fragment_shaders.Get(conf); | ||||||
|  |                 handle = h; | ||||||
|  |                 result = r; | ||||||
|  |             } else { | ||||||
|  |                 // Unsupported shader type got stored somehow so nuke the cache
 | ||||||
|  |                 LOG_CRITICAL(Frontend, "failed to load raw programtype {}", | ||||||
|  |                              static_cast<u32>(raw.GetProgramType())); | ||||||
|  |                 compilation_failed = true; | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             if (handle == 0) { | ||||||
|  |                 LOG_CRITICAL(Frontend, "compilation from raw failed {:x} {:x}", | ||||||
|  |                              raw.GetProgramCode().at(0), raw.GetProgramCode().at(1)); | ||||||
|  |                 compilation_failed = true; | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             disk_cache.SaveDecompiled(unique_identifier, *result); | ||||||
|  |             disk_cache.SaveDump(unique_identifier, handle); | ||||||
|  |             precompiled_cache_altered = true; | ||||||
|  | 
 | ||||||
|  |             if (callback) { | ||||||
|  |                 callback(VideoCore::LoadCallbackStage::Build, i, raws.size()); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     LoadTransferable(0, raws.size(), raws); | ||||||
|  | 
 | ||||||
|  |     if (compilation_failed) { | ||||||
|  |         disk_cache.InvalidateTransferable(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (precompiled_cache_altered) { | ||||||
|  |         disk_cache.SaveVirtualPrecompiledFile(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| } // namespace OpenGL
 | } // namespace OpenGL
 | ||||||
|  |  | ||||||
|  | @ -6,14 +6,21 @@ | ||||||
| 
 | 
 | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <glad/glad.h> | #include <glad/glad.h> | ||||||
|  | #include "video_core/rasterizer_interface.h" | ||||||
| #include "video_core/regs_lighting.h" | #include "video_core/regs_lighting.h" | ||||||
| #include "video_core/renderer_opengl/gl_resource_manager.h" | #include "video_core/renderer_opengl/gl_resource_manager.h" | ||||||
| #include "video_core/renderer_opengl/gl_shader_gen.h" | #include "video_core/renderer_opengl/gl_shader_gen.h" | ||||||
| #include "video_core/renderer_opengl/gl_state.h" | #include "video_core/renderer_opengl/gl_state.h" | ||||||
| #include "video_core/renderer_opengl/pica_to_gl.h" | #include "video_core/renderer_opengl/pica_to_gl.h" | ||||||
| 
 | 
 | ||||||
|  | namespace Core { | ||||||
|  | class System; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| namespace OpenGL { | namespace OpenGL { | ||||||
| 
 | 
 | ||||||
|  | class ShaderDiskCacheOpenGL; | ||||||
|  | 
 | ||||||
| enum class UniformBindings : GLuint { Common, VS, GS }; | enum class UniformBindings : GLuint { Common, VS, GS }; | ||||||
| 
 | 
 | ||||||
| struct LightSrc { | struct LightSrc { | ||||||
|  | @ -97,16 +104,18 @@ public: | ||||||
|     ShaderProgramManager(bool separable, bool is_amd); |     ShaderProgramManager(bool separable, bool is_amd); | ||||||
|     ~ShaderProgramManager(); |     ~ShaderProgramManager(); | ||||||
| 
 | 
 | ||||||
|     bool UseProgrammableVertexShader(const PicaVSConfig& config, |     void LoadDiskCache(const std::atomic_bool& stop_loading, | ||||||
|                                      const Pica::Shader::ShaderSetup setup); |                        const VideoCore::DiskResourceLoadCallback& callback); | ||||||
|  | 
 | ||||||
|  |     bool UseProgrammableVertexShader(const Pica::Regs& config, Pica::Shader::ShaderSetup& setup); | ||||||
| 
 | 
 | ||||||
|     void UseTrivialVertexShader(); |     void UseTrivialVertexShader(); | ||||||
| 
 | 
 | ||||||
|     void UseFixedGeometryShader(const PicaFixedGSConfig& config); |     void UseFixedGeometryShader(const Pica::Regs& regs); | ||||||
| 
 | 
 | ||||||
|     void UseTrivialGeometryShader(); |     void UseTrivialGeometryShader(); | ||||||
| 
 | 
 | ||||||
|     void UseFragmentShader(const PicaFSConfig& config); |     void UseFragmentShader(const Pica::Regs& config); | ||||||
| 
 | 
 | ||||||
|     void ApplyTo(OpenGLState& state); |     void ApplyTo(OpenGLState& state); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1002,9 +1002,9 @@ static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Initialize the renderer
 | /// Initialize the renderer
 | ||||||
| Core::System::ResultStatus RendererOpenGL::Init() { | VideoCore::ResultStatus RendererOpenGL::Init() { | ||||||
|     if (!gladLoadGL()) { |     if (!gladLoadGL()) { | ||||||
|         return Core::System::ResultStatus::ErrorVideoCore_ErrorBelowGL33; |         return VideoCore::ResultStatus::ErrorBelowGL33; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (GLAD_GL_KHR_debug) { |     if (GLAD_GL_KHR_debug) { | ||||||
|  | @ -1026,18 +1026,18 @@ Core::System::ResultStatus RendererOpenGL::Init() { | ||||||
|     telemetry_session.AddField(Telemetry::FieldType::UserSystem, "GPU_OpenGL_Version", gl_version); |     telemetry_session.AddField(Telemetry::FieldType::UserSystem, "GPU_OpenGL_Version", gl_version); | ||||||
| 
 | 
 | ||||||
|     if (!strcmp(gpu_vendor, "GDI Generic")) { |     if (!strcmp(gpu_vendor, "GDI Generic")) { | ||||||
|         return Core::System::ResultStatus::ErrorVideoCore_ErrorGenericDrivers; |         return VideoCore::ResultStatus::ErrorGenericDrivers; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (!(GLAD_GL_VERSION_3_3 || GLAD_GL_ES_VERSION_3_1)) { |     if (!(GLAD_GL_VERSION_3_3 || GLAD_GL_ES_VERSION_3_1)) { | ||||||
|         return Core::System::ResultStatus::ErrorVideoCore_ErrorBelowGL33; |         return VideoCore::ResultStatus::ErrorBelowGL33; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     InitOpenGLObjects(); |     InitOpenGLObjects(); | ||||||
| 
 | 
 | ||||||
|     RefreshRasterizerSetting(); |     RefreshRasterizerSetting(); | ||||||
| 
 | 
 | ||||||
|     return Core::System::ResultStatus::Success; |     return VideoCore::ResultStatus::Success; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Shutdown the renderer
 | /// Shutdown the renderer
 | ||||||
|  |  | ||||||
|  | @ -48,7 +48,7 @@ public: | ||||||
|     ~RendererOpenGL() override; |     ~RendererOpenGL() override; | ||||||
| 
 | 
 | ||||||
|     /// Initialize the renderer
 |     /// Initialize the renderer
 | ||||||
|     Core::System::ResultStatus Init() override; |     VideoCore::ResultStatus Init() override; | ||||||
| 
 | 
 | ||||||
|     /// Shutdown the renderer
 |     /// Shutdown the renderer
 | ||||||
|     void ShutDown() override; |     void ShutDown() override; | ||||||
|  |  | ||||||
|  | @ -26,6 +26,8 @@ namespace Pica::Shader { | ||||||
| 
 | 
 | ||||||
| constexpr unsigned MAX_PROGRAM_CODE_LENGTH = 4096; | constexpr unsigned MAX_PROGRAM_CODE_LENGTH = 4096; | ||||||
| constexpr unsigned MAX_SWIZZLE_DATA_LENGTH = 4096; | constexpr unsigned MAX_SWIZZLE_DATA_LENGTH = 4096; | ||||||
|  | using ProgramCode = std::array<u32, MAX_PROGRAM_CODE_LENGTH>; | ||||||
|  | using SwizzleData = std::array<u32, MAX_SWIZZLE_DATA_LENGTH>; | ||||||
| 
 | 
 | ||||||
| struct AttributeBuffer { | struct AttributeBuffer { | ||||||
|     alignas(16) Common::Vec4<float24> attr[16]; |     alignas(16) Common::Vec4<float24> attr[16]; | ||||||
|  | @ -196,8 +198,8 @@ struct Uniforms { | ||||||
| struct ShaderSetup { | struct ShaderSetup { | ||||||
|     Uniforms uniforms; |     Uniforms uniforms; | ||||||
| 
 | 
 | ||||||
|     std::array<u32, MAX_PROGRAM_CODE_LENGTH> program_code; |     ProgramCode program_code; | ||||||
|     std::array<u32, MAX_SWIZZLE_DATA_LENGTH> swizzle_data; |     SwizzleData swizzle_data; | ||||||
| 
 | 
 | ||||||
|     /// Data private to ShaderEngines
 |     /// Data private to ShaderEngines
 | ||||||
|     struct EngineData { |     struct EngineData { | ||||||
|  |  | ||||||
|  | @ -22,6 +22,7 @@ std::atomic<bool> g_hw_renderer_enabled; | ||||||
| std::atomic<bool> g_shader_jit_enabled; | std::atomic<bool> g_shader_jit_enabled; | ||||||
| std::atomic<bool> g_hw_shader_enabled; | std::atomic<bool> g_hw_shader_enabled; | ||||||
| std::atomic<bool> g_hw_shader_accurate_mul; | std::atomic<bool> g_hw_shader_accurate_mul; | ||||||
|  | std::atomic<bool> g_use_disk_shader_cache; | ||||||
| std::atomic<bool> g_renderer_bg_color_update_requested; | std::atomic<bool> g_renderer_bg_color_update_requested; | ||||||
| std::atomic<bool> g_renderer_sampler_update_requested; | std::atomic<bool> g_renderer_sampler_update_requested; | ||||||
| std::atomic<bool> g_renderer_shader_update_requested; | std::atomic<bool> g_renderer_shader_update_requested; | ||||||
|  | @ -34,16 +35,16 @@ Layout::FramebufferLayout g_screenshot_framebuffer_layout; | ||||||
| Memory::MemorySystem* g_memory; | Memory::MemorySystem* g_memory; | ||||||
| 
 | 
 | ||||||
| /// Initialize the video core
 | /// Initialize the video core
 | ||||||
| Core::System::ResultStatus Init(Frontend::EmuWindow& emu_window, Memory::MemorySystem& memory) { | ResultStatus Init(Frontend::EmuWindow& emu_window, Memory::MemorySystem& memory) { | ||||||
|     g_memory = &memory; |     g_memory = &memory; | ||||||
|     Pica::Init(); |     Pica::Init(); | ||||||
| 
 | 
 | ||||||
|     OpenGL::GLES = Settings::values.use_gles; |     OpenGL::GLES = Settings::values.use_gles; | ||||||
| 
 | 
 | ||||||
|     g_renderer = std::make_unique<OpenGL::RendererOpenGL>(emu_window); |     g_renderer = std::make_unique<OpenGL::RendererOpenGL>(emu_window); | ||||||
|     Core::System::ResultStatus result = g_renderer->Init(); |     ResultStatus result = g_renderer->Init(); | ||||||
| 
 | 
 | ||||||
|     if (result != Core::System::ResultStatus::Success) { |     if (result != ResultStatus::Success) { | ||||||
|         LOG_ERROR(Render, "initialization failed !"); |         LOG_ERROR(Render, "initialization failed !"); | ||||||
|     } else { |     } else { | ||||||
|         LOG_DEBUG(Render, "initialized OK"); |         LOG_DEBUG(Render, "initialized OK"); | ||||||
|  |  | ||||||
|  | @ -6,7 +6,6 @@ | ||||||
| 
 | 
 | ||||||
| #include <atomic> | #include <atomic> | ||||||
| #include <memory> | #include <memory> | ||||||
| #include "core/core.h" |  | ||||||
| #include "core/frontend/emu_window.h" | #include "core/frontend/emu_window.h" | ||||||
| 
 | 
 | ||||||
| namespace Frontend { | namespace Frontend { | ||||||
|  | @ -32,6 +31,7 @@ extern std::atomic<bool> g_hw_renderer_enabled; | ||||||
| extern std::atomic<bool> g_shader_jit_enabled; | extern std::atomic<bool> g_shader_jit_enabled; | ||||||
| extern std::atomic<bool> g_hw_shader_enabled; | extern std::atomic<bool> g_hw_shader_enabled; | ||||||
| extern std::atomic<bool> g_hw_shader_accurate_mul; | extern std::atomic<bool> g_hw_shader_accurate_mul; | ||||||
|  | extern std::atomic<bool> g_use_disk_shader_cache; | ||||||
| extern std::atomic<bool> g_renderer_bg_color_update_requested; | extern std::atomic<bool> g_renderer_bg_color_update_requested; | ||||||
| extern std::atomic<bool> g_renderer_sampler_update_requested; | extern std::atomic<bool> g_renderer_sampler_update_requested; | ||||||
| extern std::atomic<bool> g_renderer_shader_update_requested; | extern std::atomic<bool> g_renderer_shader_update_requested; | ||||||
|  | @ -43,8 +43,14 @@ extern Layout::FramebufferLayout g_screenshot_framebuffer_layout; | ||||||
| 
 | 
 | ||||||
| extern Memory::MemorySystem* g_memory; | extern Memory::MemorySystem* g_memory; | ||||||
| 
 | 
 | ||||||
|  | enum class ResultStatus { | ||||||
|  |     Success, | ||||||
|  |     ErrorGenericDrivers, | ||||||
|  |     ErrorBelowGL33, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| /// Initialize the video core
 | /// Initialize the video core
 | ||||||
| Core::System::ResultStatus Init(Frontend::EmuWindow& emu_window, Memory::MemorySystem& memory); | ResultStatus Init(Frontend::EmuWindow& emu_window, Memory::MemorySystem& memory); | ||||||
| 
 | 
 | ||||||
| /// Shutdown the video core
 | /// Shutdown the video core
 | ||||||
| void Shutdown(); | void Shutdown(); | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue