From 4e9ec4efd08784265c37310ea5aa5e49067fb487 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Sat, 7 Sep 2019 16:11:09 -0600 Subject: [PATCH] Add shader disk caching --- src/video_core/CMakeLists.txt | 2 + src/video_core/rasterizer_interface.h | 13 + src/video_core/renderer_base.h | 5 +- .../renderer_opengl/gl_rasterizer.cpp | 16 +- .../renderer_opengl/gl_rasterizer.h | 3 + .../renderer_opengl/gl_shader_decompiler.cpp | 19 +- .../renderer_opengl/gl_shader_decompiler.h | 11 +- .../renderer_opengl/gl_shader_disk_cache.cpp | 489 ++++++++++++++++++ .../renderer_opengl/gl_shader_disk_cache.h | 213 ++++++++ .../renderer_opengl/gl_shader_gen.h | 9 +- .../renderer_opengl/gl_shader_manager.cpp | 327 +++++++++++- .../renderer_opengl/gl_shader_manager.h | 17 +- .../renderer_opengl/renderer_opengl.cpp | 10 +- .../renderer_opengl/renderer_opengl.h | 2 +- src/video_core/shader/shader.h | 6 +- src/video_core/video_core.cpp | 7 +- src/video_core/video_core.h | 10 +- 17 files changed, 1097 insertions(+), 62 deletions(-) create mode 100644 src/video_core/renderer_opengl/gl_shader_disk_cache.cpp create mode 100644 src/video_core/renderer_opengl/gl_shader_disk_cache.h diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 3129982ed3..4cb9763543 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -31,6 +31,8 @@ add_library(video_core STATIC renderer_opengl/gl_resource_manager.h renderer_opengl/gl_shader_decompiler.cpp 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.h renderer_opengl/gl_shader_manager.cpp diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h index 7222e97ce7..468d84084a 100644 --- a/src/video_core/rasterizer_interface.h +++ b/src/video_core/rasterizer_interface.h @@ -4,6 +4,8 @@ #pragma once +#include +#include #include "common/common_types.h" #include "core/hw/gpu.h" @@ -17,6 +19,14 @@ struct OutputVertex; namespace VideoCore { +enum class LoadCallbackStage { + Prepare, + Decompile, + Build, + Complete, +}; +using DiskResourceLoadCallback = std::function; + class RasterizerInterface { public: virtual ~RasterizerInterface() {} @@ -71,5 +81,8 @@ public: virtual bool AccelerateDrawBatch(bool is_indexed) { return false; } + + virtual void LoadDiskResources(const std::atomic_bool& stop_loading, + const DiskResourceLoadCallback& callback) {} }; } // namespace VideoCore diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index b1ee6a973f..cecf2a3235 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h @@ -6,8 +6,9 @@ #include #include "common/common_types.h" -#include "core/core.h" +#include "core/frontend/emu_window.h" #include "video_core/rasterizer_interface.h" +#include "video_core/video_core.h" namespace Frontend { class EmuWindow; @@ -23,7 +24,7 @@ public: virtual ~RendererBase(); /// Initialize the renderer - virtual Core::System::ResultStatus Init() = 0; + virtual VideoCore::ResultStatus Init() = 0; /// Shutdown the renderer virtual void ShutDown() = 0; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index dde0ac4451..a3f6fd9b92 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -15,6 +15,7 @@ #include "common/microprofile.h" #include "common/scope_exit.h" #include "common/vector_math.h" +#include "core/core.h" #include "core/hw/gpu.h" #include "video_core/pica_state.h" #include "video_core/regs_framebuffer.h" @@ -171,6 +172,11 @@ RasterizerOpenGL::RasterizerOpenGL(Frontend::EmuWindow& window) 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() { // Sync fixed function OpenGL state SyncClipEnabled(); @@ -378,16 +384,15 @@ void RasterizerOpenGL::SetupVertexArray(u8* array_ptr, GLintptr buffer_offset, bool RasterizerOpenGL::SetupVertexShader() { MICROPROFILE_SCOPE(OpenGL_VS); - PicaVSConfig vs_config(Pica::g_state.regs, Pica::g_state.vs); - return shader_program_manager->UseProgrammableVertexShader(vs_config, Pica::g_state.vs); + return shader_program_manager->UseProgrammableVertexShader(Pica::g_state.regs, + Pica::g_state.vs); } bool RasterizerOpenGL::SetupGeometryShader() { MICROPROFILE_SCOPE(OpenGL_GS); const auto& regs = Pica::g_state.regs; if (regs.pipeline.use_gs == Pica::PipelineRegs::UseGS::No) { - PicaFixedGSConfig gs_config(regs); - shader_program_manager->UseFixedGeometryShader(gs_config); + shader_program_manager->UseFixedGeometryShader(regs); return true; } else { LOG_ERROR(Render_OpenGL, "Accelerate draw doesn't support geometry shader"); @@ -1622,8 +1627,7 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig( } void RasterizerOpenGL::SetShader() { - auto config = PicaFSConfig::BuildFromRegs(Pica::g_state.regs); - shader_program_manager->UseFragmentShader(config); + shader_program_manager->UseFragmentShader(Pica::g_state.regs); } void RasterizerOpenGL::SyncClipEnabled() { diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 6ba23762d4..b3356a69b6 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -42,6 +42,9 @@ public: explicit RasterizerOpenGL(Frontend::EmuWindow& renderer); ~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, const Pica::Shader::OutputVertex& v2) override; void DrawTriangles() override; diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 19ac13ce26..693c74cc36 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -56,7 +56,7 @@ struct Subroutine { /// Analyzes shader code and produces a set of subroutines. class ControlFlowAnalyzer { public: - ControlFlowAnalyzer(const ProgramCode& program_code, u32 main_offset) + ControlFlowAnalyzer(const Pica::Shader::ProgramCode& program_code, u32 main_offset) : program_code(program_code) { // Recursively finds all subroutines. @@ -70,7 +70,7 @@ public: } private: - const ProgramCode& program_code; + const Pica::Shader::ProgramCode& program_code; std::set subroutines; std::map, ExitMethod> exit_method_map; @@ -246,8 +246,9 @@ constexpr auto GetSelectorSrc3 = GetSelectorSrc<&SwizzlePattern::GetSelectorSrc3 class GLSLGenerator { public: - GLSLGenerator(const std::set& subroutines, const ProgramCode& program_code, - const SwizzleData& swizzle_data, u32 main_offset, + GLSLGenerator(const std::set& subroutines, + const Pica::Shader::ProgramCode& program_code, + const Pica::Shader::SwizzleData& swizzle_data, u32 main_offset, const RegGetter& inputreg_getter, const RegGetter& outputreg_getter, bool sanitize_mul) : subroutines(subroutines), program_code(program_code), swizzle_data(swizzle_data), @@ -865,8 +866,8 @@ private: private: const std::set& subroutines; - const ProgramCode& program_code; - const SwizzleData& swizzle_data; + const Pica::Shader::ProgramCode& program_code; + const Pica::Shader::SwizzleData& swizzle_data; const u32 main_offset; const RegGetter& inputreg_getter; const RegGetter& outputreg_getter; @@ -888,9 +889,9 @@ bool exec_shader(); )"; } -std::optional DecompileProgram(const ProgramCode& program_code, - const SwizzleData& swizzle_data, u32 main_offset, - const RegGetter& inputreg_getter, +std::optional DecompileProgram(const Pica::Shader::ProgramCode& program_code, + const Pica::Shader::SwizzleData& swizzle_data, + u32 main_offset, const RegGetter& inputreg_getter, const RegGetter& outputreg_getter, bool sanitize_mul) { try { diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.h b/src/video_core/renderer_opengl/gl_shader_decompiler.h index 51befb91d1..ea86386ab8 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.h +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.h @@ -11,15 +11,14 @@ namespace OpenGL::ShaderDecompiler { -using ProgramCode = std::array; -using SwizzleData = std::array; using RegGetter = std::function; +using ProgramResult = std::string; std::string GetCommonDeclarations(); -std::optional DecompileProgram(const ProgramCode& program_code, - const SwizzleData& swizzle_data, u32 main_offset, - const RegGetter& inputreg_getter, - const RegGetter& outputreg_getter, bool sanitize_mul); +std::optional DecompileProgram(const Pica::Shader::ProgramCode& program_code, + const Pica::Shader::SwizzleData& swizzle_data, + u32 main_offset, const RegGetter& inputreg_getter, + const RegGetter& outputreg_getter, bool sanitize_mul); } // namespace OpenGL::ShaderDecompiler diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp new file mode 100644 index 0000000000..78efb46d5d --- /dev/null +++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp @@ -0,0 +1,489 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include + +#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; + +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(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(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(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> 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 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(kind)); + return {}; + } + } + + return {raws}; +} + +std::pair, 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, ShaderDumpsMap>> +ShaderDiskCache::LoadPrecompiledFile(FileUtil::IOFile& file) { + // Read compressed file from disk and decompress to virtual precompiled cache file + std::vector compressed(file.GetSize()); + file.ReadBytes(compressed.data(), compressed.size()); + const std::vector 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 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 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(PrecompiledEntryKind::Decompiled)) || + !SaveObjectToPrecompiled(unique_identifier) || + !SaveObjectToPrecompiled(static_cast(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 binary(binary_length); + glGetProgramBinary(program, binary_length, nullptr, &binary_format, binary.data()); + + if (!SaveObjectToPrecompiled(static_cast(PrecompiledEntryKind::Dump)) || + !SaveObjectToPrecompiled(unique_identifier) || + !SaveObjectToPrecompiled(static_cast(binary_format)) || + !SaveObjectToPrecompiled(static_cast(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& uncompressed = precompiled_cache_virtual_file.ReadAllBytes(); + const std::vector& 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 \ No newline at end of file diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.h b/src/video_core/renderer_opengl/gl_shader_disk_cache.h new file mode 100644 index 0000000000..c74eb60cc5 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.h @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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; +using ShaderDecompiledMap = std::unordered_map; +using ShaderDumpsMap = std::unordered_map; + +/// 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 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> LoadTransferable(); + + /// Loads current game's precompiled cache. Invalidates on failure. + std::pair 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> LoadPrecompiledFile( + FileUtil::IOFile& file); + + /// Loads a decompiled cache entry from m_precompiled_cache_virtual_file. Returns empty on + /// failure. + std::optional 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 + 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 + 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 + bool SaveObjectToPrecompiled(const T& object) { + return SaveArrayToPrecompiled(&object, 1); + } + + bool SaveObjectToPrecompiled(bool object) { + const auto value = static_cast(object); + return SaveArrayToPrecompiled(&value, 1); + } + + template + 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 transferable; + + // The cache has been loaded at boot + bool tried_to_load{}; + + bool separable{}; + + u64 program_id{}; + std::string title_id; +}; + +} // namespace OpenGL \ No newline at end of file diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h index 4f798dd07b..436ce70e7d 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.h +++ b/src/video_core/renderer_opengl/gl_shader_gen.h @@ -16,6 +16,8 @@ namespace OpenGL { +enum class ProgramType : u32 { VS, GS, FS }; + enum Attributes { ATTRIBUTE_POSITION, ATTRIBUTE_COLOR, @@ -161,8 +163,11 @@ struct PicaShaderConfigCommon { * shader. */ struct PicaVSConfig : Common::HashableStruct { - explicit PicaVSConfig(const Pica::Regs& regs, Pica::Shader::ShaderSetup& setup) { - state.Init(regs.vs, setup); + explicit PicaVSConfig(const Pica::ShaderRegs& regs, Pica::Shader::ShaderSetup& setup) { + state.Init(regs, setup); + } + explicit PicaVSConfig(PicaShaderConfigCommon& conf) { + state = conf; } }; diff --git a/src/video_core/renderer_opengl/gl_shader_manager.cpp b/src/video_core/renderer_opengl/gl_shader_manager.cpp index c0172c9008..9a340fd1d5 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.cpp +++ b/src/video_core/renderer_opengl/gl_shader_manager.cpp @@ -3,13 +3,79 @@ // Refer to the license.txt file included. #include +#include #include #include #include +#include "core/core.h" +#include "video_core/renderer_opengl/gl_shader_disk_cache.h" #include "video_core/renderer_opengl/gl_shader_manager.h" 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& 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(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 GetSupportedFormats() { + std::set supported_formats; + + GLint num_formats{}; + glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &num_formats); + + std::vector formats(num_formats); + glGetIntegerv(GL_PROGRAM_BINARY_FORMATS, formats.data()); + + for (const GLint format : formats) + supported_formats.insert(static_cast(format)); + return supported_formats; +} + +static std::tuple 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, std::size_t expected_size) { const GLuint ub_index = glGetUniformBlockIndex(shader, name); @@ -121,6 +187,10 @@ public: } } + void Inject(OGLProgram&& program) { + shader_or_program = std::move(program); + } + private: boost::variant shader_or_program; }; @@ -143,13 +213,22 @@ template > Get( + const KeyConfigType& config) { auto [iter, new_shader] = shaders.emplace(config, OGLShaderStage{separable}); OGLShaderStage& cached_shader = iter->second; + std::optional result{}; 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: @@ -169,30 +248,41 @@ template > Get( + const KeyConfigType& key, const Pica::Shader::ShaderSetup& setup) { + std::optional result{}; auto map_it = shader_map.find(key); if (map_it == shader_map.end()) { auto program_opt = CodeGenerator(setup, key, separable); if (!program_opt) { shader_map[key] = nullptr; - return 0; + return {0, {}}; } std::string& program = *program_opt; auto [iter, new_shader] = shader_cache.emplace(program, OGLShaderStage{separable}); OGLShaderStage& cached_shader = iter->second; if (new_shader) { + result = program; cached_shader.Create(program.c_str(), ShaderType); } shader_map[key] = &cached_shader; - return cached_shader.GetHandle(); + return {cached_shader.GetHandle(), result}; } 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: @@ -214,7 +304,7 @@ public: explicit Impl(bool separable, bool is_amd) : is_amd(is_amd), separable(separable), programmable_vertex_shaders(separable), trivial_vertex_shader(separable), fixed_geometry_shaders(separable), - fragment_shaders(separable) { + fragment_shaders(separable), disk_cache(separable) { if (separable) pipeline.Create(); } @@ -257,6 +347,7 @@ public: bool separable; std::unordered_map program_cache; OGLPipeline pipeline; + ShaderDiskCache disk_cache; }; ShaderProgramManager::ShaderProgramManager(bool separable, bool is_amd) @@ -264,12 +355,23 @@ ShaderProgramManager::ShaderProgramManager(bool separable, bool is_amd) ShaderProgramManager::~ShaderProgramManager() = default; -bool ShaderProgramManager::UseProgrammableVertexShader(const PicaVSConfig& config, - const Pica::Shader::ShaderSetup setup) { - GLuint handle = impl->programmable_vertex_shaders.Get(config, setup); +bool ShaderProgramManager::UseProgrammableVertexShader(const Pica::Regs& regs, + Pica::Shader::ShaderSetup& setup) { + PicaVSConfig config{regs.vs, setup}; + auto [handle, result] = impl->programmable_vertex_shaders.Get(config, setup); if (handle == 0) return false; 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; } @@ -277,25 +379,36 @@ void ShaderProgramManager::UseTrivialVertexShader() { impl->current.vs = impl->trivial_vertex_shader.Get(); } -void ShaderProgramManager::UseFixedGeometryShader(const PicaFixedGSConfig& config) { - impl->current.gs = impl->fixed_geometry_shaders.Get(config); +void ShaderProgramManager::UseFixedGeometryShader(const Pica::Regs& regs) { + PicaFixedGSConfig gs_config(regs); + auto [handle, _] = impl->fixed_geometry_shaders.Get(gs_config); + impl->current.gs = handle; } void ShaderProgramManager::UseTrivialGeometryShader() { impl->current.gs = 0; } -void ShaderProgramManager::UseFragmentShader(const PicaFSConfig& config) { - impl->current.fs = impl->fragment_shaders.Get(config); +void ShaderProgramManager::UseFragmentShader(const Pica::Regs& regs) { + 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) { if (impl->separable) { if (impl->is_amd) { - // Without this reseting, AMD sometimes freezes when one stage is changed but not for - // the others. - // On the other hand, including this reset seems to introduce memory leak in Intel - // Graphics. + // Without this reseting, AMD sometimes freezes when one stage is changed but not + // for the others. On the other hand, including this reset seems to introduce memory + // leak in Intel Graphics. glUseProgramStages( impl->pipeline.handle, 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; } } + +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 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 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& 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(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& 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 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(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 diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h index a31fbc4e65..ada9015102 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.h +++ b/src/video_core/renderer_opengl/gl_shader_manager.h @@ -6,14 +6,21 @@ #include #include +#include "video_core/rasterizer_interface.h" #include "video_core/regs_lighting.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_state.h" #include "video_core/renderer_opengl/pica_to_gl.h" +namespace Core { +class System; +} + namespace OpenGL { +class ShaderDiskCacheOpenGL; + enum class UniformBindings : GLuint { Common, VS, GS }; struct LightSrc { @@ -97,16 +104,18 @@ public: ShaderProgramManager(bool separable, bool is_amd); ~ShaderProgramManager(); - bool UseProgrammableVertexShader(const PicaVSConfig& config, - const Pica::Shader::ShaderSetup setup); + void LoadDiskCache(const std::atomic_bool& stop_loading, + const VideoCore::DiskResourceLoadCallback& callback); + + bool UseProgrammableVertexShader(const Pica::Regs& config, Pica::Shader::ShaderSetup& setup); void UseTrivialVertexShader(); - void UseFixedGeometryShader(const PicaFixedGSConfig& config); + void UseFixedGeometryShader(const Pica::Regs& regs); void UseTrivialGeometryShader(); - void UseFragmentShader(const PicaFSConfig& config); + void UseFragmentShader(const Pica::Regs& config); void ApplyTo(OpenGLState& state); diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 7b7c734831..77a3eb5dd1 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -1002,9 +1002,9 @@ static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum } /// Initialize the renderer -Core::System::ResultStatus RendererOpenGL::Init() { +VideoCore::ResultStatus RendererOpenGL::Init() { if (!gladLoadGL()) { - return Core::System::ResultStatus::ErrorVideoCore_ErrorBelowGL33; + return VideoCore::ResultStatus::ErrorBelowGL33; } 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); 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)) { - return Core::System::ResultStatus::ErrorVideoCore_ErrorBelowGL33; + return VideoCore::ResultStatus::ErrorBelowGL33; } InitOpenGLObjects(); RefreshRasterizerSetting(); - return Core::System::ResultStatus::Success; + return VideoCore::ResultStatus::Success; } /// Shutdown the renderer diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index 40c850b78b..54604a5d09 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -48,7 +48,7 @@ public: ~RendererOpenGL() override; /// Initialize the renderer - Core::System::ResultStatus Init() override; + VideoCore::ResultStatus Init() override; /// Shutdown the renderer void ShutDown() override; diff --git a/src/video_core/shader/shader.h b/src/video_core/shader/shader.h index 68b7542fe1..bb6a5fae73 100644 --- a/src/video_core/shader/shader.h +++ b/src/video_core/shader/shader.h @@ -26,6 +26,8 @@ namespace Pica::Shader { constexpr unsigned MAX_PROGRAM_CODE_LENGTH = 4096; constexpr unsigned MAX_SWIZZLE_DATA_LENGTH = 4096; +using ProgramCode = std::array; +using SwizzleData = std::array; struct AttributeBuffer { alignas(16) Common::Vec4 attr[16]; @@ -196,8 +198,8 @@ struct Uniforms { struct ShaderSetup { Uniforms uniforms; - std::array program_code; - std::array swizzle_data; + ProgramCode program_code; + SwizzleData swizzle_data; /// Data private to ShaderEngines struct EngineData { diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp index 2b2dffcd38..b7e1d4885b 100644 --- a/src/video_core/video_core.cpp +++ b/src/video_core/video_core.cpp @@ -22,6 +22,7 @@ std::atomic g_hw_renderer_enabled; std::atomic g_shader_jit_enabled; std::atomic g_hw_shader_enabled; std::atomic g_hw_shader_accurate_mul; +std::atomic g_use_disk_shader_cache; std::atomic g_renderer_bg_color_update_requested; std::atomic g_renderer_sampler_update_requested; std::atomic g_renderer_shader_update_requested; @@ -34,16 +35,16 @@ Layout::FramebufferLayout g_screenshot_framebuffer_layout; Memory::MemorySystem* g_memory; /// 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; Pica::Init(); OpenGL::GLES = Settings::values.use_gles; g_renderer = std::make_unique(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 !"); } else { LOG_DEBUG(Render, "initialized OK"); diff --git a/src/video_core/video_core.h b/src/video_core/video_core.h index fdf99add84..f11b678399 100644 --- a/src/video_core/video_core.h +++ b/src/video_core/video_core.h @@ -6,7 +6,6 @@ #include #include -#include "core/core.h" #include "core/frontend/emu_window.h" namespace Frontend { @@ -32,6 +31,7 @@ extern std::atomic g_hw_renderer_enabled; extern std::atomic g_shader_jit_enabled; extern std::atomic g_hw_shader_enabled; extern std::atomic g_hw_shader_accurate_mul; +extern std::atomic g_use_disk_shader_cache; extern std::atomic g_renderer_bg_color_update_requested; extern std::atomic g_renderer_sampler_update_requested; extern std::atomic g_renderer_shader_update_requested; @@ -43,8 +43,14 @@ extern Layout::FramebufferLayout g_screenshot_framebuffer_layout; extern Memory::MemorySystem* g_memory; +enum class ResultStatus { + Success, + ErrorGenericDrivers, + ErrorBelowGL33, +}; + /// 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 void Shutdown();