diff --git a/CMakeLists.txt b/CMakeLists.txt index 871e0ca1ab..97d888762d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -419,19 +419,6 @@ function(create_target_directory_groups target_name) endforeach() endfunction() -# Gets a UTC timstamp and sets the provided variable to it -function(get_timestamp _var) - string(TIMESTAMP timestamp UTC) - set(${_var} "${timestamp}" PARENT_SCOPE) -endfunction() - -# generate git/build information -include(GetGitRevisionDescription) -get_git_head_revision(GIT_REF_SPEC GIT_REV) -git_describe(GIT_DESC --always --long --dirty) -git_branch_name(GIT_BRANCH) -get_timestamp(BUILD_DATE) - enable_testing() add_subdirectory(externals) add_subdirectory(src) diff --git a/CMakeModules/GenerateSCMRev.cmake b/CMakeModules/GenerateSCMRev.cmake new file mode 100644 index 0000000000..78728e08b0 --- /dev/null +++ b/CMakeModules/GenerateSCMRev.cmake @@ -0,0 +1,94 @@ +# Gets a UTC timstamp and sets the provided variable to it +function(get_timestamp _var) + string(TIMESTAMP timestamp UTC) + set(${_var} "${timestamp}" PARENT_SCOPE) +endfunction() + +list(APPEND CMAKE_MODULE_PATH "${SRC_DIR}/externals/cmake-modules") +# generate git/build information +include(GetGitRevisionDescription) +get_git_head_revision(GIT_REF_SPEC GIT_REV) +git_describe(GIT_DESC --always --long --dirty) +git_branch_name(GIT_BRANCH) +get_timestamp(BUILD_DATE) + +# Generate cpp with Git revision from template +# Also if this is a CI build, add the build name (ie: Nightly, Canary) to the scm_rev file as well +set(REPO_NAME "") +set(BUILD_VERSION "0") +if (BUILD_REPOSITORY) + # regex capture the string nightly or canary into CMAKE_MATCH_1 + string(REGEX MATCH "yuzu-emu/yuzu-?(.*)" OUTVAR ${BUILD_REPOSITORY}) + if (${CMAKE_MATCH_COUNT} GREATER 0) + # capitalize the first letter of each word in the repo name. + string(REPLACE "-" ";" REPO_NAME_LIST ${CMAKE_MATCH_1}) + foreach(WORD ${REPO_NAME_LIST}) + string(SUBSTRING ${WORD} 0 1 FIRST_LETTER) + string(SUBSTRING ${WORD} 1 -1 REMAINDER) + string(TOUPPER ${FIRST_LETTER} FIRST_LETTER) + set(REPO_NAME "${REPO_NAME}${FIRST_LETTER}${REMAINDER}") + endforeach() + if (BUILD_TAG) + string(REGEX MATCH "${CMAKE_MATCH_1}-([0-9]+)" OUTVAR ${BUILD_TAG}) + if (${CMAKE_MATCH_COUNT} GREATER 0) + set(BUILD_VERSION ${CMAKE_MATCH_1}) + endif() + if (BUILD_VERSION) + # This leaves a trailing space on the last word, but we actually want that + # because of how it's styled in the title bar. + set(BUILD_FULLNAME "${REPO_NAME} ${BUILD_VERSION} ") + else() + set(BUILD_FULLNAME "") + endif() + endif() + endif() +endif() + +# The variable SRC_DIR must be passed into the script (since it uses the current build directory for all values of CMAKE_*_DIR) +set(VIDEO_CORE "${SRC_DIR}/src/video_core") +set(HASH_FILES + "${VIDEO_CORE}/renderer_opengl/gl_shader_cache.cpp" + "${VIDEO_CORE}/renderer_opengl/gl_shader_cache.h" + "${VIDEO_CORE}/renderer_opengl/gl_shader_decompiler.cpp" + "${VIDEO_CORE}/renderer_opengl/gl_shader_decompiler.h" + "${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.cpp" + "${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.h" + "${VIDEO_CORE}/renderer_opengl/gl_shader_gen.cpp" + "${VIDEO_CORE}/renderer_opengl/gl_shader_gen.h" + "${VIDEO_CORE}/shader/decode/arithmetic.cpp" + "${VIDEO_CORE}/shader/decode/arithmetic_half.cpp" + "${VIDEO_CORE}/shader/decode/arithmetic_half_immediate.cpp" + "${VIDEO_CORE}/shader/decode/arithmetic_immediate.cpp" + "${VIDEO_CORE}/shader/decode/arithmetic_integer.cpp" + "${VIDEO_CORE}/shader/decode/arithmetic_integer_immediate.cpp" + "${VIDEO_CORE}/shader/decode/bfe.cpp" + "${VIDEO_CORE}/shader/decode/bfi.cpp" + "${VIDEO_CORE}/shader/decode/conversion.cpp" + "${VIDEO_CORE}/shader/decode/ffma.cpp" + "${VIDEO_CORE}/shader/decode/float_set.cpp" + "${VIDEO_CORE}/shader/decode/float_set_predicate.cpp" + "${VIDEO_CORE}/shader/decode/half_set.cpp" + "${VIDEO_CORE}/shader/decode/half_set_predicate.cpp" + "${VIDEO_CORE}/shader/decode/hfma2.cpp" + "${VIDEO_CORE}/shader/decode/integer_set.cpp" + "${VIDEO_CORE}/shader/decode/integer_set_predicate.cpp" + "${VIDEO_CORE}/shader/decode/memory.cpp" + "${VIDEO_CORE}/shader/decode/other.cpp" + "${VIDEO_CORE}/shader/decode/predicate_set_predicate.cpp" + "${VIDEO_CORE}/shader/decode/predicate_set_register.cpp" + "${VIDEO_CORE}/shader/decode/register_set_predicate.cpp" + "${VIDEO_CORE}/shader/decode/shift.cpp" + "${VIDEO_CORE}/shader/decode/video.cpp" + "${VIDEO_CORE}/shader/decode/xmad.cpp" + "${VIDEO_CORE}/shader/decode.cpp" + "${VIDEO_CORE}/shader/shader_ir.cpp" + "${VIDEO_CORE}/shader/shader_ir.h" + "${VIDEO_CORE}/shader/track.cpp" +) +set(COMBINED "") +foreach (F IN LISTS HASH_FILES) + file(READ ${F} TMP) + set(COMBINED "${COMBINED}${TMP}") +endforeach() +string(MD5 SHADER_CACHE_VERSION "${COMBINED}") +configure_file("${SRC_DIR}/src/common/scm_rev.cpp.in" "scm_rev.cpp" @ONLY) diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 845626fc5b..bdd885273c 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -1,42 +1,69 @@ -# Generate cpp with Git revision from template -# Also if this is a CI build, add the build name (ie: Nightly, Canary) to the scm_rev file as well -set(REPO_NAME "") -set(BUILD_VERSION "0") -if ($ENV{CI}) - if ($ENV{TRAVIS}) +# Add a custom command to generate a new shader_cache_version hash when any of the following files change +# NOTE: This is an approximation of what files affect shader generation, its possible something else +# could affect the result, but much more unlikely than the following files. Keeping a list of files +# like this allows for much better caching since it doesn't force the user to recompile binary shaders every update +set(VIDEO_CORE "${CMAKE_SOURCE_DIR}/src/video_core") +if (DEFINED ENV{CI}) + if (DEFINED ENV{TRAVIS}) set(BUILD_REPOSITORY $ENV{TRAVIS_REPO_SLUG}) set(BUILD_TAG $ENV{TRAVIS_TAG}) - elseif($ENV{APPVEYOR}) + elseif(DEFINED ENV{APPVEYOR}) set(BUILD_REPOSITORY $ENV{APPVEYOR_REPO_NAME}) set(BUILD_TAG $ENV{APPVEYOR_REPO_TAG_NAME}) endif() - # regex capture the string nightly or canary into CMAKE_MATCH_1 - string(REGEX MATCH "yuzu-emu/yuzu-?(.*)" OUTVAR ${BUILD_REPOSITORY}) - if (${CMAKE_MATCH_COUNT} GREATER 0) - # capitalize the first letter of each word in the repo name. - string(REPLACE "-" ";" REPO_NAME_LIST ${CMAKE_MATCH_1}) - foreach(WORD ${REPO_NAME_LIST}) - string(SUBSTRING ${WORD} 0 1 FIRST_LETTER) - string(SUBSTRING ${WORD} 1 -1 REMAINDER) - string(TOUPPER ${FIRST_LETTER} FIRST_LETTER) - set(REPO_NAME "${REPO_NAME}${FIRST_LETTER}${REMAINDER}") - endforeach() - if (BUILD_TAG) - string(REGEX MATCH "${CMAKE_MATCH_1}-([0-9]+)" OUTVAR ${BUILD_TAG}) - if (${CMAKE_MATCH_COUNT} GREATER 0) - set(BUILD_VERSION ${CMAKE_MATCH_1}) - endif() - if (BUILD_VERSION) - # This leaves a trailing space on the last word, but we actually want that - # because of how it's styled in the title bar. - set(BUILD_FULLNAME "${REPO_NAME} ${BUILD_VERSION} ") - else() - set(BUILD_FULLNAME "") - endif() - endif() - endif() endif() -configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp" @ONLY) +add_custom_command(OUTPUT scm_rev.cpp + COMMAND ${CMAKE_COMMAND} + -DSRC_DIR="${CMAKE_SOURCE_DIR}" + -DBUILD_REPOSITORY="${BUILD_REPOSITORY}" + -DBUILD_TAG="${BUILD_TAG}" + -P "${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake" + DEPENDS + # WARNING! It was too much work to try and make a common location for this list, + # so if you need to change it, please update CMakeModules/GenerateSCMRev.cmake as well + "${VIDEO_CORE}/renderer_opengl/gl_shader_cache.cpp" + "${VIDEO_CORE}/renderer_opengl/gl_shader_cache.h" + "${VIDEO_CORE}/renderer_opengl/gl_shader_decompiler.cpp" + "${VIDEO_CORE}/renderer_opengl/gl_shader_decompiler.h" + "${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.cpp" + "${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.h" + "${VIDEO_CORE}/renderer_opengl/gl_shader_gen.cpp" + "${VIDEO_CORE}/renderer_opengl/gl_shader_gen.h" + "${VIDEO_CORE}/shader/decode/arithmetic.cpp" + "${VIDEO_CORE}/shader/decode/arithmetic_half.cpp" + "${VIDEO_CORE}/shader/decode/arithmetic_half_immediate.cpp" + "${VIDEO_CORE}/shader/decode/arithmetic_immediate.cpp" + "${VIDEO_CORE}/shader/decode/arithmetic_integer.cpp" + "${VIDEO_CORE}/shader/decode/arithmetic_integer_immediate.cpp" + "${VIDEO_CORE}/shader/decode/bfe.cpp" + "${VIDEO_CORE}/shader/decode/bfi.cpp" + "${VIDEO_CORE}/shader/decode/conversion.cpp" + "${VIDEO_CORE}/shader/decode/ffma.cpp" + "${VIDEO_CORE}/shader/decode/float_set.cpp" + "${VIDEO_CORE}/shader/decode/float_set_predicate.cpp" + "${VIDEO_CORE}/shader/decode/half_set.cpp" + "${VIDEO_CORE}/shader/decode/half_set_predicate.cpp" + "${VIDEO_CORE}/shader/decode/hfma2.cpp" + "${VIDEO_CORE}/shader/decode/integer_set.cpp" + "${VIDEO_CORE}/shader/decode/integer_set_predicate.cpp" + "${VIDEO_CORE}/shader/decode/memory.cpp" + "${VIDEO_CORE}/shader/decode/other.cpp" + "${VIDEO_CORE}/shader/decode/predicate_set_predicate.cpp" + "${VIDEO_CORE}/shader/decode/predicate_set_register.cpp" + "${VIDEO_CORE}/shader/decode/register_set_predicate.cpp" + "${VIDEO_CORE}/shader/decode/shift.cpp" + "${VIDEO_CORE}/shader/decode/video.cpp" + "${VIDEO_CORE}/shader/decode/xmad.cpp" + "${VIDEO_CORE}/shader/decode.cpp" + "${VIDEO_CORE}/shader/shader_ir.cpp" + "${VIDEO_CORE}/shader/shader_ir.h" + "${VIDEO_CORE}/shader/track.cpp" + # and also check that the scm_rev files haven't changed + "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" + "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.h" + # technically we should regenerate if the git version changed, but its not worth the effort imo + "${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake" +) add_library(common STATIC alignment.h diff --git a/src/common/common_paths.h b/src/common/common_paths.h index 4f88de768c..076752d3b6 100644 --- a/src/common/common_paths.h +++ b/src/common/common_paths.h @@ -35,6 +35,7 @@ #define KEYS_DIR "keys" #define LOAD_DIR "load" #define DUMP_DIR "dump" +#define SHADER_DIR "shader" #define LOG_DIR "log" // Filenames diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index b52492da69..aecb66c32d 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -710,6 +710,7 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) { paths.emplace(UserPath::NANDDir, user_path + NAND_DIR DIR_SEP); paths.emplace(UserPath::LoadDir, user_path + LOAD_DIR DIR_SEP); paths.emplace(UserPath::DumpDir, user_path + DUMP_DIR DIR_SEP); + paths.emplace(UserPath::ShaderDir, user_path + SHADER_DIR DIR_SEP); paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP); paths.emplace(UserPath::KeysDir, user_path + KEYS_DIR DIR_SEP); // TODO: Put the logs in a better location for each OS diff --git a/src/common/file_util.h b/src/common/file_util.h index 571503d2ae..38cc7f059c 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -31,6 +31,7 @@ enum class UserPath { SDMCDir, LoadDir, DumpDir, + ShaderDir, SysDataDir, UserDir, }; diff --git a/src/common/scm_rev.cpp.in b/src/common/scm_rev.cpp.in index 2b17277696..d69038f650 100644 --- a/src/common/scm_rev.cpp.in +++ b/src/common/scm_rev.cpp.in @@ -11,6 +11,7 @@ #define BUILD_DATE "@BUILD_DATE@" #define BUILD_FULLNAME "@BUILD_FULLNAME@" #define BUILD_VERSION "@BUILD_VERSION@" +#define SHADER_CACHE_VERSION "@SHADER_CACHE_VERSION@" namespace Common { @@ -21,6 +22,7 @@ const char g_build_name[] = BUILD_NAME; const char g_build_date[] = BUILD_DATE; const char g_build_fullname[] = BUILD_FULLNAME; const char g_build_version[] = BUILD_VERSION; +const char g_shader_cache_version[] = SHADER_CACHE_VERSION; } // namespace diff --git a/src/common/scm_rev.h b/src/common/scm_rev.h index af9a9daed0..666bf03676 100644 --- a/src/common/scm_rev.h +++ b/src/common/scm_rev.h @@ -13,5 +13,6 @@ extern const char g_build_name[]; extern const char g_build_date[]; extern const char g_build_fullname[]; extern const char g_build_version[]; +extern const char g_shader_cache_version[]; } // namespace Common diff --git a/src/core/core.cpp b/src/core/core.cpp index 572814e4b5..1dd576c266 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -123,7 +123,7 @@ struct System::Impl { Service::Init(service_manager, *virtual_filesystem); GDBStub::Init(); - renderer = VideoCore::CreateRenderer(emu_window); + renderer = VideoCore::CreateRenderer(emu_window, system); if (!renderer->Init()) { return ResultStatus::ErrorVideoCore; } @@ -175,6 +175,7 @@ struct System::Impl { return static_cast(static_cast(ResultStatus::ErrorLoader) + static_cast(load_result)); } + status = ResultStatus::Success; return status; } diff --git a/src/core/settings.h b/src/core/settings.h index c97387fc78..7e76e0466f 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -391,6 +391,7 @@ struct Values { float resolution_factor; bool use_frame_limit; u16 frame_limit; + bool use_disk_shader_cache; bool use_accurate_gpu_emulation; float bg_red; diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index 09ed74d787..58dfcc4dfc 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -158,6 +158,8 @@ TelemetrySession::TelemetrySession() { AddField(Telemetry::FieldType::UserConfig, "Renderer_UseFrameLimit", Settings::values.use_frame_limit); AddField(Telemetry::FieldType::UserConfig, "Renderer_FrameLimit", Settings::values.frame_limit); + AddField(Telemetry::FieldType::UserConfig, "Renderer_UseDiskShaderCache", + Settings::values.use_disk_shader_cache); AddField(Telemetry::FieldType::UserConfig, "Renderer_UseAccurateGpuEmulation", Settings::values.use_accurate_gpu_emulation); AddField(Telemetry::FieldType::UserConfig, "System_UseDockedMode", diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 6113e17ff1..33e507e69e 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -44,6 +44,8 @@ add_library(video_core STATIC renderer_opengl/gl_shader_cache.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 @@ -102,4 +104,4 @@ add_library(video_core STATIC create_target_directory_groups(video_core) target_link_libraries(video_core PUBLIC common core) -target_link_libraries(video_core PRIVATE glad) +target_link_libraries(video_core PRIVATE glad lz4_static) diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h index 4c08bb1485..77da135a0d 100644 --- a/src/video_core/rasterizer_interface.h +++ b/src/video_core/rasterizer_interface.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include "common/common_types.h" #include "video_core/engines/fermi_2d.h" @@ -61,5 +62,9 @@ public: /// Increase/decrease the number of object in pages touching the specified region virtual void UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta) {} + + /// Initialize disk cached resources for the game being emulated + virtual void LoadDiskResources(const std::atomic_bool& stop_loading = false, + const DiskResourceLoadCallback& callback = {}) {} }; } // namespace VideoCore diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index c806b7da7e..974ca6a20e 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -22,6 +22,7 @@ #include "core/settings.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/renderer_opengl/gl_rasterizer.h" +#include "video_core/renderer_opengl/gl_shader_cache.h" #include "video_core/renderer_opengl/gl_shader_gen.h" #include "video_core/renderer_opengl/maxwell_to_gl.h" #include "video_core/renderer_opengl/renderer_opengl.h" @@ -99,8 +100,9 @@ struct FramebufferCacheKey { } }; -RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo& info) - : res_cache{*this}, shader_cache{*this}, emu_window{window}, screen_info{info}, +RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, Core::System& system, + ScreenInfo& info) + : res_cache{*this}, shader_cache{*this, system}, emu_window{window}, screen_info{info}, buffer_cache(*this, STREAM_BUFFER_SIZE), global_cache{*this} { // Create sampler objects for (std::size_t i = 0; i < texture_samplers.size(); ++i) { @@ -447,7 +449,7 @@ static constexpr auto RangeFromInterval(Map& map, const Interval& interval) { return boost::make_iterator_range(map.equal_range(interval)); } -void RasterizerOpenGL::UpdatePagesCachedCount(VAddr addr, u64 size, int delta) { +void RasterizerOpenGL::UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta) { const u64 page_start{addr >> Memory::PAGE_BITS}; const u64 page_end{(addr + size + Memory::PAGE_SIZE - 1) >> Memory::PAGE_BITS}; @@ -477,6 +479,11 @@ void RasterizerOpenGL::UpdatePagesCachedCount(VAddr addr, u64 size, int delta) { cached_pages.add({pages_interval, delta}); } +void RasterizerOpenGL::LoadDiskResources(const std::atomic_bool& stop_loading, + const VideoCore::DiskResourceLoadCallback& callback) { + shader_cache.LoadDiskCache(stop_loading, callback); +} + std::pair RasterizerOpenGL::ConfigureFramebuffers( OpenGLState& current_state, bool using_color_fb, bool using_depth_fb, bool preserve_contents, std::optional single_color_target) { @@ -1004,22 +1011,20 @@ void RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& s for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { const auto& entry = entries[bindpoint]; + const auto texture = maxwell3d.GetStageTexture(stage, entry.GetOffset()); const u32 current_bindpoint = base_bindings.sampler + bindpoint; - auto& unit = state.texture_units[current_bindpoint]; - - const auto texture = maxwell3d.GetStageTexture(entry.GetStage(), entry.GetOffset()); texture_samplers[current_bindpoint].SyncWithConfig(texture.tsc); Surface surface = res_cache.GetTextureSurface(texture, entry); if (surface != nullptr) { - unit.texture = + state.texture_units[current_bindpoint].texture = entry.IsArray() ? surface->TextureLayer().handle : surface->Texture().handle; surface->UpdateSwizzle(texture.tic.x_source, texture.tic.y_source, texture.tic.z_source, texture.tic.w_source); } else { // Can occur when texture addr is null or its memory is unmapped/invalid - unit.texture = 0; + state.texture_units[current_bindpoint].texture = 0; } } } diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 7f2bf0f8bc..f3b607f4d6 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include #include @@ -33,6 +34,10 @@ #include "video_core/renderer_opengl/gl_state.h" #include "video_core/renderer_opengl/gl_stream_buffer.h" +namespace Core { +class System; +} + namespace Core::Frontend { class EmuWindow; } @@ -45,7 +50,8 @@ struct FramebufferCacheKey; class RasterizerOpenGL : public VideoCore::RasterizerInterface { public: - explicit RasterizerOpenGL(Core::Frontend::EmuWindow& renderer, ScreenInfo& info); + explicit RasterizerOpenGL(Core::Frontend::EmuWindow& window, Core::System& system, + ScreenInfo& info); ~RasterizerOpenGL() override; void DrawArrays() override; @@ -60,6 +66,8 @@ public: u32 pixel_stride) override; bool AccelerateDrawBatch(bool is_indexed) override; void UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta) override; + void LoadDiskResources(const std::atomic_bool& stop_loading, + const VideoCore::DiskResourceLoadCallback& callback) override; /// Maximum supported size that a constbuffer can have in bytes. static constexpr std::size_t MaxConstbufferSize = 0x10000; diff --git a/src/video_core/renderer_opengl/gl_resource_manager.cpp b/src/video_core/renderer_opengl/gl_resource_manager.cpp index 4170cbd3c0..bfe666a73f 100644 --- a/src/video_core/renderer_opengl/gl_resource_manager.cpp +++ b/src/video_core/renderer_opengl/gl_resource_manager.cpp @@ -71,7 +71,8 @@ void OGLShader::Release() { } void OGLProgram::CreateFromSource(const char* vert_shader, const char* geo_shader, - const char* frag_shader, bool separable_program) { + const char* frag_shader, bool separable_program, + bool hint_retrievable) { OGLShader vert, geo, frag; if (vert_shader) vert.Create(vert_shader, GL_VERTEX_SHADER); @@ -81,7 +82,7 @@ void OGLProgram::CreateFromSource(const char* vert_shader, const char* geo_shade frag.Create(frag_shader, GL_FRAGMENT_SHADER); MICROPROFILE_SCOPE(OpenGL_ResourceCreation); - Create(separable_program, vert.handle, geo.handle, frag.handle); + Create(separable_program, hint_retrievable, vert.handle, geo.handle, frag.handle); } void OGLProgram::Release() { diff --git a/src/video_core/renderer_opengl/gl_resource_manager.h b/src/video_core/renderer_opengl/gl_resource_manager.h index df76cbc4b0..fbb93ee499 100644 --- a/src/video_core/renderer_opengl/gl_resource_manager.h +++ b/src/video_core/renderer_opengl/gl_resource_manager.h @@ -101,15 +101,15 @@ public: } template - void Create(bool separable_program, T... shaders) { + void Create(bool separable_program, bool hint_retrievable, T... shaders) { if (handle != 0) return; - handle = GLShader::LoadProgram(separable_program, shaders...); + handle = GLShader::LoadProgram(separable_program, hint_retrievable, shaders...); } /// Creates a new internal OpenGL resource and stores the handle void CreateFromSource(const char* vert_shader, const char* geo_shader, const char* frag_shader, - bool separable_program = false); + bool separable_program = false, bool hint_retrievable = false); /// Deletes the internal OpenGL resource void Release(); diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index 6174f7074b..4883e4f625 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -11,6 +11,7 @@ #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/gl_shader_cache.h" #include "video_core/renderer_opengl/gl_shader_decompiler.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/utils.h" #include "video_core/shader/shader_ir.h" @@ -19,8 +20,19 @@ namespace OpenGL { using VideoCommon::Shader::ProgramCode; +// One UBO is always reserved for emulation values +constexpr u32 RESERVED_UBOS = 1; + +struct UnspecializedShader { + std::string code; + GLShader::ShaderEntries entries; + Maxwell::ShaderProgram program_type; +}; + +namespace { + /// Gets the address for the specified shader stage program -static VAddr GetShaderAddress(Maxwell::ShaderProgram program) { +VAddr GetShaderAddress(Maxwell::ShaderProgram program) { const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); const auto& shader_config = gpu.regs.shader_config[static_cast(program)]; const auto address = gpu.memory_manager.GpuToCpuAddress(gpu.regs.code_address.CodeAddress() + @@ -30,7 +42,7 @@ static VAddr GetShaderAddress(Maxwell::ShaderProgram program) { } /// Gets the shader program code from memory for the specified address -static ProgramCode GetShaderCode(VAddr addr) { +ProgramCode GetShaderCode(VAddr addr) { ProgramCode program_code(VideoCommon::Shader::MAX_PROGRAM_LENGTH); Memory::ReadBlock(addr, program_code.data(), program_code.size() * sizeof(u64)); return program_code; @@ -51,38 +63,196 @@ constexpr GLenum GetShaderType(Maxwell::ShaderProgram program_type) { } } -CachedShader::CachedShader(VAddr addr, Maxwell::ShaderProgram program_type) - : addr{addr}, program_type{program_type}, setup{GetShaderCode(addr)} { +/// Gets if the current instruction offset is a scheduler instruction +constexpr bool IsSchedInstruction(std::size_t offset, std::size_t main_offset) { + // Sched instructions appear once every 4 instructions. + constexpr std::size_t SchedPeriod = 4; + const std::size_t absolute_offset = offset - main_offset; + return (absolute_offset % SchedPeriod) == 0; +} - GLShader::ProgramResult program_result; +/// Describes primitive behavior on geometry shaders +constexpr std::tuple GetPrimitiveDescription(GLenum primitive_mode) { + switch (primitive_mode) { + case GL_POINTS: + return {"points", "Points", 1}; + case GL_LINES: + case GL_LINE_STRIP: + return {"lines", "Lines", 2}; + case GL_LINES_ADJACENCY: + case GL_LINE_STRIP_ADJACENCY: + return {"lines_adjacency", "LinesAdj", 4}; + case GL_TRIANGLES: + case GL_TRIANGLE_STRIP: + case GL_TRIANGLE_FAN: + return {"triangles", "Triangles", 3}; + case GL_TRIANGLES_ADJACENCY: + case GL_TRIANGLE_STRIP_ADJACENCY: + return {"triangles_adjacency", "TrianglesAdj", 6}; + default: + return {"points", "Invalid", 1}; + } +} - switch (program_type) { - case Maxwell::ShaderProgram::VertexA: +/// Calculates the size of a program stream +std::size_t CalculateProgramSize(const GLShader::ProgramCode& program) { + constexpr std::size_t start_offset = 10; + std::size_t offset = start_offset; + std::size_t size = start_offset * sizeof(u64); + while (offset < program.size()) { + const u64 instruction = program[offset]; + if (!IsSchedInstruction(offset, start_offset)) { + if (instruction == 0 || (instruction >> 52) == 0x50b) { + // End on Maxwell's "nop" instruction + break; + } + } + size += sizeof(u64); + offset++; + } + // The last instruction is included in the program size + return std::min(size + sizeof(u64), program.size() * sizeof(u64)); +} + +/// Hashes one (or two) program streams +u64 GetUniqueIdentifier(Maxwell::ShaderProgram program_type, const ProgramCode& code, + const ProgramCode& code_b) { + u64 unique_identifier = + Common::CityHash64(reinterpret_cast(code.data()), CalculateProgramSize(code)); + if (program_type != Maxwell::ShaderProgram::VertexA) { + return unique_identifier; + } + // VertexA programs include two programs + + std::size_t seed = 0; + boost::hash_combine(seed, unique_identifier); + + const u64 identifier_b = Common::CityHash64(reinterpret_cast(code_b.data()), + CalculateProgramSize(code_b)); + boost::hash_combine(seed, identifier_b); + return static_cast(seed); +} + +/// Creates an unspecialized program from code streams +GLShader::ProgramResult CreateProgram(Maxwell::ShaderProgram program_type, ProgramCode program_code, + ProgramCode program_code_b) { + GLShader::ShaderSetup setup(program_code); + if (program_type == Maxwell::ShaderProgram::VertexA) { // VertexB is always enabled, so when VertexA is enabled, we have two vertex shaders. // Conventional HW does not support this, so we combine VertexA and VertexB into one // stage here. - setup.SetProgramB(GetShaderCode(GetShaderAddress(Maxwell::ShaderProgram::VertexB))); + setup.SetProgramB(program_code_b); + } + setup.program.unique_identifier = + GetUniqueIdentifier(program_type, program_code, program_code_b); + + switch (program_type) { + case Maxwell::ShaderProgram::VertexA: case Maxwell::ShaderProgram::VertexB: - CalculateProperties(); - program_result = GLShader::GenerateVertexShader(setup); - break; + return GLShader::GenerateVertexShader(setup); case Maxwell::ShaderProgram::Geometry: - CalculateProperties(); - program_result = GLShader::GenerateGeometryShader(setup); - break; + return GLShader::GenerateGeometryShader(setup); case Maxwell::ShaderProgram::Fragment: - CalculateProperties(); - program_result = GLShader::GenerateFragmentShader(setup); - break; + return GLShader::GenerateFragmentShader(setup); default: LOG_CRITICAL(HW_GPU, "Unimplemented program_type={}", static_cast(program_type)); UNREACHABLE(); + return {}; + } +} + +CachedProgram SpecializeShader(const std::string& code, const GLShader::ShaderEntries& entries, + Maxwell::ShaderProgram program_type, BaseBindings base_bindings, + GLenum primitive_mode, bool hint_retrievable = false) { + std::string source = "#version 430 core\n"; + source += fmt::format("#define EMULATION_UBO_BINDING {}\n", base_bindings.cbuf++); + + for (const auto& cbuf : entries.const_buffers) { + source += + fmt::format("#define CBUF_BINDING_{} {}\n", cbuf.GetIndex(), base_bindings.cbuf++); + } + for (const auto& gmem : entries.global_memory_entries) { + source += fmt::format("#define GMEM_BINDING_{}_{} {}\n", gmem.GetCbufIndex(), + gmem.GetCbufOffset(), base_bindings.gmem++); + } + for (const auto& sampler : entries.samplers) { + source += fmt::format("#define SAMPLER_BINDING_{} {}\n", sampler.GetIndex(), + base_bindings.sampler++); + } + + if (program_type == Maxwell::ShaderProgram::Geometry) { + const auto [glsl_topology, debug_name, max_vertices] = + GetPrimitiveDescription(primitive_mode); + + source += "layout (" + std::string(glsl_topology) + ") in;\n"; + source += "#define MAX_VERTEX_INPUT " + std::to_string(max_vertices) + '\n'; + } + + source += code; + + OGLShader shader; + shader.Create(source.c_str(), GetShaderType(program_type)); + + auto program = std::make_shared(); + program->Create(true, hint_retrievable, shader.handle); + return program; +} + +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; +} + +} // namespace + +CachedShader::CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderProgram program_type, + ShaderDiskCacheOpenGL& disk_cache, + const PrecompiledPrograms& precompiled_programs, + ProgramCode&& program_code, ProgramCode&& program_code_b) + : addr{addr}, unique_identifier{unique_identifier}, program_type{program_type}, + disk_cache{disk_cache}, precompiled_programs{precompiled_programs} { + + const std::size_t code_size = CalculateProgramSize(program_code); + const std::size_t code_size_b = + program_code_b.empty() ? 0 : CalculateProgramSize(program_code_b); + + GLShader::ProgramResult program_result = + CreateProgram(program_type, program_code, program_code_b); + if (program_result.first.empty()) { + // TODO(Rodrigo): Unimplemented shader stages hit here, avoid using these for now return; } code = program_result.first; entries = program_result.second; shader_length = entries.shader_length; + + const ShaderDiskCacheRaw raw(unique_identifier, program_type, + static_cast(code_size / sizeof(u64)), + static_cast(code_size_b / sizeof(u64)), + std::move(program_code), std::move(program_code_b)); + disk_cache.SaveRaw(raw); +} + +CachedShader::CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderProgram program_type, + ShaderDiskCacheOpenGL& disk_cache, + const PrecompiledPrograms& precompiled_programs, + GLShader::ProgramResult result) + : addr{addr}, unique_identifier{unique_identifier}, program_type{program_type}, + disk_cache{disk_cache}, precompiled_programs{precompiled_programs} { + + code = std::move(result.first); + entries = result.second; + shader_length = entries.shader_length; } std::tuple CachedShader::GetProgramHandle(GLenum primitive_mode, @@ -94,136 +264,222 @@ std::tuple CachedShader::GetProgramHandle(GLenum primitive const auto [entry, is_cache_miss] = programs.try_emplace(base_bindings); auto& program = entry->second; if (is_cache_miss) { - std::string source = AllocateBindings(base_bindings); - source += code; + program = TryLoadProgram(primitive_mode, base_bindings); + if (!program) { + program = + SpecializeShader(code, entries, program_type, base_bindings, primitive_mode); + disk_cache.SaveUsage(GetUsage(primitive_mode, base_bindings)); + } - OGLShader shader; - shader.Create(source.c_str(), GetShaderType(program_type)); - program.Create(true, shader.handle); - LabelGLObject(GL_PROGRAM, program.handle, addr); + LabelGLObject(GL_PROGRAM, program->handle, addr); } - handle = program.handle; + handle = program->handle; } - // Add const buffer and samplers offset reserved by this shader. One UBO binding is reserved for - // emulation values - base_bindings.cbuf += static_cast(entries.const_buffers.size()) + 1; + base_bindings.cbuf += static_cast(entries.const_buffers.size()) + RESERVED_UBOS; base_bindings.gmem += static_cast(entries.global_memory_entries.size()); base_bindings.sampler += static_cast(entries.samplers.size()); return {handle, base_bindings}; } -std::string CachedShader::AllocateBindings(BaseBindings base_bindings) { - std::string code = "#version 430 core\n"; - code += fmt::format("#define EMULATION_UBO_BINDING {}\n", base_bindings.cbuf++); - - for (const auto& cbuf : entries.const_buffers) { - code += fmt::format("#define CBUF_BINDING_{} {}\n", cbuf.GetIndex(), base_bindings.cbuf++); - } - - for (const auto& gmem : entries.global_memory_entries) { - code += fmt::format("#define GMEM_BINDING_{}_{} {}\n", gmem.GetCbufIndex(), - gmem.GetCbufOffset(), base_bindings.gmem++); - } - - for (const auto& sampler : entries.samplers) { - code += fmt::format("#define SAMPLER_BINDING_{} {}\n", sampler.GetIndex(), - base_bindings.sampler++); - } - - return code; -} - GLuint CachedShader::GetGeometryShader(GLenum primitive_mode, BaseBindings base_bindings) { const auto [entry, is_cache_miss] = geometry_programs.try_emplace(base_bindings); auto& programs = entry->second; switch (primitive_mode) { case GL_POINTS: - return LazyGeometryProgram(programs.points, base_bindings, "points", 1, "ShaderPoints"); + return LazyGeometryProgram(programs.points, base_bindings, primitive_mode); case GL_LINES: case GL_LINE_STRIP: - return LazyGeometryProgram(programs.lines, base_bindings, "lines", 2, "ShaderLines"); + return LazyGeometryProgram(programs.lines, base_bindings, primitive_mode); case GL_LINES_ADJACENCY: case GL_LINE_STRIP_ADJACENCY: - return LazyGeometryProgram(programs.lines_adjacency, base_bindings, "lines_adjacency", 4, - "ShaderLinesAdjacency"); + return LazyGeometryProgram(programs.lines_adjacency, base_bindings, primitive_mode); case GL_TRIANGLES: case GL_TRIANGLE_STRIP: case GL_TRIANGLE_FAN: - return LazyGeometryProgram(programs.triangles, base_bindings, "triangles", 3, - "ShaderTriangles"); + return LazyGeometryProgram(programs.triangles, base_bindings, primitive_mode); case GL_TRIANGLES_ADJACENCY: case GL_TRIANGLE_STRIP_ADJACENCY: - return LazyGeometryProgram(programs.triangles_adjacency, base_bindings, - "triangles_adjacency", 6, "ShaderTrianglesAdjacency"); + return LazyGeometryProgram(programs.triangles_adjacency, base_bindings, primitive_mode); default: UNREACHABLE_MSG("Unknown primitive mode."); - return LazyGeometryProgram(programs.points, base_bindings, "points", 1, "ShaderPoints"); + return LazyGeometryProgram(programs.points, base_bindings, primitive_mode); } } -GLuint CachedShader::LazyGeometryProgram(OGLProgram& target_program, BaseBindings base_bindings, - const std::string& glsl_topology, u32 max_vertices, - const std::string& debug_name) { - if (target_program.handle != 0) { - return target_program.handle; +GLuint CachedShader::LazyGeometryProgram(CachedProgram& target_program, BaseBindings base_bindings, + GLenum primitive_mode) { + if (target_program) { + return target_program->handle; + } + const auto [glsl_name, debug_name, vertices] = GetPrimitiveDescription(primitive_mode); + target_program = TryLoadProgram(primitive_mode, base_bindings); + if (!target_program) { + target_program = + SpecializeShader(code, entries, program_type, base_bindings, primitive_mode); + disk_cache.SaveUsage(GetUsage(primitive_mode, base_bindings)); } - std::string source = AllocateBindings(base_bindings); - source += "layout (" + glsl_topology + ") in;\n"; - source += "#define MAX_VERTEX_INPUT " + std::to_string(max_vertices) + '\n'; - source += code; - OGLShader shader; - shader.Create(source.c_str(), GL_GEOMETRY_SHADER); - target_program.Create(true, shader.handle); - LabelGLObject(GL_PROGRAM, target_program.handle, addr, debug_name); - return target_program.handle; + LabelGLObject(GL_PROGRAM, target_program->handle, addr, debug_name); + + return target_program->handle; }; -static bool IsSchedInstruction(std::size_t offset, std::size_t main_offset) { - // sched instructions appear once every 4 instructions. - static constexpr std::size_t SchedPeriod = 4; - const std::size_t absolute_offset = offset - main_offset; - return (absolute_offset % SchedPeriod) == 0; +CachedProgram CachedShader::TryLoadProgram(GLenum primitive_mode, + BaseBindings base_bindings) const { + const auto found = precompiled_programs.find(GetUsage(primitive_mode, base_bindings)); + if (found == precompiled_programs.end()) { + return {}; + } + return found->second; } -static std::size_t CalculateProgramSize(const GLShader::ProgramCode& program) { - constexpr std::size_t start_offset = 10; - std::size_t offset = start_offset; - std::size_t size = start_offset * sizeof(u64); - while (offset < program.size()) { - const u64 inst = program[offset]; - if (!IsSchedInstruction(offset, start_offset)) { - if (inst == 0 || (inst >> 52) == 0x50b) { - break; +ShaderDiskCacheUsage CachedShader::GetUsage(GLenum primitive_mode, + BaseBindings base_bindings) const { + return {unique_identifier, base_bindings, primitive_mode}; +} + +ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer, Core::System& system) + : RasterizerCache{rasterizer}, disk_cache{system} {} + +void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading, + const VideoCore::DiskResourceLoadCallback& callback) { + const auto transferable = disk_cache.LoadTransferable(); + if (!transferable) { + return; + } + const auto [raws, usages] = *transferable; + + auto [decompiled, dumps] = disk_cache.LoadPrecompiled(); + + const auto supported_formats{GetSupportedFormats()}; + const auto unspecialized{ + GenerateUnspecializedShaders(stop_loading, callback, raws, decompiled)}; + if (stop_loading) + return; + + // Build shaders + if (callback) + callback(VideoCore::LoadCallbackStage::Build, 0, usages.size()); + for (std::size_t i = 0; i < usages.size(); ++i) { + if (stop_loading) + return; + + const auto& usage{usages[i]}; + LOG_INFO(Render_OpenGL, "Building shader {:016x} ({} of {})", usage.unique_identifier, + i + 1, usages.size()); + + const auto& unspec{unspecialized.at(usage.unique_identifier)}; + const auto dump_it = dumps.find(usage); + + CachedProgram shader; + if (dump_it != dumps.end()) { + // If the shader is dumped, attempt to load it with + shader = GeneratePrecompiledProgram(dump_it->second, supported_formats); + if (!shader) { + // Invalidate the precompiled cache if a shader dumped shader was rejected + disk_cache.InvalidatePrecompiled(); + dumps.clear(); } } - size += sizeof(inst); - offset++; - } - return size; -} + if (!shader) { + shader = SpecializeShader(unspec.code, unspec.entries, unspec.program_type, + usage.bindings, usage.primitive, true); + } + precompiled_programs.insert({usage, std::move(shader)}); -void CachedShader::CalculateProperties() { - setup.program.real_size = CalculateProgramSize(setup.program.code); - setup.program.real_size_b = 0; - setup.program.unique_identifier = Common::CityHash64( - reinterpret_cast(setup.program.code.data()), setup.program.real_size); - if (program_type == Maxwell::ShaderProgram::VertexA) { - std::size_t seed = 0; - boost::hash_combine(seed, setup.program.unique_identifier); - setup.program.real_size_b = CalculateProgramSize(setup.program.code_b); - const u64 identifier_b = Common::CityHash64( - reinterpret_cast(setup.program.code_b.data()), setup.program.real_size_b); - boost::hash_combine(seed, identifier_b); - setup.program.unique_identifier = static_cast(seed); + if (callback) + callback(VideoCore::LoadCallbackStage::Build, i + 1, usages.size()); + } + + // TODO(Rodrigo): Do state tracking for transferable shaders and do a dummy draw before + // precompiling them + + for (std::size_t i = 0; i < usages.size(); ++i) { + const auto& usage{usages[i]}; + if (dumps.find(usage) == dumps.end()) { + const auto& program = precompiled_programs.at(usage); + disk_cache.SaveDump(usage, program->handle); + } } } -ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer) : RasterizerCache{rasterizer} {} +CachedProgram ShaderCacheOpenGL::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 {}; + } + + CachedProgram shader = std::make_shared(); + 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; +} + +std::unordered_map ShaderCacheOpenGL::GenerateUnspecializedShaders( + const std::atomic_bool& stop_loading, const VideoCore::DiskResourceLoadCallback& callback, + const std::vector& raws, + const std::unordered_map& decompiled) { + std::unordered_map unspecialized; + + if (callback) + callback(VideoCore::LoadCallbackStage::Decompile, 0, raws.size()); + + for (std::size_t i = 0; i < raws.size(); ++i) { + if (stop_loading) + return {}; + + const auto& raw{raws[i]}; + const u64 unique_identifier = raw.GetUniqueIdentifier(); + const u64 calculated_hash = + GetUniqueIdentifier(raw.GetProgramType(), raw.GetProgramCode(), raw.GetProgramCodeB()); + 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(); + return {}; + } + + GLShader::ProgramResult result; + if (const auto it = decompiled.find(unique_identifier); it != decompiled.end()) { + // If it's stored in the precompiled file, avoid decompiling it here + const auto& stored_decompiled{it->second}; + result = {stored_decompiled.code, stored_decompiled.entries}; + } else { + // Otherwise decompile the shader at boot and save the result to the decompiled file + result = + CreateProgram(raw.GetProgramType(), raw.GetProgramCode(), raw.GetProgramCodeB()); + disk_cache.SaveDecompiled(unique_identifier, result.first, result.second); + } + + precompiled_shaders.insert({unique_identifier, result}); + + unspecialized.insert( + {raw.GetUniqueIdentifier(), + {std::move(result.first), std::move(result.second), raw.GetProgramType()}}); + + if (callback) + callback(VideoCore::LoadCallbackStage::Decompile, i, raws.size()); + } + return unspecialized; +} Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) { if (!Core::System::GetInstance().GPU().Maxwell3D().dirty_flags.shaders) { @@ -237,7 +493,23 @@ Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) { if (!shader) { // No shader found - create a new one - shader = std::make_shared(program_addr, program); + ProgramCode program_code = GetShaderCode(program_addr); + ProgramCode program_code_b; + if (program == Maxwell::ShaderProgram::VertexA) { + program_code_b = GetShaderCode(GetShaderAddress(Maxwell::ShaderProgram::VertexB)); + } + const u64 unique_identifier = GetUniqueIdentifier(program, program_code, program_code_b); + + const auto found = precompiled_shaders.find(unique_identifier); + if (found != precompiled_shaders.end()) { + shader = + std::make_shared(program_addr, unique_identifier, program, disk_cache, + precompiled_programs, found->second); + } else { + shader = std::make_shared( + program_addr, unique_identifier, program, disk_cache, precompiled_programs, + std::move(program_code), std::move(program_code_b)); + } Register(shader); } diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h index 904d15dd08..97eed192fd 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.h +++ b/src/video_core/renderer_opengl/gl_shader_cache.h @@ -5,40 +5,49 @@ #pragma once #include -#include #include +#include #include +#include #include #include "common/assert.h" #include "common/common_types.h" #include "video_core/rasterizer_cache.h" +#include "video_core/renderer_base.h" #include "video_core/renderer_opengl/gl_resource_manager.h" #include "video_core/renderer_opengl/gl_shader_decompiler.h" +#include "video_core/renderer_opengl/gl_shader_disk_cache.h" #include "video_core/renderer_opengl/gl_shader_gen.h" +namespace Core { +class System; +} // namespace Core + namespace OpenGL { class CachedShader; class RasterizerOpenGL; +struct UnspecializedShader; using Shader = std::shared_ptr; +using CachedProgram = std::shared_ptr; using Maxwell = Tegra::Engines::Maxwell3D::Regs; - -struct BaseBindings { - u32 cbuf{}; - u32 gmem{}; - u32 sampler{}; - - bool operator<(const BaseBindings& rhs) const { - return std::tie(cbuf, gmem, sampler) < std::tie(rhs.cbuf, rhs.gmem, rhs.sampler); - } -}; +using PrecompiledPrograms = std::unordered_map; +using PrecompiledShaders = std::unordered_map; class CachedShader final : public RasterizerCacheObject { public: - CachedShader(VAddr addr, Maxwell::ShaderProgram program_type); + explicit CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderProgram program_type, + ShaderDiskCacheOpenGL& disk_cache, + const PrecompiledPrograms& precompiled_programs, + ProgramCode&& program_code, ProgramCode&& program_code_b); + + explicit CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderProgram program_type, + ShaderDiskCacheOpenGL& disk_cache, + const PrecompiledPrograms& precompiled_programs, + GLShader::ProgramResult result); VAddr GetAddr() const override { return addr; @@ -65,49 +74,67 @@ private: // declared by the hardware. Workaround this issue by generating a different shader per input // topology class. struct GeometryPrograms { - OGLProgram points; - OGLProgram lines; - OGLProgram lines_adjacency; - OGLProgram triangles; - OGLProgram triangles_adjacency; + CachedProgram points; + CachedProgram lines; + CachedProgram lines_adjacency; + CachedProgram triangles; + CachedProgram triangles_adjacency; }; - std::string AllocateBindings(BaseBindings base_bindings); - GLuint GetGeometryShader(GLenum primitive_mode, BaseBindings base_bindings); /// Generates a geometry shader or returns one that already exists. - GLuint LazyGeometryProgram(OGLProgram& target_program, BaseBindings base_bindings, - const std::string& glsl_topology, u32 max_vertices, - const std::string& debug_name); + GLuint LazyGeometryProgram(CachedProgram& target_program, BaseBindings base_bindings, + GLenum primitive_mode); - void CalculateProperties(); + CachedProgram TryLoadProgram(GLenum primitive_mode, BaseBindings base_bindings) const; + + ShaderDiskCacheUsage GetUsage(GLenum primitive_mode, BaseBindings base_bindings) const; VAddr addr{}; - std::size_t shader_length{}; + u64 unique_identifier{}; Maxwell::ShaderProgram program_type{}; - GLShader::ShaderSetup setup; + ShaderDiskCacheOpenGL& disk_cache; + const PrecompiledPrograms& precompiled_programs; + + std::size_t shader_length{}; GLShader::ShaderEntries entries; std::string code; - std::map programs; - std::map geometry_programs; + std::unordered_map programs; + std::unordered_map geometry_programs; - std::map cbuf_resource_cache; - std::map gmem_resource_cache; - std::map uniform_cache; + std::unordered_map cbuf_resource_cache; + std::unordered_map gmem_resource_cache; + std::unordered_map uniform_cache; }; class ShaderCacheOpenGL final : public RasterizerCache { public: - explicit ShaderCacheOpenGL(RasterizerOpenGL& rasterizer); + explicit ShaderCacheOpenGL(RasterizerOpenGL& rasterizer, Core::System& system); + + /// Loads disk cache for the current game + void LoadDiskCache(const std::atomic_bool& stop_loading, + const VideoCore::DiskResourceLoadCallback& callback); /// Gets the current specified shader stage program Shader GetStageProgram(Maxwell::ShaderProgram program); private: + std::unordered_map GenerateUnspecializedShaders( + const std::atomic_bool& stop_loading, const VideoCore::DiskResourceLoadCallback& callback, + const std::vector& raws, + const std::unordered_map& decompiled); + + CachedProgram GeneratePrecompiledProgram(const ShaderDiskCacheDump& dump, + const std::set& supported_formats); + std::array last_shaders; + + ShaderDiskCacheOpenGL disk_cache; + PrecompiledShaders precompiled_shaders; + PrecompiledPrograms precompiled_programs; }; } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 36035d0d21..d84caa6db6 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -193,15 +193,14 @@ public: ShaderEntries GetShaderEntries() const { ShaderEntries entries; for (const auto& cbuf : ir.GetConstantBuffers()) { - entries.const_buffers.emplace_back(cbuf.second, stage, GetConstBufferBlock(cbuf.first), + entries.const_buffers.emplace_back(cbuf.second.GetMaxOffset(), cbuf.second.IsIndirect(), cbuf.first); } for (const auto& sampler : ir.GetSamplers()) { - entries.samplers.emplace_back(sampler, stage, GetSampler(sampler)); + entries.samplers.emplace_back(sampler); } for (const auto& gmem : ir.GetGlobalMemoryBases()) { - entries.global_memory_entries.emplace_back(gmem.cbuf_index, gmem.cbuf_offset, stage, - GetGlobalMemoryBlock(gmem)); + entries.global_memory_entries.emplace_back(gmem.cbuf_index, gmem.cbuf_offset); } entries.clip_distances = ir.GetClipDistances(); entries.shader_length = ir.GetLength(); diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.h b/src/video_core/renderer_opengl/gl_shader_decompiler.h index 0856a1361b..72aca49382 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.h +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include #include @@ -18,56 +19,29 @@ class ShaderIR; namespace OpenGL::GLShader { +struct ShaderEntries; + using Maxwell = Tegra::Engines::Maxwell3D::Regs; +using ProgramResult = std::pair; +using SamplerEntry = VideoCommon::Shader::Sampler; class ConstBufferEntry : public VideoCommon::Shader::ConstBuffer { public: - explicit ConstBufferEntry(const VideoCommon::Shader::ConstBuffer& entry, - Maxwell::ShaderStage stage, const std::string& name, u32 index) - : VideoCommon::Shader::ConstBuffer{entry}, stage{stage}, name{name}, index{index} {} - - const std::string& GetName() const { - return name; - } - - Maxwell::ShaderStage GetStage() const { - return stage; - } + explicit ConstBufferEntry(u32 max_offset, bool is_indirect, u32 index) + : VideoCommon::Shader::ConstBuffer{max_offset, is_indirect}, index{index} {} u32 GetIndex() const { return index; } private: - std::string name; - Maxwell::ShaderStage stage{}; u32 index{}; }; -class SamplerEntry : public VideoCommon::Shader::Sampler { -public: - explicit SamplerEntry(const VideoCommon::Shader::Sampler& entry, Maxwell::ShaderStage stage, - const std::string& name) - : VideoCommon::Shader::Sampler{entry}, stage{stage}, name{name} {} - - const std::string& GetName() const { - return name; - } - - Maxwell::ShaderStage GetStage() const { - return stage; - } - -private: - std::string name; - Maxwell::ShaderStage stage{}; -}; - class GlobalMemoryEntry { public: - explicit GlobalMemoryEntry(u32 cbuf_index, u32 cbuf_offset, Maxwell::ShaderStage stage, - std::string name) - : cbuf_index{cbuf_index}, cbuf_offset{cbuf_offset}, stage{stage}, name{std::move(name)} {} + explicit GlobalMemoryEntry(u32 cbuf_index, u32 cbuf_offset) + : cbuf_index{cbuf_index}, cbuf_offset{cbuf_offset} {} u32 GetCbufIndex() const { return cbuf_index; @@ -77,19 +51,9 @@ public: return cbuf_offset; } - const std::string& GetName() const { - return name; - } - - Maxwell::ShaderStage GetStage() const { - return stage; - } - private: u32 cbuf_index{}; u32 cbuf_offset{}; - Maxwell::ShaderStage stage{}; - std::string name; }; struct ShaderEntries { @@ -100,8 +64,6 @@ struct ShaderEntries { std::size_t shader_length{}; }; -using ProgramResult = std::pair; - std::string GetCommonDeclarations(); ProgramResult Decompile(const VideoCommon::Shader::ShaderIR& ir, Maxwell::ShaderStage stage, 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..81882822b4 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp @@ -0,0 +1,656 @@ +// 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 "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 "core/core.h" +#include "core/hle/kernel/process.h" +#include "core/settings.h" + +#include "video_core/renderer_opengl/gl_shader_cache.h" +#include "video_core/renderer_opengl/gl_shader_disk_cache.h" + +namespace OpenGL { + +using ShaderCacheVersionHash = std::array; + +enum class TransferableEntryKind : u32 { + Raw, + Usage, +}; + +enum class PrecompiledEntryKind : u32 { + Decompiled, + Dump, +}; + +constexpr u32 NativeVersion = 1; + +// Making sure sizes doesn't change by accident +static_assert(sizeof(BaseBindings) == 12); +static_assert(sizeof(ShaderDiskCacheUsage) == 24); + +namespace { + +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; +} + +template +std::vector CompressData(const T* source, std::size_t source_size) { + if (source_size > LZ4_MAX_INPUT_SIZE) { + // Source size exceeds LZ4 maximum input size + return {}; + } + const auto source_size_int = static_cast(source_size); + const int max_compressed_size = LZ4_compressBound(source_size_int); + std::vector compressed(max_compressed_size); + const int compressed_size = LZ4_compress_default(reinterpret_cast(source), + reinterpret_cast(compressed.data()), + source_size_int, max_compressed_size); + if (compressed_size <= 0) { + // Compression failed + return {}; + } + compressed.resize(compressed_size); + return compressed; +} + +std::vector DecompressData(const std::vector& compressed, std::size_t uncompressed_size) { + std::vector uncompressed(uncompressed_size); + const int size_check = LZ4_decompress_safe(reinterpret_cast(compressed.data()), + reinterpret_cast(uncompressed.data()), + static_cast(compressed.size()), + static_cast(uncompressed.size())); + if (static_cast(uncompressed_size) != size_check) { + // Decompression failed + return {}; + } + return uncompressed; +} + +} // namespace + +ShaderDiskCacheRaw::ShaderDiskCacheRaw(u64 unique_identifier, Maxwell::ShaderProgram program_type, + u32 program_code_size, u32 program_code_size_b, + ProgramCode program_code, ProgramCode program_code_b) + : unique_identifier{unique_identifier}, program_type{program_type}, + program_code_size{program_code_size}, program_code_size_b{program_code_size_b}, + program_code{std::move(program_code)}, program_code_b{std::move(program_code_b)} {} + +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; + } + u32 program_code_size{}; + u32 program_code_size_b{}; + if (file.ReadBytes(&program_code_size, sizeof(u32)) != sizeof(u32) || + file.ReadBytes(&program_code_size_b, sizeof(u32)) != sizeof(u32)) { + return false; + } + + program_code.resize(program_code_size); + program_code_b.resize(program_code_size_b); + + if (file.ReadArray(program_code.data(), program_code_size) != program_code_size) + return false; + + if (HasProgramA() && + file.ReadArray(program_code_b.data(), program_code_size_b) != program_code_size_b) { + return false; + } + return true; +} + +bool ShaderDiskCacheRaw::Save(FileUtil::IOFile& file) const { + if (file.WriteObject(unique_identifier) != 1 || + file.WriteObject(static_cast(program_type)) != 1 || + file.WriteObject(program_code_size) != 1 || file.WriteObject(program_code_size_b) != 1) { + return false; + } + + if (file.WriteArray(program_code.data(), program_code_size) != program_code_size) + return false; + + if (HasProgramA() && + file.WriteArray(program_code_b.data(), program_code_size_b) != program_code_size_b) { + return false; + } + return true; +} + +ShaderDiskCacheOpenGL::ShaderDiskCacheOpenGL(Core::System& system) : system{system} {} + +std::optional, std::vector>> +ShaderDiskCacheOpenGL::LoadTransferable() { + // Skip games without title id + const bool has_title_id = system.CurrentProcess()->GetTitleID() != 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; + std::vector usages; + 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; + } + case TransferableEntryKind::Usage: { + ShaderDiskCacheUsage usage{}; + if (file.ReadBytes(&usage, sizeof(usage)) != sizeof(usage)) { + LOG_ERROR(Render_OpenGL, "Failed to load transferable usage entry - skipping"); + return {}; + } + usages.push_back(std::move(usage)); + break; + } + default: + LOG_ERROR(Render_OpenGL, "Unknown transferable shader cache entry kind={} - skipping", + static_cast(kind)); + return {}; + } + } + return {{raws, usages}}; +} + +std::pair, + std::unordered_map> +ShaderDiskCacheOpenGL::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::unordered_map>> +ShaderDiskCacheOpenGL::LoadPrecompiledFile(FileUtil::IOFile& file) { + ShaderCacheVersionHash file_hash{}; + if (file.ReadArray(file_hash.data(), file_hash.size()) != file_hash.size()) { + return {}; + } + if (GetShaderCacheVersionHash() != file_hash) { + LOG_INFO(Render_OpenGL, "Precompiled cache is from another version of the emulator"); + return {}; + } + + std::unordered_map decompiled; + std::unordered_map dumps; + while (file.Tell() < file.GetSize()) { + PrecompiledEntryKind kind{}; + if (file.ReadBytes(&kind, sizeof(u32)) != sizeof(u32)) { + return {}; + } + + switch (kind) { + case PrecompiledEntryKind::Decompiled: { + u64 unique_identifier{}; + if (file.ReadBytes(&unique_identifier, sizeof(u64)) != sizeof(u64)) + return {}; + + const auto entry = LoadDecompiledEntry(file); + if (!entry) + return {}; + decompiled.insert({unique_identifier, std::move(*entry)}); + break; + } + case PrecompiledEntryKind::Dump: { + ShaderDiskCacheUsage usage; + if (file.ReadBytes(&usage, sizeof(usage)) != sizeof(usage)) + return {}; + + ShaderDiskCacheDump dump; + if (file.ReadBytes(&dump.binary_format, sizeof(u32)) != sizeof(u32)) + return {}; + + u32 binary_length{}; + u32 compressed_size{}; + if (file.ReadBytes(&binary_length, sizeof(u32)) != sizeof(u32) || + file.ReadBytes(&compressed_size, sizeof(u32)) != sizeof(u32)) { + return {}; + } + + std::vector compressed_binary(compressed_size); + if (file.ReadArray(compressed_binary.data(), compressed_binary.size()) != + compressed_binary.size()) { + return {}; + } + + dump.binary = DecompressData(compressed_binary, binary_length); + if (dump.binary.empty()) { + return {}; + } + + dumps.insert({usage, dump}); + break; + } + default: + return {}; + } + } + return {{decompiled, dumps}}; +} + +std::optional ShaderDiskCacheOpenGL::LoadDecompiledEntry( + FileUtil::IOFile& file) { + u32 code_size{}; + u32 compressed_code_size{}; + if (file.ReadBytes(&code_size, sizeof(u32)) != sizeof(u32) || + file.ReadBytes(&compressed_code_size, sizeof(u32)) != sizeof(u32)) { + return {}; + } + + std::vector compressed_code(compressed_code_size); + if (file.ReadArray(compressed_code.data(), compressed_code.size()) != compressed_code.size()) { + return {}; + } + + const std::vector code = DecompressData(compressed_code, code_size); + if (code.empty()) { + return {}; + } + ShaderDiskCacheDecompiled entry; + entry.code = std::string(reinterpret_cast(code.data()), code_size); + + u32 const_buffers_count{}; + if (file.ReadBytes(&const_buffers_count, sizeof(u32)) != sizeof(u32)) + return {}; + for (u32 i = 0; i < const_buffers_count; ++i) { + u32 max_offset{}; + u32 index{}; + u8 is_indirect{}; + if (file.ReadBytes(&max_offset, sizeof(u32)) != sizeof(u32) || + file.ReadBytes(&index, sizeof(u32)) != sizeof(u32) || + file.ReadBytes(&is_indirect, sizeof(u8)) != sizeof(u8)) { + return {}; + } + entry.entries.const_buffers.emplace_back(max_offset, is_indirect != 0, index); + } + + u32 samplers_count{}; + if (file.ReadBytes(&samplers_count, sizeof(u32)) != sizeof(u32)) + return {}; + for (u32 i = 0; i < samplers_count; ++i) { + u64 offset{}; + u64 index{}; + u32 type{}; + u8 is_array{}; + u8 is_shadow{}; + if (file.ReadBytes(&offset, sizeof(u64)) != sizeof(u64) || + file.ReadBytes(&index, sizeof(u64)) != sizeof(u64) || + file.ReadBytes(&type, sizeof(u32)) != sizeof(u32) || + file.ReadBytes(&is_array, sizeof(u8)) != sizeof(u8) || + file.ReadBytes(&is_shadow, sizeof(u8)) != sizeof(u8)) { + return {}; + } + entry.entries.samplers.emplace_back( + static_cast(offset), static_cast(index), + static_cast(type), is_array != 0, is_shadow != 0); + } + + u32 global_memory_count{}; + if (file.ReadBytes(&global_memory_count, sizeof(u32)) != sizeof(u32)) + return {}; + for (u32 i = 0; i < global_memory_count; ++i) { + u32 cbuf_index{}; + u32 cbuf_offset{}; + if (file.ReadBytes(&cbuf_index, sizeof(u32)) != sizeof(u32) || + file.ReadBytes(&cbuf_offset, sizeof(u32)) != sizeof(u32)) { + return {}; + } + entry.entries.global_memory_entries.emplace_back(cbuf_index, cbuf_offset); + } + + for (auto& clip_distance : entry.entries.clip_distances) { + u8 clip_distance_raw{}; + if (file.ReadBytes(&clip_distance_raw, sizeof(u8)) != sizeof(u8)) + return {}; + clip_distance = clip_distance_raw != 0; + } + + u64 shader_length{}; + if (file.ReadBytes(&shader_length, sizeof(u64)) != sizeof(u64)) + return {}; + entry.entries.shader_length = static_cast(shader_length); + + return entry; +} + +bool ShaderDiskCacheOpenGL::SaveDecompiledFile(FileUtil::IOFile& file, u64 unique_identifier, + const std::string& code, + const std::vector& compressed_code, + const GLShader::ShaderEntries& entries) { + if (file.WriteObject(static_cast(PrecompiledEntryKind::Decompiled)) != 1 || + file.WriteObject(unique_identifier) != 1 || + file.WriteObject(static_cast(code.size())) != 1 || + file.WriteObject(static_cast(compressed_code.size())) != 1 || + file.WriteArray(compressed_code.data(), compressed_code.size()) != compressed_code.size()) { + return false; + } + + if (file.WriteObject(static_cast(entries.const_buffers.size())) != 1) + return false; + for (const auto& cbuf : entries.const_buffers) { + if (file.WriteObject(static_cast(cbuf.GetMaxOffset())) != 1 || + file.WriteObject(static_cast(cbuf.GetIndex())) != 1 || + file.WriteObject(static_cast(cbuf.IsIndirect() ? 1 : 0)) != 1) { + return false; + } + } + + if (file.WriteObject(static_cast(entries.samplers.size())) != 1) + return false; + for (const auto& sampler : entries.samplers) { + if (file.WriteObject(static_cast(sampler.GetOffset())) != 1 || + file.WriteObject(static_cast(sampler.GetIndex())) != 1 || + file.WriteObject(static_cast(sampler.GetType())) != 1 || + file.WriteObject(static_cast(sampler.IsArray() ? 1 : 0)) != 1 || + file.WriteObject(static_cast(sampler.IsShadow() ? 1 : 0)) != 1) { + return false; + } + } + + if (file.WriteObject(static_cast(entries.global_memory_entries.size())) != 1) + return false; + for (const auto& gmem : entries.global_memory_entries) { + if (file.WriteObject(static_cast(gmem.GetCbufIndex())) != 1 || + file.WriteObject(static_cast(gmem.GetCbufOffset())) != 1) { + return false; + } + } + + for (const bool clip_distance : entries.clip_distances) { + if (file.WriteObject(static_cast(clip_distance ? 1 : 0)) != 1) + return false; + } + + return file.WriteObject(static_cast(entries.shader_length)) == 1; +} + +void ShaderDiskCacheOpenGL::InvalidateTransferable() const { + if (!FileUtil::Delete(GetTransferablePath())) { + LOG_ERROR(Render_OpenGL, "Failed to invalidate transferable file={}", + GetTransferablePath()); + } + InvalidatePrecompiled(); +} + +void ShaderDiskCacheOpenGL::InvalidatePrecompiled() const { + if (!FileUtil::Delete(GetPrecompiledPath())) { + LOG_ERROR(Render_OpenGL, "Failed to invalidate precompiled file={}", GetPrecompiledPath()); + } +} + +void ShaderDiskCacheOpenGL::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, {}}); +} + +void ShaderDiskCacheOpenGL::SaveUsage(const ShaderDiskCacheUsage& usage) { + if (!IsUsable()) + return; + + const auto it = transferable.find(usage.unique_identifier); + ASSERT_MSG(it != transferable.end(), "Saving shader usage without storing raw previously"); + + auto& usages{it->second}; + ASSERT(usages.find(usage) == usages.end()); + usages.insert(usage); + + FileUtil::IOFile file = AppendTransferableFile(); + if (!file.IsOpen()) + return; + + if (file.WriteObject(TransferableEntryKind::Usage) != 1 || file.WriteObject(usage) != 1) { + LOG_ERROR(Render_OpenGL, "Failed to save usage transferable cache entry - removing"); + file.Close(); + InvalidateTransferable(); + return; + } +} + +void ShaderDiskCacheOpenGL::SaveDecompiled(u64 unique_identifier, const std::string& code, + const GLShader::ShaderEntries& entries) { + if (!IsUsable()) + return; + + const std::vector compressed_code{CompressData(code.data(), code.size())}; + if (compressed_code.empty()) { + LOG_ERROR(Render_OpenGL, "Failed to compress GLSL code - skipping shader {:016x}", + unique_identifier); + return; + } + + FileUtil::IOFile file = AppendPrecompiledFile(); + if (!file.IsOpen()) + return; + + if (!SaveDecompiledFile(file, unique_identifier, code, compressed_code, entries)) { + LOG_ERROR(Render_OpenGL, + "Failed to save decompiled entry to the precompiled file - removing"); + file.Close(); + InvalidatePrecompiled(); + } +} + +void ShaderDiskCacheOpenGL::SaveDump(const ShaderDiskCacheUsage& usage, 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()); + + const std::vector compressed_binary = CompressData(binary.data(), binary.size()); + if (compressed_binary.empty()) { + LOG_ERROR(Render_OpenGL, "Failed to compress binary program in shader={:016x}", + usage.unique_identifier); + return; + } + + FileUtil::IOFile file = AppendPrecompiledFile(); + if (!file.IsOpen()) + return; + + if (file.WriteObject(static_cast(PrecompiledEntryKind::Dump)) != 1 || + file.WriteObject(usage) != 1 || file.WriteObject(static_cast(binary_format)) != 1 || + file.WriteObject(static_cast(binary_length)) != 1 || + file.WriteObject(static_cast(compressed_binary.size())) != 1 || + file.WriteArray(compressed_binary.data(), compressed_binary.size()) != + compressed_binary.size()) { + LOG_ERROR(Render_OpenGL, "Failed to save binary program file in shader={:016x} - removing", + usage.unique_identifier); + file.Close(); + InvalidatePrecompiled(); + return; + } +} + +bool ShaderDiskCacheOpenGL::IsUsable() const { + return tried_to_load && Settings::values.use_disk_shader_cache; +} + +FileUtil::IOFile ShaderDiskCacheOpenGL::AppendTransferableFile() const { + 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; +} + +FileUtil::IOFile ShaderDiskCacheOpenGL::AppendPrecompiledFile() const { + if (!EnsureDirectories()) + return {}; + + const auto precompiled_path{GetPrecompiledPath()}; + const bool existed = FileUtil::Exists(precompiled_path); + + FileUtil::IOFile file(precompiled_path, "ab"); + if (!file.IsOpen()) { + LOG_ERROR(Render_OpenGL, "Failed to open precompiled cache in path={}", precompiled_path); + return {}; + } + + if (!existed || file.GetSize() == 0) { + const auto hash{GetShaderCacheVersionHash()}; + if (file.WriteArray(hash.data(), hash.size()) != hash.size()) { + LOG_ERROR(Render_OpenGL, "Failed to write precompiled cache version hash in path={}", + precompiled_path); + return {}; + } + } + return file; +} + +bool ShaderDiskCacheOpenGL::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 ShaderDiskCacheOpenGL::GetTransferablePath() const { + return FileUtil::SanitizePath(GetTransferableDir() + DIR_SEP_CHR + GetTitleID() + ".bin"); +} + +std::string ShaderDiskCacheOpenGL::GetPrecompiledPath() const { + return FileUtil::SanitizePath(GetPrecompiledDir() + DIR_SEP_CHR + GetTitleID() + ".bin"); +} + +std::string ShaderDiskCacheOpenGL::GetTransferableDir() const { + return GetBaseDir() + DIR_SEP "transferable"; +} + +std::string ShaderDiskCacheOpenGL::GetPrecompiledDir() const { + return GetBaseDir() + DIR_SEP "precompiled"; +} + +std::string ShaderDiskCacheOpenGL::GetBaseDir() const { + return FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir) + DIR_SEP "opengl"; +} + +std::string ShaderDiskCacheOpenGL::GetTitleID() const { + return fmt::format("{:016X}", system.CurrentProcess()->GetTitleID()); +} + +} // namespace OpenGL 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..6be0c0547b --- /dev/null +++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.h @@ -0,0 +1,245 @@ +// 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 "common/assert.h" +#include "common/common_types.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/renderer_opengl/gl_shader_gen.h" + +namespace Core { +class System; +} + +namespace FileUtil { +class IOFile; +} + +namespace OpenGL { + +using ProgramCode = std::vector; +using Maxwell = Tegra::Engines::Maxwell3D::Regs; + +/// Allocated bindings used by an OpenGL shader program +struct BaseBindings { + u32 cbuf{}; + u32 gmem{}; + u32 sampler{}; + + bool operator==(const BaseBindings& rhs) const { + return std::tie(cbuf, gmem, sampler) == std::tie(rhs.cbuf, rhs.gmem, rhs.sampler); + } + + bool operator!=(const BaseBindings& rhs) const { + return !operator==(rhs); + } +}; + +/// Describes how a shader is used +struct ShaderDiskCacheUsage { + u64 unique_identifier{}; + BaseBindings bindings; + GLenum primitive{}; + + bool operator==(const ShaderDiskCacheUsage& rhs) const { + return std::tie(unique_identifier, bindings, primitive) == + std::tie(rhs.unique_identifier, rhs.bindings, rhs.primitive); + } + + bool operator!=(const ShaderDiskCacheUsage& rhs) const { + return !operator==(rhs); + } +}; + +} // namespace OpenGL + +namespace std { + +template <> +struct hash { + std::size_t operator()(const OpenGL::BaseBindings& bindings) const { + return bindings.cbuf | bindings.gmem << 8 | bindings.sampler << 16; + } +}; + +template <> +struct hash { + std::size_t operator()(const OpenGL::ShaderDiskCacheUsage& usage) const { + return static_cast(usage.unique_identifier) ^ + std::hash()(usage.bindings) ^ usage.primitive << 16; + } +}; + +} // namespace std + +namespace OpenGL { + +/// Describes a shader how it's used by the guest GPU +class ShaderDiskCacheRaw { +public: + explicit ShaderDiskCacheRaw(u64 unique_identifier, Maxwell::ShaderProgram program_type, + u32 program_code_size, u32 program_code_size_b, + ProgramCode program_code, ProgramCode program_code_b); + ShaderDiskCacheRaw(); + ~ShaderDiskCacheRaw(); + + bool Load(FileUtil::IOFile& file); + + bool Save(FileUtil::IOFile& file) const; + + u64 GetUniqueIdentifier() const { + return unique_identifier; + } + + bool HasProgramA() const { + return program_type == Maxwell::ShaderProgram::VertexA; + } + + Maxwell::ShaderProgram GetProgramType() const { + return program_type; + } + + Maxwell::ShaderStage GetProgramStage() const { + switch (program_type) { + case Maxwell::ShaderProgram::VertexA: + case Maxwell::ShaderProgram::VertexB: + return Maxwell::ShaderStage::Vertex; + case Maxwell::ShaderProgram::TesselationControl: + return Maxwell::ShaderStage::TesselationControl; + case Maxwell::ShaderProgram::TesselationEval: + return Maxwell::ShaderStage::TesselationEval; + case Maxwell::ShaderProgram::Geometry: + return Maxwell::ShaderStage::Geometry; + case Maxwell::ShaderProgram::Fragment: + return Maxwell::ShaderStage::Fragment; + } + UNREACHABLE(); + } + + const ProgramCode& GetProgramCode() const { + return program_code; + } + + const ProgramCode& GetProgramCodeB() const { + return program_code_b; + } + +private: + u64 unique_identifier{}; + Maxwell::ShaderProgram program_type{}; + u32 program_code_size{}; + u32 program_code_size_b{}; + + ProgramCode program_code; + ProgramCode program_code_b; +}; + +/// Contains decompiled data from a shader +struct ShaderDiskCacheDecompiled { + std::string code; + GLShader::ShaderEntries entries; +}; + +/// Contains an OpenGL dumped binary program +struct ShaderDiskCacheDump { + GLenum binary_format; + std::vector binary; +}; + +class ShaderDiskCacheOpenGL { +public: + explicit ShaderDiskCacheOpenGL(Core::System& system); + + /// Loads transferable cache. If file has a old version or on failure, it deletes the file. + std::optional, std::vector>> + LoadTransferable(); + + /// Loads current game's precompiled cache. Invalidates on failure. + std::pair, + std::unordered_map> + LoadPrecompiled(); + + /// Removes the transferable (and precompiled) cache file. + void InvalidateTransferable() const; + + /// Removes the precompiled cache file. + void InvalidatePrecompiled() const; + + /// Saves a raw dump to the transferable file. Checks for collisions. + void SaveRaw(const ShaderDiskCacheRaw& entry); + + /// Saves shader usage to the transferable file. Does not check for collisions. + void SaveUsage(const ShaderDiskCacheUsage& usage); + + /// Saves a decompiled entry to the precompiled file. Does not check for collisions. + void SaveDecompiled(u64 unique_identifier, const std::string& code, + const GLShader::ShaderEntries& entries); + + /// Saves a dump entry to the precompiled file. Does not check for collisions. + void SaveDump(const ShaderDiskCacheUsage& usage, GLuint program); + +private: + /// Loads the transferable cache. Returns empty on failure. + std::optional, + std::unordered_map>> + LoadPrecompiledFile(FileUtil::IOFile& file); + + /// Loads a decompiled cache entry from the passed file. Returns empty on failure. + std::optional LoadDecompiledEntry(FileUtil::IOFile& file); + + /// Saves a decompiled entry to the passed file. Returns true on success. + bool SaveDecompiledFile(FileUtil::IOFile& file, u64 unique_identifier, const std::string& code, + const std::vector& compressed_code, + const GLShader::ShaderEntries& entries); + + /// 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() const; + + /// Opens current game's precompiled file and write it's header if it doesn't exist + FileUtil::IOFile AppendPrecompiledFile() const; + + /// Create shader disk cache directories. Returns true on success. + bool EnsureDirectories() const; + + /// Gets current game's transferable file path + std::string GetTransferablePath() const; + + /// Gets current game's precompiled file path + std::string GetPrecompiledPath() const; + + /// 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 + std::string GetTitleID() const; + + // Copre system + Core::System& system; + // Stored transferable shaders + std::map> transferable; + // The cache has been loaded at boot + bool tried_to_load{}; +}; + +} // 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 ac5e6917b6..fba8e681b7 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.h +++ b/src/video_core/renderer_opengl/gl_shader_gen.h @@ -26,12 +26,10 @@ struct ShaderSetup { ProgramCode code; ProgramCode code_b; // Used for dual vertex shaders u64 unique_identifier; - std::size_t real_size; - std::size_t real_size_b; } program; /// Used in scenarios where we have a dual vertex shaders - void SetProgramB(ProgramCode&& program_b) { + void SetProgramB(ProgramCode program_b) { program.code_b = std::move(program_b); has_program_b = true; } diff --git a/src/video_core/renderer_opengl/gl_shader_util.h b/src/video_core/renderer_opengl/gl_shader_util.h index 285594f507..03b7548c21 100644 --- a/src/video_core/renderer_opengl/gl_shader_util.h +++ b/src/video_core/renderer_opengl/gl_shader_util.h @@ -47,7 +47,7 @@ GLuint LoadShader(const char* source, GLenum type); * @returns Handle of the newly created OpenGL program object */ template -GLuint LoadProgram(bool separable_program, T... shaders) { +GLuint LoadProgram(bool separable_program, bool hint_retrievable, T... shaders) { // Link the program LOG_DEBUG(Render_OpenGL, "Linking program..."); @@ -58,6 +58,9 @@ GLuint LoadProgram(bool separable_program, T... shaders) { if (separable_program) { glProgramParameteri(program_id, GL_PROGRAM_SEPARABLE, GL_TRUE); } + if (hint_retrievable) { + glProgramParameteri(program_id, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE); + } glLinkProgram(program_id); diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 5b09c38ea8..6476a9e1a8 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -98,8 +98,8 @@ static std::array MakeOrthographicMatrix(const float width, cons return matrix; } -RendererOpenGL::RendererOpenGL(Core::Frontend::EmuWindow& window) - : VideoCore::RendererBase{window} {} +RendererOpenGL::RendererOpenGL(Core::Frontend::EmuWindow& window, Core::System& system) + : VideoCore::RendererBase{window}, system{system} {} RendererOpenGL::~RendererOpenGL() = default; @@ -250,7 +250,7 @@ void RendererOpenGL::CreateRasterizer() { } // Initialize sRGB Usage OpenGLState::ClearsRGBUsed(); - rasterizer = std::make_unique(render_window, screen_info); + rasterizer = std::make_unique(render_window, system, screen_info); } void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture, diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index 1665018dbf..7e13e566b1 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -12,6 +12,10 @@ #include "video_core/renderer_opengl/gl_resource_manager.h" #include "video_core/renderer_opengl/gl_state.h" +namespace Core { +class System; +} + namespace Core::Frontend { class EmuWindow; } @@ -41,7 +45,7 @@ struct ScreenInfo { class RendererOpenGL : public VideoCore::RendererBase { public: - explicit RendererOpenGL(Core::Frontend::EmuWindow& window); + explicit RendererOpenGL(Core::Frontend::EmuWindow& window, Core::System& system); ~RendererOpenGL() override; /// Swap buffers (render frame) @@ -72,6 +76,8 @@ private: void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, u8 color_a, const TextureInfo& texture); + Core::System& system; + OpenGLState state; // OpenGL object IDs diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h index 6e42e3dfbe..ef0f3a1066 100644 --- a/src/video_core/shader/shader_ir.h +++ b/src/video_core/shader/shader_ir.h @@ -236,6 +236,11 @@ private: class ConstBuffer { public: + explicit ConstBuffer(u32 max_offset, bool is_indirect) + : max_offset{max_offset}, is_indirect{is_indirect} {} + + ConstBuffer() = default; + void MarkAsUsed(u64 offset) { max_offset = std::max(max_offset, static_cast(offset)); } @@ -252,6 +257,10 @@ public: return max_offset + sizeof(float); } + u32 GetMaxOffset() const { + return max_offset; + } + private: u32 max_offset{}; bool is_indirect{}; diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp index 0b8ccdd449..cb82ecf3f6 100644 --- a/src/video_core/video_core.cpp +++ b/src/video_core/video_core.cpp @@ -11,8 +11,9 @@ namespace VideoCore { -std::unique_ptr CreateRenderer(Core::Frontend::EmuWindow& emu_window) { - return std::make_unique(emu_window); +std::unique_ptr CreateRenderer(Core::Frontend::EmuWindow& emu_window, + Core::System& system) { + return std::make_unique(emu_window, system); } u16 GetResolutionScaleFactor(const RendererBase& renderer) { diff --git a/src/video_core/video_core.h b/src/video_core/video_core.h index 5b373bcb1f..3c583f1954 100644 --- a/src/video_core/video_core.h +++ b/src/video_core/video_core.h @@ -6,6 +6,10 @@ #include +namespace Core { +class System; +} + namespace Core::Frontend { class EmuWindow; } @@ -20,7 +24,8 @@ class RendererBase; * @note The returned renderer instance is simply allocated. Its Init() * function still needs to be called to fully complete its setup. */ -std::unique_ptr CreateRenderer(Core::Frontend::EmuWindow& emu_window); +std::unique_ptr CreateRenderer(Core::Frontend::EmuWindow& emu_window, + Core::System& system); u16 GetResolutionScaleFactor(const RendererBase& renderer); diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index f74cb693a4..73b04b7492 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -29,6 +29,15 @@ void EmuThread::run() { stop_run = false; + emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); + + Core::System::GetInstance().Renderer().Rasterizer().LoadDiskResources( + stop_run, [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) { + emit LoadProgress(stage, value, total); + }); + + emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0); + // holds whether the cpu was running during the last iteration, // so that the DebugModeLeft signal can be emitted before the // next execution step diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index d1f37e5032..7226e690eb 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -22,6 +22,10 @@ class GGLWidgetInternal; class GMainWindow; class GRenderWindow; +namespace VideoCore { +enum class LoadCallbackStage; +} + class EmuThread : public QThread { Q_OBJECT @@ -75,7 +79,7 @@ public: private: bool exec_step = false; bool running = false; - std::atomic stop_run{false}; + std::atomic_bool stop_run{false}; std::mutex running_mutex; std::condition_variable running_cv; @@ -101,6 +105,8 @@ signals: void DebugModeLeft(); void ErrorThrown(Core::System::ResultStatus, std::string); + + void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total); }; class GRenderWindow : public QWidget, public Core::Frontend::EmuWindow { diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index ddf4cf552a..e9546dadf3 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -370,6 +370,8 @@ void Config::ReadValues() { Settings::values.resolution_factor = qt_config->value("resolution_factor", 1.0).toFloat(); Settings::values.use_frame_limit = qt_config->value("use_frame_limit", true).toBool(); Settings::values.frame_limit = qt_config->value("frame_limit", 100).toInt(); + Settings::values.use_disk_shader_cache = + qt_config->value("use_disk_shader_cache", false).toBool(); Settings::values.use_accurate_gpu_emulation = qt_config->value("use_accurate_gpu_emulation", false).toBool(); @@ -629,6 +631,7 @@ void Config::SaveValues() { qt_config->setValue("resolution_factor", (double)Settings::values.resolution_factor); qt_config->setValue("use_frame_limit", Settings::values.use_frame_limit); qt_config->setValue("frame_limit", Settings::values.frame_limit); + qt_config->setValue("use_disk_shader_cache", Settings::values.use_disk_shader_cache); qt_config->setValue("use_accurate_gpu_emulation", Settings::values.use_accurate_gpu_emulation); // Cast to double because Qt's written float values are not human-readable diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index d21f95469f..0f5dd534b1 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -73,6 +73,7 @@ void ConfigureGraphics::setConfiguration() { static_cast(FromResolutionFactor(Settings::values.resolution_factor))); ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit); ui->frame_limit->setValue(Settings::values.frame_limit); + ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache); ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation); UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue)); @@ -83,6 +84,7 @@ void ConfigureGraphics::applyConfiguration() { ToResolutionFactor(static_cast(ui->resolution_factor_combobox->currentIndex())); Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked(); Settings::values.frame_limit = ui->frame_limit->value(); + Settings::values.use_disk_shader_cache = ui->use_disk_shader_cache->isChecked(); Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked(); Settings::values.bg_red = static_cast(bg_color.redF()); Settings::values.bg_green = static_cast(bg_color.greenF()); diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui index e278cdd052..824f5810a1 100644 --- a/src/yuzu/configuration/configure_graphics.ui +++ b/src/yuzu/configuration/configure_graphics.ui @@ -49,6 +49,13 @@ + + + + Use disk shader cache + + + diff --git a/src/yuzu/loading_screen.cpp b/src/yuzu/loading_screen.cpp index 907aac4f1d..86f6d0165a 100644 --- a/src/yuzu/loading_screen.cpp +++ b/src/yuzu/loading_screen.cpp @@ -43,6 +43,7 @@ QProgressBar { } QProgressBar::chunk { background-color: #0ab9e6; + width: 1px; })"; constexpr const char PROGRESSBAR_STYLE_BUILD[] = R"( @@ -53,7 +54,8 @@ QProgressBar { padding: 2px; } QProgressBar::chunk { - background-color: #ff3c28; + background-color: #ff3c28; + width: 1px; })"; constexpr const char PROGRESSBAR_STYLE_COMPLETE[] = R"( diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 485e29de20..1d460c189a 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -887,6 +887,9 @@ void GMainWindow::BootGame(const QString& filename) { connect(emu_thread.get(), &EmuThread::DebugModeLeft, waitTreeWidget, &WaitTreeWidget::OnDebugModeLeft, Qt::BlockingQueuedConnection); + connect(emu_thread.get(), &EmuThread::LoadProgress, loading_screen, + &LoadingScreen::OnLoadProgress, Qt::QueuedConnection); + // Update the GUI if (ui.action_Single_Window_Mode->isChecked()) { game_list->hide(); diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 7a77f76e83..ff05b31791 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -350,6 +350,8 @@ void Config::ReadValues() { Settings::values.use_frame_limit = sdl2_config->GetBoolean("Renderer", "use_frame_limit", true); Settings::values.frame_limit = static_cast(sdl2_config->GetInteger("Renderer", "frame_limit", 100)); + Settings::values.use_disk_shader_cache = + sdl2_config->GetBoolean("Renderer", "use_disk_shader_cache", false); Settings::values.use_accurate_gpu_emulation = sdl2_config->GetBoolean("Renderer", "use_accurate_gpu_emulation", false); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index ba51a4a512..a81986f8e6 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -110,6 +110,10 @@ use_frame_limit = # 1 - 9999: Speed limit as a percentage of target game speed. 100 (default) frame_limit = +# Whether to use disk based shader cache +# 0 (default): Off, 1 : On +use_disk_shader_cache = + # Whether to use accurate GPU emulation # 0 (default): Off (fast), 1 : On (slow) use_accurate_gpu_emulation = diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 806127b12d..c34b5467f5 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -28,6 +28,7 @@ #include "core/loader/loader.h" #include "core/settings.h" #include "core/telemetry_session.h" +#include "video_core/renderer_base.h" #include "yuzu_cmd/config.h" #include "yuzu_cmd/emu_window/emu_window_sdl2.h" @@ -217,6 +218,8 @@ int main(int argc, char** argv) { Core::Telemetry().AddField(Telemetry::FieldType::App, "Frontend", "SDL"); + system.Renderer().Rasterizer().LoadDiskResources(); + while (emu_window->IsOpen()) { system.RunLoop(); }