Merge pull request #3473 from ReinUsesLisp/shader-purge
gl_shader_cache: Rework shader cache and store texture arrays
This commit is contained in:
		| @@ -57,8 +57,6 @@ set(HASH_FILES | ||||
|     "${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" | ||||
| @@ -91,8 +89,6 @@ set(HASH_FILES | ||||
|     "${VIDEO_CORE}/shader/ast.h" | ||||
|     "${VIDEO_CORE}/shader/compiler_settings.cpp" | ||||
|     "${VIDEO_CORE}/shader/compiler_settings.h" | ||||
|     "${VIDEO_CORE}/shader/const_buffer_locker.cpp" | ||||
|     "${VIDEO_CORE}/shader/const_buffer_locker.h" | ||||
|     "${VIDEO_CORE}/shader/control_flow.cpp" | ||||
|     "${VIDEO_CORE}/shader/control_flow.h" | ||||
|     "${VIDEO_CORE}/shader/decode.cpp" | ||||
| @@ -101,6 +97,8 @@ set(HASH_FILES | ||||
|     "${VIDEO_CORE}/shader/node.h" | ||||
|     "${VIDEO_CORE}/shader/node_helper.cpp" | ||||
|     "${VIDEO_CORE}/shader/node_helper.h" | ||||
|     "${VIDEO_CORE}/shader/registry.cpp" | ||||
|     "${VIDEO_CORE}/shader/registry.h" | ||||
|     "${VIDEO_CORE}/shader/shader_ir.cpp" | ||||
|     "${VIDEO_CORE}/shader/shader_ir.h" | ||||
|     "${VIDEO_CORE}/shader/track.cpp" | ||||
|   | ||||
| @@ -38,8 +38,6 @@ add_custom_command(OUTPUT scm_rev.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" | ||||
| @@ -72,8 +70,6 @@ add_custom_command(OUTPUT scm_rev.cpp | ||||
|       "${VIDEO_CORE}/shader/ast.h" | ||||
|       "${VIDEO_CORE}/shader/compiler_settings.cpp" | ||||
|       "${VIDEO_CORE}/shader/compiler_settings.h" | ||||
|       "${VIDEO_CORE}/shader/const_buffer_locker.cpp" | ||||
|       "${VIDEO_CORE}/shader/const_buffer_locker.h" | ||||
|       "${VIDEO_CORE}/shader/control_flow.cpp" | ||||
|       "${VIDEO_CORE}/shader/control_flow.h" | ||||
|       "${VIDEO_CORE}/shader/decode.cpp" | ||||
| @@ -82,6 +78,8 @@ add_custom_command(OUTPUT scm_rev.cpp | ||||
|       "${VIDEO_CORE}/shader/node.h" | ||||
|       "${VIDEO_CORE}/shader/node_helper.cpp" | ||||
|       "${VIDEO_CORE}/shader/node_helper.h" | ||||
|       "${VIDEO_CORE}/shader/registry.cpp" | ||||
|       "${VIDEO_CORE}/shader/registry.h" | ||||
|       "${VIDEO_CORE}/shader/shader_ir.cpp" | ||||
|       "${VIDEO_CORE}/shader/shader_ir.h" | ||||
|       "${VIDEO_CORE}/shader/track.cpp" | ||||
|   | ||||
| @@ -65,8 +65,6 @@ add_library(video_core STATIC | ||||
|     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 | ||||
|     renderer_opengl/gl_shader_manager.h | ||||
|     renderer_opengl/gl_shader_util.cpp | ||||
| @@ -118,8 +116,6 @@ add_library(video_core STATIC | ||||
|     shader/ast.h | ||||
|     shader/compiler_settings.cpp | ||||
|     shader/compiler_settings.h | ||||
|     shader/const_buffer_locker.cpp | ||||
|     shader/const_buffer_locker.h | ||||
|     shader/control_flow.cpp | ||||
|     shader/control_flow.h | ||||
|     shader/decode.cpp | ||||
| @@ -128,6 +124,8 @@ add_library(video_core STATIC | ||||
|     shader/node_helper.cpp | ||||
|     shader/node_helper.h | ||||
|     shader/node.h | ||||
|     shader/registry.cpp | ||||
|     shader/registry.h | ||||
|     shader/shader_ir.cpp | ||||
|     shader/shader_ir.h | ||||
|     shader/track.cpp | ||||
|   | ||||
| @@ -16,11 +16,12 @@ namespace Tegra::Engines { | ||||
|  | ||||
| struct SamplerDescriptor { | ||||
|     union { | ||||
|         BitField<0, 20, Tegra::Shader::TextureType> texture_type; | ||||
|         BitField<20, 1, u32> is_array; | ||||
|         BitField<21, 1, u32> is_buffer; | ||||
|         BitField<22, 1, u32> is_shadow; | ||||
|         u32 raw{}; | ||||
|         u32 raw = 0; | ||||
|         BitField<0, 2, Tegra::Shader::TextureType> texture_type; | ||||
|         BitField<2, 3, Tegra::Texture::ComponentType> component_type; | ||||
|         BitField<5, 1, u32> is_array; | ||||
|         BitField<6, 1, u32> is_buffer; | ||||
|         BitField<7, 1, u32> is_shadow; | ||||
|     }; | ||||
|  | ||||
|     bool operator==(const SamplerDescriptor& rhs) const noexcept { | ||||
| @@ -31,68 +32,48 @@ struct SamplerDescriptor { | ||||
|         return !operator==(rhs); | ||||
|     } | ||||
|  | ||||
|     static SamplerDescriptor FromTicTexture(Tegra::Texture::TextureType tic_texture_type) { | ||||
|     static SamplerDescriptor FromTIC(const Tegra::Texture::TICEntry& tic) { | ||||
|         using Tegra::Shader::TextureType; | ||||
|         SamplerDescriptor result; | ||||
|         switch (tic_texture_type) { | ||||
|  | ||||
|         // This is going to be used to determine the shading language type. | ||||
|         // Because of that we don't care about all component types on color textures. | ||||
|         result.component_type.Assign(tic.r_type.Value()); | ||||
|  | ||||
|         switch (tic.texture_type.Value()) { | ||||
|         case Tegra::Texture::TextureType::Texture1D: | ||||
|             result.texture_type.Assign(Tegra::Shader::TextureType::Texture1D); | ||||
|             result.is_array.Assign(0); | ||||
|             result.is_buffer.Assign(0); | ||||
|             result.is_shadow.Assign(0); | ||||
|             result.texture_type.Assign(TextureType::Texture1D); | ||||
|             return result; | ||||
|         case Tegra::Texture::TextureType::Texture2D: | ||||
|             result.texture_type.Assign(Tegra::Shader::TextureType::Texture2D); | ||||
|             result.is_array.Assign(0); | ||||
|             result.is_buffer.Assign(0); | ||||
|             result.is_shadow.Assign(0); | ||||
|             result.texture_type.Assign(TextureType::Texture2D); | ||||
|             return result; | ||||
|         case Tegra::Texture::TextureType::Texture3D: | ||||
|             result.texture_type.Assign(Tegra::Shader::TextureType::Texture3D); | ||||
|             result.is_array.Assign(0); | ||||
|             result.is_buffer.Assign(0); | ||||
|             result.is_shadow.Assign(0); | ||||
|             result.texture_type.Assign(TextureType::Texture3D); | ||||
|             return result; | ||||
|         case Tegra::Texture::TextureType::TextureCubemap: | ||||
|             result.texture_type.Assign(Tegra::Shader::TextureType::TextureCube); | ||||
|             result.is_array.Assign(0); | ||||
|             result.is_buffer.Assign(0); | ||||
|             result.is_shadow.Assign(0); | ||||
|             result.texture_type.Assign(TextureType::TextureCube); | ||||
|             return result; | ||||
|         case Tegra::Texture::TextureType::Texture1DArray: | ||||
|             result.texture_type.Assign(Tegra::Shader::TextureType::Texture1D); | ||||
|             result.texture_type.Assign(TextureType::Texture1D); | ||||
|             result.is_array.Assign(1); | ||||
|             result.is_buffer.Assign(0); | ||||
|             result.is_shadow.Assign(0); | ||||
|             return result; | ||||
|         case Tegra::Texture::TextureType::Texture2DArray: | ||||
|             result.texture_type.Assign(Tegra::Shader::TextureType::Texture2D); | ||||
|             result.texture_type.Assign(TextureType::Texture2D); | ||||
|             result.is_array.Assign(1); | ||||
|             result.is_buffer.Assign(0); | ||||
|             result.is_shadow.Assign(0); | ||||
|             return result; | ||||
|         case Tegra::Texture::TextureType::Texture1DBuffer: | ||||
|             result.texture_type.Assign(Tegra::Shader::TextureType::Texture1D); | ||||
|             result.is_array.Assign(0); | ||||
|             result.texture_type.Assign(TextureType::Texture1D); | ||||
|             result.is_buffer.Assign(1); | ||||
|             result.is_shadow.Assign(0); | ||||
|             return result; | ||||
|         case Tegra::Texture::TextureType::Texture2DNoMipmap: | ||||
|             result.texture_type.Assign(Tegra::Shader::TextureType::Texture2D); | ||||
|             result.is_array.Assign(0); | ||||
|             result.is_buffer.Assign(0); | ||||
|             result.is_shadow.Assign(0); | ||||
|             result.texture_type.Assign(TextureType::Texture2D); | ||||
|             return result; | ||||
|         case Tegra::Texture::TextureType::TextureCubeArray: | ||||
|             result.texture_type.Assign(Tegra::Shader::TextureType::TextureCube); | ||||
|             result.texture_type.Assign(TextureType::TextureCube); | ||||
|             result.is_array.Assign(1); | ||||
|             result.is_buffer.Assign(0); | ||||
|             result.is_shadow.Assign(0); | ||||
|             return result; | ||||
|         default: | ||||
|             result.texture_type.Assign(Tegra::Shader::TextureType::Texture2D); | ||||
|             result.is_array.Assign(0); | ||||
|             result.is_buffer.Assign(0); | ||||
|             result.is_shadow.Assign(0); | ||||
|             result.texture_type.Assign(TextureType::Texture2D); | ||||
|             return result; | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -89,7 +89,7 @@ SamplerDescriptor KeplerCompute::AccessBindlessSampler(ShaderType stage, u64 con | ||||
|  | ||||
|     const Texture::TextureHandle tex_handle{memory_manager.Read<u32>(tex_info_address)}; | ||||
|     const Texture::FullTextureInfo tex_info = GetTextureInfo(tex_handle); | ||||
|     SamplerDescriptor result = SamplerDescriptor::FromTicTexture(tex_info.tic.texture_type.Value()); | ||||
|     SamplerDescriptor result = SamplerDescriptor::FromTIC(tex_info.tic); | ||||
|     result.is_shadow.Assign(tex_info.tsc.depth_compare_enabled.Value()); | ||||
|     return result; | ||||
| } | ||||
|   | ||||
| @@ -638,7 +638,7 @@ SamplerDescriptor Maxwell3D::AccessBindlessSampler(ShaderType stage, u64 const_b | ||||
|  | ||||
|     const Texture::TextureHandle tex_handle{memory_manager.Read<u32>(tex_info_address)}; | ||||
|     const Texture::FullTextureInfo tex_info = GetTextureInfo(tex_handle); | ||||
|     SamplerDescriptor result = SamplerDescriptor::FromTicTexture(tex_info.tic.texture_type.Value()); | ||||
|     SamplerDescriptor result = SamplerDescriptor::FromTIC(tex_info.tic); | ||||
|     result.is_shadow.Assign(tex_info.tsc.depth_compare_enabled.Value()); | ||||
|     return result; | ||||
| } | ||||
|   | ||||
| @@ -67,6 +67,7 @@ public: | ||||
|         static constexpr std::size_t NumVaryings = 31; | ||||
|         static constexpr std::size_t NumImages = 8; // TODO(Rodrigo): Investigate this number | ||||
|         static constexpr std::size_t NumClipDistances = 8; | ||||
|         static constexpr std::size_t NumTransformFeedbackBuffers = 4; | ||||
|         static constexpr std::size_t MaxShaderProgram = 6; | ||||
|         static constexpr std::size_t MaxShaderStage = 5; | ||||
|         // Maximum number of const buffers per shader stage. | ||||
| @@ -627,6 +628,22 @@ public: | ||||
|             float depth_range_far; | ||||
|         }; | ||||
|  | ||||
|         struct alignas(32) TransformFeedbackBinding { | ||||
|             u32 buffer_enable; | ||||
|             u32 address_high; | ||||
|             u32 address_low; | ||||
|             s32 buffer_size; | ||||
|             s32 buffer_offset; | ||||
|         }; | ||||
|         static_assert(sizeof(TransformFeedbackBinding) == 32); | ||||
|  | ||||
|         struct alignas(16) TransformFeedbackLayout { | ||||
|             u32 stream; | ||||
|             u32 varying_count; | ||||
|             u32 stride; | ||||
|         }; | ||||
|         static_assert(sizeof(TransformFeedbackLayout) == 16); | ||||
|  | ||||
|         bool IsShaderConfigEnabled(std::size_t index) const { | ||||
|             // The VertexB is always enabled. | ||||
|             if (index == static_cast<std::size_t>(Regs::ShaderProgram::VertexB)) { | ||||
| @@ -683,7 +700,13 @@ public: | ||||
|  | ||||
|                 u32 rasterize_enable; | ||||
|  | ||||
|                 INSERT_UNION_PADDING_WORDS(0xF1); | ||||
|                 std::array<TransformFeedbackBinding, NumTransformFeedbackBuffers> tfb_bindings; | ||||
|  | ||||
|                 INSERT_UNION_PADDING_WORDS(0xC0); | ||||
|  | ||||
|                 std::array<TransformFeedbackLayout, NumTransformFeedbackBuffers> tfb_layouts; | ||||
|  | ||||
|                 INSERT_UNION_PADDING_WORDS(0x1); | ||||
|  | ||||
|                 u32 tfb_enabled; | ||||
|  | ||||
| @@ -1202,7 +1225,11 @@ public: | ||||
|  | ||||
|                 u32 tex_cb_index; | ||||
|  | ||||
|                 INSERT_UNION_PADDING_WORDS(0x395); | ||||
|                 INSERT_UNION_PADDING_WORDS(0x7D); | ||||
|  | ||||
|                 std::array<std::array<u8, 128>, NumTransformFeedbackBuffers> tfb_varying_locs; | ||||
|  | ||||
|                 INSERT_UNION_PADDING_WORDS(0x298); | ||||
|  | ||||
|                 struct { | ||||
|                     /// Compressed address of a buffer that holds information about bound SSBOs. | ||||
| @@ -1428,6 +1455,8 @@ ASSERT_REG_POSITION(tess_mode, 0xC8); | ||||
| ASSERT_REG_POSITION(tess_level_outer, 0xC9); | ||||
| ASSERT_REG_POSITION(tess_level_inner, 0xCD); | ||||
| ASSERT_REG_POSITION(rasterize_enable, 0xDF); | ||||
| ASSERT_REG_POSITION(tfb_bindings, 0xE0); | ||||
| ASSERT_REG_POSITION(tfb_layouts, 0x1C0); | ||||
| ASSERT_REG_POSITION(tfb_enabled, 0x1D1); | ||||
| ASSERT_REG_POSITION(rt, 0x200); | ||||
| ASSERT_REG_POSITION(viewport_transform, 0x280); | ||||
| @@ -1526,6 +1555,7 @@ ASSERT_REG_POSITION(firmware, 0x8C0); | ||||
| ASSERT_REG_POSITION(const_buffer, 0x8E0); | ||||
| ASSERT_REG_POSITION(cb_bind[0], 0x904); | ||||
| ASSERT_REG_POSITION(tex_cb_index, 0x982); | ||||
| ASSERT_REG_POSITION(tfb_varying_locs, 0xA00); | ||||
| ASSERT_REG_POSITION(ssbo_info, 0xD18); | ||||
| ASSERT_REG_POSITION(tex_info_buffers.address[0], 0xD2A); | ||||
| ASSERT_REG_POSITION(tex_info_buffers.size[0], 0xD2F); | ||||
|   | ||||
| @@ -4,13 +4,15 @@ | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <limits> | ||||
| #include <vector> | ||||
|  | ||||
| #include "common/common_types.h" | ||||
| #include "video_core/guest_driver.h" | ||||
|  | ||||
| namespace VideoCore { | ||||
|  | ||||
| void GuestDriverProfile::DeduceTextureHandlerSize(std::vector<u32>&& bound_offsets) { | ||||
|     if (texture_handler_size_deduced) { | ||||
| void GuestDriverProfile::DeduceTextureHandlerSize(std::vector<u32> bound_offsets) { | ||||
|     if (texture_handler_size) { | ||||
|         return; | ||||
|     } | ||||
|     const std::size_t size = bound_offsets.size(); | ||||
| @@ -29,7 +31,6 @@ void GuestDriverProfile::DeduceTextureHandlerSize(std::vector<u32>&& bound_offse | ||||
|     if (min_val > 2) { | ||||
|         return; | ||||
|     } | ||||
|     texture_handler_size_deduced = true; | ||||
|     texture_handler_size = min_texture_handler_size * min_val; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <optional> | ||||
| #include <vector> | ||||
|  | ||||
| #include "common/common_types.h" | ||||
| @@ -17,25 +18,29 @@ namespace VideoCore { | ||||
|  */ | ||||
| class GuestDriverProfile { | ||||
| public: | ||||
|     void DeduceTextureHandlerSize(std::vector<u32>&& bound_offsets); | ||||
|     explicit GuestDriverProfile() = default; | ||||
|     explicit GuestDriverProfile(std::optional<u32> texture_handler_size) | ||||
|         : texture_handler_size{texture_handler_size} {} | ||||
|  | ||||
|     void DeduceTextureHandlerSize(std::vector<u32> bound_offsets); | ||||
|  | ||||
|     u32 GetTextureHandlerSize() const { | ||||
|         return texture_handler_size; | ||||
|         return texture_handler_size.value_or(default_texture_handler_size); | ||||
|     } | ||||
|  | ||||
|     bool TextureHandlerSizeKnown() const { | ||||
|         return texture_handler_size_deduced; | ||||
|     bool IsTextureHandlerSizeKnown() const { | ||||
|         return texture_handler_size.has_value(); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     // Minimum size of texture handler any driver can use. | ||||
|     static constexpr u32 min_texture_handler_size = 4; | ||||
|     // This goes with Vulkan and OpenGL standards but Nvidia GPUs can easily | ||||
|     // use 4 bytes instead. Thus, certain drivers may squish the size. | ||||
|  | ||||
|     // This goes with Vulkan and OpenGL standards but Nvidia GPUs can easily use 4 bytes instead. | ||||
|     // Thus, certain drivers may squish the size. | ||||
|     static constexpr u32 default_texture_handler_size = 8; | ||||
|  | ||||
|     u32 texture_handler_size = default_texture_handler_size; | ||||
|     bool texture_handler_size_deduced = false; | ||||
|     std::optional<u32> texture_handler_size = default_texture_handler_size; | ||||
| }; | ||||
|  | ||||
| } // namespace VideoCore | ||||
|   | ||||
| @@ -25,7 +25,6 @@ constexpr std::size_t NumQueryTypes = 1; | ||||
|  | ||||
| enum class LoadCallbackStage { | ||||
|     Prepare, | ||||
|     Decompile, | ||||
|     Build, | ||||
|     Complete, | ||||
| }; | ||||
|   | ||||
| @@ -28,7 +28,6 @@ | ||||
| #include "video_core/renderer_opengl/gl_query_cache.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" | ||||
|  | ||||
| @@ -76,7 +75,7 @@ Tegra::Texture::FullTextureInfo GetTextureInfo(const Engine& engine, const Entry | ||||
| } | ||||
|  | ||||
| std::size_t GetConstBufferSize(const Tegra::Engines::ConstBufferInfo& buffer, | ||||
|                                const GLShader::ConstBufferEntry& entry) { | ||||
|                                const ConstBufferEntry& entry) { | ||||
|     if (!entry.IsIndirect()) { | ||||
|         return entry.GetSize(); | ||||
|     } | ||||
| @@ -272,9 +271,7 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { | ||||
|         SetupDrawTextures(stage, shader); | ||||
|         SetupDrawImages(stage, shader); | ||||
|  | ||||
|         const ProgramVariant variant(primitive_mode); | ||||
|         const auto program_handle = shader->GetHandle(variant); | ||||
|  | ||||
|         const GLuint program_handle = shader->GetHandle(); | ||||
|         switch (program) { | ||||
|         case Maxwell::ShaderProgram::VertexA: | ||||
|         case Maxwell::ShaderProgram::VertexB: | ||||
| @@ -295,7 +292,7 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { | ||||
|         // When a clip distance is enabled but not set in the shader it crops parts of the screen | ||||
|         // (sometimes it's half the screen, sometimes three quarters). To avoid this, enable the | ||||
|         // clip distances only when it's written by a shader stage. | ||||
|         clip_distances |= shader->GetShaderEntries().clip_distances; | ||||
|         clip_distances |= shader->GetEntries().clip_distances; | ||||
|  | ||||
|         // When VertexA is enabled, we have dual vertex shaders | ||||
|         if (program == Maxwell::ShaderProgram::VertexA) { | ||||
| @@ -623,12 +620,7 @@ void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) { | ||||
|     auto kernel = shader_cache.GetComputeKernel(code_addr); | ||||
|     SetupComputeTextures(kernel); | ||||
|     SetupComputeImages(kernel); | ||||
|  | ||||
|     const auto& launch_desc = system.GPU().KeplerCompute().launch_description; | ||||
|     const ProgramVariant variant(launch_desc.block_dim_x, launch_desc.block_dim_y, | ||||
|                                  launch_desc.block_dim_z, launch_desc.shared_alloc, | ||||
|                                  launch_desc.local_pos_alloc); | ||||
|     program_manager.BindComputeShader(kernel->GetHandle(variant)); | ||||
|     program_manager.BindComputeShader(kernel->GetHandle()); | ||||
|  | ||||
|     const std::size_t buffer_size = | ||||
|         Tegra::Engines::KeplerCompute::NumConstBuffers * | ||||
| @@ -646,6 +638,7 @@ void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) { | ||||
|     bind_ubo_pushbuffer.Bind(); | ||||
|     bind_ssbo_pushbuffer.Bind(); | ||||
|  | ||||
|     const auto& launch_desc = system.GPU().KeplerCompute().launch_description; | ||||
|     glDispatchCompute(launch_desc.grid_dim_x, launch_desc.grid_dim_y, launch_desc.grid_dim_z); | ||||
|     ++num_queued_commands; | ||||
| } | ||||
| @@ -750,7 +743,7 @@ void RasterizerOpenGL::SetupDrawConstBuffers(std::size_t stage_index, const Shad | ||||
|     const auto& shader_stage = stages[stage_index]; | ||||
|  | ||||
|     u32 binding = device.GetBaseBindings(stage_index).uniform_buffer; | ||||
|     for (const auto& entry : shader->GetShaderEntries().const_buffers) { | ||||
|     for (const auto& entry : shader->GetEntries().const_buffers) { | ||||
|         const auto& buffer = shader_stage.const_buffers[entry.GetIndex()]; | ||||
|         SetupConstBuffer(binding++, buffer, entry); | ||||
|     } | ||||
| @@ -761,7 +754,7 @@ void RasterizerOpenGL::SetupComputeConstBuffers(const Shader& kernel) { | ||||
|     const auto& launch_desc = system.GPU().KeplerCompute().launch_description; | ||||
|  | ||||
|     u32 binding = 0; | ||||
|     for (const auto& entry : kernel->GetShaderEntries().const_buffers) { | ||||
|     for (const auto& entry : kernel->GetEntries().const_buffers) { | ||||
|         const auto& config = launch_desc.const_buffer_config[entry.GetIndex()]; | ||||
|         const std::bitset<8> mask = launch_desc.const_buffer_enable_mask.Value(); | ||||
|         Tegra::Engines::ConstBufferInfo buffer; | ||||
| @@ -773,7 +766,7 @@ void RasterizerOpenGL::SetupComputeConstBuffers(const Shader& kernel) { | ||||
| } | ||||
|  | ||||
| void RasterizerOpenGL::SetupConstBuffer(u32 binding, const Tegra::Engines::ConstBufferInfo& buffer, | ||||
|                                         const GLShader::ConstBufferEntry& entry) { | ||||
|                                         const ConstBufferEntry& entry) { | ||||
|     if (!buffer.enabled) { | ||||
|         // Set values to zero to unbind buffers | ||||
|         bind_ubo_pushbuffer.Push(binding, buffer_cache.GetEmptyBuffer(sizeof(float)), 0, | ||||
| @@ -797,7 +790,7 @@ void RasterizerOpenGL::SetupDrawGlobalMemory(std::size_t stage_index, const Shad | ||||
|     const auto cbufs{gpu.Maxwell3D().state.shader_stages[stage_index]}; | ||||
|  | ||||
|     u32 binding = device.GetBaseBindings(stage_index).shader_storage_buffer; | ||||
|     for (const auto& entry : shader->GetShaderEntries().global_memory_entries) { | ||||
|     for (const auto& entry : shader->GetEntries().global_memory_entries) { | ||||
|         const auto addr{cbufs.const_buffers[entry.GetCbufIndex()].address + entry.GetCbufOffset()}; | ||||
|         const auto gpu_addr{memory_manager.Read<u64>(addr)}; | ||||
|         const auto size{memory_manager.Read<u32>(addr + 8)}; | ||||
| @@ -811,7 +804,7 @@ void RasterizerOpenGL::SetupComputeGlobalMemory(const Shader& kernel) { | ||||
|     const auto cbufs{gpu.KeplerCompute().launch_description.const_buffer_config}; | ||||
|  | ||||
|     u32 binding = 0; | ||||
|     for (const auto& entry : kernel->GetShaderEntries().global_memory_entries) { | ||||
|     for (const auto& entry : kernel->GetEntries().global_memory_entries) { | ||||
|         const auto addr{cbufs[entry.GetCbufIndex()].Address() + entry.GetCbufOffset()}; | ||||
|         const auto gpu_addr{memory_manager.Read<u64>(addr)}; | ||||
|         const auto size{memory_manager.Read<u32>(addr + 8)}; | ||||
| @@ -819,7 +812,7 @@ void RasterizerOpenGL::SetupComputeGlobalMemory(const Shader& kernel) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| void RasterizerOpenGL::SetupGlobalMemory(u32 binding, const GLShader::GlobalMemoryEntry& entry, | ||||
| void RasterizerOpenGL::SetupGlobalMemory(u32 binding, const GlobalMemoryEntry& entry, | ||||
|                                          GPUVAddr gpu_addr, std::size_t size) { | ||||
|     const auto alignment{device.GetShaderStorageBufferAlignment()}; | ||||
|     const auto [ssbo, buffer_offset] = | ||||
| @@ -831,7 +824,7 @@ void RasterizerOpenGL::SetupDrawTextures(std::size_t stage_index, const Shader& | ||||
|     MICROPROFILE_SCOPE(OpenGL_Texture); | ||||
|     const auto& maxwell3d = system.GPU().Maxwell3D(); | ||||
|     u32 binding = device.GetBaseBindings(stage_index).sampler; | ||||
|     for (const auto& entry : shader->GetShaderEntries().samplers) { | ||||
|     for (const auto& entry : shader->GetEntries().samplers) { | ||||
|         const auto shader_type = static_cast<ShaderType>(stage_index); | ||||
|         for (std::size_t i = 0; i < entry.Size(); ++i) { | ||||
|             const auto texture = GetTextureInfo(maxwell3d, entry, shader_type, i); | ||||
| @@ -844,7 +837,7 @@ void RasterizerOpenGL::SetupComputeTextures(const Shader& kernel) { | ||||
|     MICROPROFILE_SCOPE(OpenGL_Texture); | ||||
|     const auto& compute = system.GPU().KeplerCompute(); | ||||
|     u32 binding = 0; | ||||
|     for (const auto& entry : kernel->GetShaderEntries().samplers) { | ||||
|     for (const auto& entry : kernel->GetEntries().samplers) { | ||||
|         for (std::size_t i = 0; i < entry.Size(); ++i) { | ||||
|             const auto texture = GetTextureInfo(compute, entry, ShaderType::Compute, i); | ||||
|             SetupTexture(binding++, texture, entry); | ||||
| @@ -853,7 +846,7 @@ void RasterizerOpenGL::SetupComputeTextures(const Shader& kernel) { | ||||
| } | ||||
|  | ||||
| void RasterizerOpenGL::SetupTexture(u32 binding, const Tegra::Texture::FullTextureInfo& texture, | ||||
|                                     const GLShader::SamplerEntry& entry) { | ||||
|                                     const SamplerEntry& entry) { | ||||
|     const auto view = texture_cache.GetTextureSurface(texture.tic, entry); | ||||
|     if (!view) { | ||||
|         // Can occur when texture addr is null or its memory is unmapped/invalid | ||||
| @@ -876,7 +869,7 @@ void RasterizerOpenGL::SetupTexture(u32 binding, const Tegra::Texture::FullTextu | ||||
| void RasterizerOpenGL::SetupDrawImages(std::size_t stage_index, const Shader& shader) { | ||||
|     const auto& maxwell3d = system.GPU().Maxwell3D(); | ||||
|     u32 binding = device.GetBaseBindings(stage_index).image; | ||||
|     for (const auto& entry : shader->GetShaderEntries().images) { | ||||
|     for (const auto& entry : shader->GetEntries().images) { | ||||
|         const auto shader_type = static_cast<Tegra::Engines::ShaderType>(stage_index); | ||||
|         const auto tic = GetTextureInfo(maxwell3d, entry, shader_type).tic; | ||||
|         SetupImage(binding++, tic, entry); | ||||
| @@ -886,14 +879,14 @@ void RasterizerOpenGL::SetupDrawImages(std::size_t stage_index, const Shader& sh | ||||
| void RasterizerOpenGL::SetupComputeImages(const Shader& shader) { | ||||
|     const auto& compute = system.GPU().KeplerCompute(); | ||||
|     u32 binding = 0; | ||||
|     for (const auto& entry : shader->GetShaderEntries().images) { | ||||
|     for (const auto& entry : shader->GetEntries().images) { | ||||
|         const auto tic = GetTextureInfo(compute, entry, Tegra::Engines::ShaderType::Compute).tic; | ||||
|         SetupImage(binding++, tic, entry); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void RasterizerOpenGL::SetupImage(u32 binding, const Tegra::Texture::TICEntry& tic, | ||||
|                                   const GLShader::ImageEntry& entry) { | ||||
|                                   const ImageEntry& entry) { | ||||
|     const auto view = texture_cache.GetImageSurface(tic, entry); | ||||
|     if (!view) { | ||||
|         glBindImageTexture(binding, 0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R8); | ||||
|   | ||||
| @@ -98,7 +98,7 @@ private: | ||||
|  | ||||
|     /// Configures a constant buffer. | ||||
|     void SetupConstBuffer(u32 binding, const Tegra::Engines::ConstBufferInfo& buffer, | ||||
|                           const GLShader::ConstBufferEntry& entry); | ||||
|                           const ConstBufferEntry& entry); | ||||
|  | ||||
|     /// Configures the current global memory entries to use for the draw command. | ||||
|     void SetupDrawGlobalMemory(std::size_t stage_index, const Shader& shader); | ||||
| @@ -107,7 +107,7 @@ private: | ||||
|     void SetupComputeGlobalMemory(const Shader& kernel); | ||||
|  | ||||
|     /// Configures a constant buffer. | ||||
|     void SetupGlobalMemory(u32 binding, const GLShader::GlobalMemoryEntry& entry, GPUVAddr gpu_addr, | ||||
|     void SetupGlobalMemory(u32 binding, const GlobalMemoryEntry& entry, GPUVAddr gpu_addr, | ||||
|                            std::size_t size); | ||||
|  | ||||
|     /// Configures the current textures to use for the draw command. | ||||
| @@ -118,7 +118,7 @@ private: | ||||
|  | ||||
|     /// Configures a texture. | ||||
|     void SetupTexture(u32 binding, const Tegra::Texture::FullTextureInfo& texture, | ||||
|                       const GLShader::SamplerEntry& entry); | ||||
|                       const SamplerEntry& entry); | ||||
|  | ||||
|     /// Configures images in a graphics shader. | ||||
|     void SetupDrawImages(std::size_t stage_index, const Shader& shader); | ||||
| @@ -127,8 +127,7 @@ private: | ||||
|     void SetupComputeImages(const Shader& shader); | ||||
|  | ||||
|     /// Configures an image. | ||||
|     void SetupImage(u32 binding, const Tegra::Texture::TICEntry& tic, | ||||
|                     const GLShader::ImageEntry& entry); | ||||
|     void SetupImage(u32 binding, const Tegra::Texture::TICEntry& tic, const ImageEntry& entry); | ||||
|  | ||||
|     /// Syncs the viewport and depth range to match the guest state | ||||
|     void SyncViewport(); | ||||
|   | ||||
| @@ -2,12 +2,16 @@ | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include <atomic> | ||||
| #include <functional> | ||||
| #include <mutex> | ||||
| #include <optional> | ||||
| #include <string> | ||||
| #include <thread> | ||||
| #include <unordered_set> | ||||
|  | ||||
| #include <boost/functional/hash.hpp> | ||||
|  | ||||
| #include "common/alignment.h" | ||||
| #include "common/assert.h" | ||||
| #include "common/logging/log.h" | ||||
| @@ -24,13 +28,14 @@ | ||||
| #include "video_core/renderer_opengl/gl_shader_disk_cache.h" | ||||
| #include "video_core/renderer_opengl/gl_state_tracker.h" | ||||
| #include "video_core/renderer_opengl/utils.h" | ||||
| #include "video_core/shader/registry.h" | ||||
| #include "video_core/shader/shader_ir.h" | ||||
|  | ||||
| namespace OpenGL { | ||||
|  | ||||
| using Tegra::Engines::ShaderType; | ||||
| using VideoCommon::Shader::ConstBufferLocker; | ||||
| using VideoCommon::Shader::ProgramCode; | ||||
| using VideoCommon::Shader::Registry; | ||||
| using VideoCommon::Shader::ShaderIR; | ||||
|  | ||||
| namespace { | ||||
| @@ -56,7 +61,7 @@ constexpr bool IsSchedInstruction(std::size_t offset, std::size_t main_offset) { | ||||
| } | ||||
|  | ||||
| /// Calculates the size of a program stream | ||||
| std::size_t CalculateProgramSize(const GLShader::ProgramCode& program) { | ||||
| std::size_t CalculateProgramSize(const ProgramCode& program) { | ||||
|     constexpr std::size_t start_offset = 10; | ||||
|     // This is the encoded version of BRA that jumps to itself. All Nvidia | ||||
|     // shaders end with one. | ||||
| @@ -109,32 +114,9 @@ constexpr GLenum GetGLShaderType(ShaderType shader_type) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Describes primitive behavior on geometry shaders | ||||
| constexpr std::pair<const char*, u32> GetPrimitiveDescription(GLenum primitive_mode) { | ||||
|     switch (primitive_mode) { | ||||
|     case GL_POINTS: | ||||
|         return {"points", 1}; | ||||
|     case GL_LINES: | ||||
|     case GL_LINE_STRIP: | ||||
|         return {"lines", 2}; | ||||
|     case GL_LINES_ADJACENCY: | ||||
|     case GL_LINE_STRIP_ADJACENCY: | ||||
|         return {"lines_adjacency", 4}; | ||||
|     case GL_TRIANGLES: | ||||
|     case GL_TRIANGLE_STRIP: | ||||
|     case GL_TRIANGLE_FAN: | ||||
|         return {"triangles", 3}; | ||||
|     case GL_TRIANGLES_ADJACENCY: | ||||
|     case GL_TRIANGLE_STRIP_ADJACENCY: | ||||
|         return {"triangles_adjacency", 6}; | ||||
|     default: | ||||
|         return {"points", 1}; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Hashes one (or two) program streams | ||||
| u64 GetUniqueIdentifier(ShaderType shader_type, bool is_a, const ProgramCode& code, | ||||
|                         const ProgramCode& code_b) { | ||||
|                         const ProgramCode& code_b = {}) { | ||||
|     u64 unique_identifier = boost::hash_value(code); | ||||
|     if (is_a) { | ||||
|         // VertexA programs include two programs | ||||
| @@ -143,24 +125,6 @@ u64 GetUniqueIdentifier(ShaderType shader_type, bool is_a, const ProgramCode& co | ||||
|     return unique_identifier; | ||||
| } | ||||
|  | ||||
| /// Creates an unspecialized program from code streams | ||||
| std::string GenerateGLSL(const Device& device, ShaderType shader_type, const ShaderIR& ir, | ||||
|                          const std::optional<ShaderIR>& ir_b) { | ||||
|     switch (shader_type) { | ||||
|     case ShaderType::Vertex: | ||||
|         return GLShader::GenerateVertexShader(device, ir, ir_b ? &*ir_b : nullptr); | ||||
|     case ShaderType::Geometry: | ||||
|         return GLShader::GenerateGeometryShader(device, ir); | ||||
|     case ShaderType::Fragment: | ||||
|         return GLShader::GenerateFragmentShader(device, ir); | ||||
|     case ShaderType::Compute: | ||||
|         return GLShader::GenerateComputeShader(device, ir); | ||||
|     default: | ||||
|         UNIMPLEMENTED_MSG("Unimplemented shader_type={}", static_cast<u32>(shader_type)); | ||||
|         return {}; | ||||
|     } | ||||
| } | ||||
|  | ||||
| constexpr const char* GetShaderTypeName(ShaderType shader_type) { | ||||
|     switch (shader_type) { | ||||
|     case ShaderType::Vertex: | ||||
| @@ -196,102 +160,38 @@ constexpr ShaderType GetShaderType(Maxwell::ShaderProgram program_type) { | ||||
|     return {}; | ||||
| } | ||||
|  | ||||
| std::string GetShaderId(u64 unique_identifier, ShaderType shader_type) { | ||||
| std::string MakeShaderID(u64 unique_identifier, ShaderType shader_type) { | ||||
|     return fmt::format("{}{:016X}", GetShaderTypeName(shader_type), unique_identifier); | ||||
| } | ||||
|  | ||||
| Tegra::Engines::ConstBufferEngineInterface& GetConstBufferEngineInterface(Core::System& system, | ||||
|                                                                           ShaderType shader_type) { | ||||
|     if (shader_type == ShaderType::Compute) { | ||||
|         return system.GPU().KeplerCompute(); | ||||
|     } else { | ||||
|         return system.GPU().Maxwell3D(); | ||||
| std::shared_ptr<Registry> MakeRegistry(const ShaderDiskCacheEntry& entry) { | ||||
|     const VideoCore::GuestDriverProfile guest_profile{entry.texture_handler_size}; | ||||
|     const VideoCommon::Shader::SerializedRegistryInfo info{guest_profile, entry.bound_buffer, | ||||
|                                                            entry.graphics_info, entry.compute_info}; | ||||
|     const auto registry = std::make_shared<Registry>(entry.type, info); | ||||
|     for (const auto& [address, value] : entry.keys) { | ||||
|         const auto [buffer, offset] = address; | ||||
|         registry->InsertKey(buffer, offset, value); | ||||
|     } | ||||
| } | ||||
|  | ||||
| std::unique_ptr<ConstBufferLocker> MakeLocker(Core::System& system, ShaderType shader_type) { | ||||
|     return std::make_unique<ConstBufferLocker>(shader_type, | ||||
|                                                GetConstBufferEngineInterface(system, shader_type)); | ||||
| } | ||||
|  | ||||
| void FillLocker(ConstBufferLocker& locker, const ShaderDiskCacheUsage& usage) { | ||||
|     locker.SetBoundBuffer(usage.bound_buffer); | ||||
|     for (const auto& key : usage.keys) { | ||||
|         const auto [buffer, offset] = key.first; | ||||
|         locker.InsertKey(buffer, offset, key.second); | ||||
|     for (const auto& [offset, sampler] : entry.bound_samplers) { | ||||
|         registry->InsertBoundSampler(offset, sampler); | ||||
|     } | ||||
|     for (const auto& [offset, sampler] : usage.bound_samplers) { | ||||
|         locker.InsertBoundSampler(offset, sampler); | ||||
|     } | ||||
|     for (const auto& [key, sampler] : usage.bindless_samplers) { | ||||
|     for (const auto& [key, sampler] : entry.bindless_samplers) { | ||||
|         const auto [buffer, offset] = key; | ||||
|         locker.InsertBindlessSampler(buffer, offset, sampler); | ||||
|         registry->InsertBindlessSampler(buffer, offset, sampler); | ||||
|     } | ||||
|     return registry; | ||||
| } | ||||
|  | ||||
| CachedProgram BuildShader(const Device& device, u64 unique_identifier, ShaderType shader_type, | ||||
|                           const ProgramCode& code, const ProgramCode& code_b, | ||||
|                           ConstBufferLocker& locker, const ProgramVariant& variant, | ||||
|                           bool hint_retrievable = false) { | ||||
|     LOG_INFO(Render_OpenGL, "called. {}", GetShaderId(unique_identifier, shader_type)); | ||||
|  | ||||
|     const bool is_compute = shader_type == ShaderType::Compute; | ||||
|     const u32 main_offset = is_compute ? KERNEL_MAIN_OFFSET : STAGE_MAIN_OFFSET; | ||||
|     const ShaderIR ir(code, main_offset, COMPILER_SETTINGS, locker); | ||||
|     std::optional<ShaderIR> ir_b; | ||||
|     if (!code_b.empty()) { | ||||
|         ir_b.emplace(code_b, main_offset, COMPILER_SETTINGS, locker); | ||||
|     } | ||||
|  | ||||
|     std::string source = fmt::format(R"(// {} | ||||
| #version 430 core | ||||
| #extension GL_ARB_separate_shader_objects : enable | ||||
| )", | ||||
|                                      GetShaderId(unique_identifier, shader_type)); | ||||
|     if (device.HasShaderBallot()) { | ||||
|         source += "#extension GL_ARB_shader_ballot : require\n"; | ||||
|     } | ||||
|     if (device.HasVertexViewportLayer()) { | ||||
|         source += "#extension GL_ARB_shader_viewport_layer_array : require\n"; | ||||
|     } | ||||
|     if (device.HasImageLoadFormatted()) { | ||||
|         source += "#extension GL_EXT_shader_image_load_formatted : require\n"; | ||||
|     } | ||||
|     if (device.HasWarpIntrinsics()) { | ||||
|         source += "#extension GL_NV_gpu_shader5 : require\n" | ||||
|                   "#extension GL_NV_shader_thread_group : require\n" | ||||
|                   "#extension GL_NV_shader_thread_shuffle : require\n"; | ||||
|     } | ||||
|     // This pragma stops Nvidia's driver from over optimizing math (probably using fp16 operations) | ||||
|     // on places where we don't want to. | ||||
|     // Thanks to Ryujinx for finding this workaround. | ||||
|     source += "#pragma optionNV(fastmath off)\n"; | ||||
|  | ||||
|     if (shader_type == ShaderType::Geometry) { | ||||
|         const auto [glsl_topology, max_vertices] = GetPrimitiveDescription(variant.primitive_mode); | ||||
|         source += fmt::format("#define MAX_VERTEX_INPUT {}\n", max_vertices); | ||||
|         source += fmt::format("layout ({}) in;\n", glsl_topology); | ||||
|     } | ||||
|     if (shader_type == ShaderType::Compute) { | ||||
|         if (variant.local_memory_size > 0) { | ||||
|             source += fmt::format("#define LOCAL_MEMORY_SIZE {}\n", | ||||
|                                   Common::AlignUp(variant.local_memory_size, 4) / 4); | ||||
|         } | ||||
|         source += | ||||
|             fmt::format("layout (local_size_x = {}, local_size_y = {}, local_size_z = {}) in;\n", | ||||
|                         variant.block_x, variant.block_y, variant.block_z); | ||||
|  | ||||
|         if (variant.shared_memory_size > 0) { | ||||
|             // shared_memory_size is described in number of words | ||||
|             source += fmt::format("shared uint smem[{}];\n", variant.shared_memory_size); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     source += '\n'; | ||||
|     source += GenerateGLSL(device, shader_type, ir, ir_b); | ||||
| std::shared_ptr<OGLProgram> BuildShader(const Device& device, ShaderType shader_type, | ||||
|                                         u64 unique_identifier, const ShaderIR& ir, | ||||
|                                         const Registry& registry, bool hint_retrievable = false) { | ||||
|     const std::string shader_id = MakeShaderID(unique_identifier, shader_type); | ||||
|     LOG_INFO(Render_OpenGL, "{}", shader_id); | ||||
|  | ||||
|     const std::string glsl = DecompileShader(device, ir, registry, shader_type, shader_id); | ||||
|     OGLShader shader; | ||||
|     shader.Create(source.c_str(), GetGLShaderType(shader_type)); | ||||
|     shader.Create(glsl.c_str(), GetGLShaderType(shader_type)); | ||||
|  | ||||
|     auto program = std::make_shared<OGLProgram>(); | ||||
|     program->Create(true, hint_retrievable, shader.handle); | ||||
| @@ -299,7 +199,7 @@ CachedProgram BuildShader(const Device& device, u64 unique_identifier, ShaderTyp | ||||
| } | ||||
|  | ||||
| std::unordered_set<GLenum> GetSupportedFormats() { | ||||
|     GLint num_formats{}; | ||||
|     GLint num_formats; | ||||
|     glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &num_formats); | ||||
|  | ||||
|     std::vector<GLint> formats(num_formats); | ||||
| @@ -314,115 +214,82 @@ std::unordered_set<GLenum> GetSupportedFormats() { | ||||
|  | ||||
| } // Anonymous namespace | ||||
|  | ||||
| CachedShader::CachedShader(const ShaderParameters& params, ShaderType shader_type, | ||||
|                            GLShader::ShaderEntries entries, ProgramCode code, ProgramCode code_b) | ||||
|     : RasterizerCacheObject{params.host_ptr}, system{params.system}, | ||||
|       disk_cache{params.disk_cache}, device{params.device}, cpu_addr{params.cpu_addr}, | ||||
|       unique_identifier{params.unique_identifier}, shader_type{shader_type}, | ||||
|       entries{std::move(entries)}, code{std::move(code)}, code_b{std::move(code_b)} { | ||||
|     if (!params.precompiled_variants) { | ||||
|         return; | ||||
|     } | ||||
|     for (const auto& pair : *params.precompiled_variants) { | ||||
|         auto locker = MakeLocker(system, shader_type); | ||||
|         const auto& usage = pair->first; | ||||
|         FillLocker(*locker, usage); | ||||
| CachedShader::CachedShader(const u8* host_ptr, VAddr cpu_addr, std::size_t size_in_bytes, | ||||
|                            std::shared_ptr<VideoCommon::Shader::Registry> registry, | ||||
|                            ShaderEntries entries, std::shared_ptr<OGLProgram> program) | ||||
|     : RasterizerCacheObject{host_ptr}, registry{std::move(registry)}, entries{std::move(entries)}, | ||||
|       cpu_addr{cpu_addr}, size_in_bytes{size_in_bytes}, program{std::move(program)} {} | ||||
|  | ||||
|         std::unique_ptr<LockerVariant>* locker_variant = nullptr; | ||||
|         const auto it = | ||||
|             std::find_if(locker_variants.begin(), locker_variants.end(), [&](const auto& variant) { | ||||
|                 return variant->locker->HasEqualKeys(*locker); | ||||
|             }); | ||||
|         if (it == locker_variants.end()) { | ||||
|             locker_variant = &locker_variants.emplace_back(); | ||||
|             *locker_variant = std::make_unique<LockerVariant>(); | ||||
|             locker_variant->get()->locker = std::move(locker); | ||||
|         } else { | ||||
|             locker_variant = &*it; | ||||
|         } | ||||
|         locker_variant->get()->programs.emplace(usage.variant, pair->second); | ||||
|     } | ||||
| CachedShader::~CachedShader() = default; | ||||
|  | ||||
| GLuint CachedShader::GetHandle() const { | ||||
|     DEBUG_ASSERT(registry->IsConsistent()); | ||||
|     return program->handle; | ||||
| } | ||||
|  | ||||
| Shader CachedShader::CreateStageFromMemory(const ShaderParameters& params, | ||||
|                                            Maxwell::ShaderProgram program_type, ProgramCode code, | ||||
|                                            ProgramCode code_b) { | ||||
|     const auto shader_type = GetShaderType(program_type); | ||||
|     params.disk_cache.SaveRaw( | ||||
|         ShaderDiskCacheRaw(params.unique_identifier, shader_type, code, code_b)); | ||||
|     const std::size_t size_in_bytes = code.size() * sizeof(u64); | ||||
|  | ||||
|     ConstBufferLocker locker(shader_type, params.system.GPU().Maxwell3D()); | ||||
|     const ShaderIR ir(code, STAGE_MAIN_OFFSET, COMPILER_SETTINGS, locker); | ||||
|     auto registry = std::make_shared<Registry>(shader_type, params.system.GPU().Maxwell3D()); | ||||
|     const ShaderIR ir(code, STAGE_MAIN_OFFSET, COMPILER_SETTINGS, *registry); | ||||
|     // TODO(Rodrigo): Handle VertexA shaders | ||||
|     // std::optional<ShaderIR> ir_b; | ||||
|     // if (!code_b.empty()) { | ||||
|     //     ir_b.emplace(code_b, STAGE_MAIN_OFFSET); | ||||
|     // } | ||||
|     return std::shared_ptr<CachedShader>(new CachedShader( | ||||
|         params, shader_type, GLShader::GetEntries(ir), std::move(code), std::move(code_b))); | ||||
|     auto program = BuildShader(params.device, shader_type, params.unique_identifier, ir, *registry); | ||||
|  | ||||
|     ShaderDiskCacheEntry entry; | ||||
|     entry.type = shader_type; | ||||
|     entry.code = std::move(code); | ||||
|     entry.code_b = std::move(code_b); | ||||
|     entry.unique_identifier = params.unique_identifier; | ||||
|     entry.bound_buffer = registry->GetBoundBuffer(); | ||||
|     entry.graphics_info = registry->GetGraphicsInfo(); | ||||
|     entry.keys = registry->GetKeys(); | ||||
|     entry.bound_samplers = registry->GetBoundSamplers(); | ||||
|     entry.bindless_samplers = registry->GetBindlessSamplers(); | ||||
|     params.disk_cache.SaveEntry(std::move(entry)); | ||||
|  | ||||
|     return std::shared_ptr<CachedShader>(new CachedShader(params.host_ptr, params.cpu_addr, | ||||
|                                                           size_in_bytes, std::move(registry), | ||||
|                                                           MakeEntries(ir), std::move(program))); | ||||
| } | ||||
|  | ||||
| Shader CachedShader::CreateKernelFromMemory(const ShaderParameters& params, ProgramCode code) { | ||||
|     params.disk_cache.SaveRaw( | ||||
|         ShaderDiskCacheRaw(params.unique_identifier, ShaderType::Compute, code)); | ||||
|     const std::size_t size_in_bytes = code.size() * sizeof(u64); | ||||
|  | ||||
|     ConstBufferLocker locker(Tegra::Engines::ShaderType::Compute, | ||||
|                              params.system.GPU().KeplerCompute()); | ||||
|     const ShaderIR ir(code, KERNEL_MAIN_OFFSET, COMPILER_SETTINGS, locker); | ||||
|     return std::shared_ptr<CachedShader>(new CachedShader( | ||||
|         params, ShaderType::Compute, GLShader::GetEntries(ir), std::move(code), {})); | ||||
|     auto& engine = params.system.GPU().KeplerCompute(); | ||||
|     auto registry = std::make_shared<Registry>(ShaderType::Compute, engine); | ||||
|     const ShaderIR ir(code, KERNEL_MAIN_OFFSET, COMPILER_SETTINGS, *registry); | ||||
|     const u64 uid = params.unique_identifier; | ||||
|     auto program = BuildShader(params.device, ShaderType::Compute, uid, ir, *registry); | ||||
|  | ||||
|     ShaderDiskCacheEntry entry; | ||||
|     entry.type = ShaderType::Compute; | ||||
|     entry.code = std::move(code); | ||||
|     entry.unique_identifier = uid; | ||||
|     entry.bound_buffer = registry->GetBoundBuffer(); | ||||
|     entry.compute_info = registry->GetComputeInfo(); | ||||
|     entry.keys = registry->GetKeys(); | ||||
|     entry.bound_samplers = registry->GetBoundSamplers(); | ||||
|     entry.bindless_samplers = registry->GetBindlessSamplers(); | ||||
|     params.disk_cache.SaveEntry(std::move(entry)); | ||||
|  | ||||
|     return std::shared_ptr<CachedShader>(new CachedShader(params.host_ptr, params.cpu_addr, | ||||
|                                                           size_in_bytes, std::move(registry), | ||||
|                                                           MakeEntries(ir), std::move(program))); | ||||
| } | ||||
|  | ||||
| Shader CachedShader::CreateFromCache(const ShaderParameters& params, | ||||
|                                      const UnspecializedShader& unspecialized) { | ||||
|     return std::shared_ptr<CachedShader>(new CachedShader(params, unspecialized.type, | ||||
|                                                           unspecialized.entries, unspecialized.code, | ||||
|                                                           unspecialized.code_b)); | ||||
| } | ||||
|  | ||||
| GLuint CachedShader::GetHandle(const ProgramVariant& variant) { | ||||
|     EnsureValidLockerVariant(); | ||||
|  | ||||
|     const auto [entry, is_cache_miss] = curr_locker_variant->programs.try_emplace(variant); | ||||
|     auto& program = entry->second; | ||||
|     if (!is_cache_miss) { | ||||
|         return program->handle; | ||||
|     } | ||||
|  | ||||
|     program = BuildShader(device, unique_identifier, shader_type, code, code_b, | ||||
|                           *curr_locker_variant->locker, variant); | ||||
|     disk_cache.SaveUsage(GetUsage(variant, *curr_locker_variant->locker)); | ||||
|  | ||||
|     LabelGLObject(GL_PROGRAM, program->handle, cpu_addr); | ||||
|     return program->handle; | ||||
| } | ||||
|  | ||||
| bool CachedShader::EnsureValidLockerVariant() { | ||||
|     const auto previous_variant = curr_locker_variant; | ||||
|     if (curr_locker_variant && !curr_locker_variant->locker->IsConsistent()) { | ||||
|         curr_locker_variant = nullptr; | ||||
|     } | ||||
|     if (!curr_locker_variant) { | ||||
|         for (auto& variant : locker_variants) { | ||||
|             if (variant->locker->IsConsistent()) { | ||||
|                 curr_locker_variant = variant.get(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     if (!curr_locker_variant) { | ||||
|         auto& new_variant = locker_variants.emplace_back(); | ||||
|         new_variant = std::make_unique<LockerVariant>(); | ||||
|         new_variant->locker = MakeLocker(system, shader_type); | ||||
|         curr_locker_variant = new_variant.get(); | ||||
|     } | ||||
|     return previous_variant == curr_locker_variant; | ||||
| } | ||||
|  | ||||
| ShaderDiskCacheUsage CachedShader::GetUsage(const ProgramVariant& variant, | ||||
|                                             const ConstBufferLocker& locker) const { | ||||
|     return ShaderDiskCacheUsage{unique_identifier,         variant, | ||||
|                                 locker.GetBoundBuffer(),   locker.GetKeys(), | ||||
|                                 locker.GetBoundSamplers(), locker.GetBindlessSamplers()}; | ||||
|                                      const PrecompiledShader& precompiled_shader, | ||||
|                                      std::size_t size_in_bytes) { | ||||
|     return std::shared_ptr<CachedShader>(new CachedShader( | ||||
|         params.host_ptr, params.cpu_addr, size_in_bytes, precompiled_shader.registry, | ||||
|         precompiled_shader.entries, precompiled_shader.program)); | ||||
| } | ||||
|  | ||||
| ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer, Core::System& system, | ||||
| @@ -432,16 +299,12 @@ ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer, Core::System& | ||||
|  | ||||
| void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading, | ||||
|                                       const VideoCore::DiskResourceLoadCallback& callback) { | ||||
|     const auto transferable = disk_cache.LoadTransferable(); | ||||
|     const std::optional transferable = disk_cache.LoadTransferable(); | ||||
|     if (!transferable) { | ||||
|         return; | ||||
|     } | ||||
|     const auto [raws, shader_usages] = *transferable; | ||||
|     if (!GenerateUnspecializedShaders(stop_loading, callback, raws) || stop_loading) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const auto dumps = disk_cache.LoadPrecompiled(); | ||||
|     const std::vector gl_cache = disk_cache.LoadPrecompiled(); | ||||
|     const auto supported_formats = GetSupportedFormats(); | ||||
|  | ||||
|     // Track if precompiled cache was altered during loading to know if we have to | ||||
| @@ -450,77 +313,82 @@ void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading, | ||||
|  | ||||
|     // Inform the frontend about shader build initialization | ||||
|     if (callback) { | ||||
|         callback(VideoCore::LoadCallbackStage::Build, 0, shader_usages.size()); | ||||
|         callback(VideoCore::LoadCallbackStage::Build, 0, transferable->size()); | ||||
|     } | ||||
|  | ||||
|     std::mutex mutex; | ||||
|     std::size_t built_shaders = 0; // It doesn't have be atomic since it's used behind a mutex | ||||
|     std::atomic_bool compilation_failed = false; | ||||
|     std::atomic_bool gl_cache_failed = false; | ||||
|  | ||||
|     const auto Worker = [&](Core::Frontend::GraphicsContext* context, std::size_t begin, | ||||
|                             std::size_t end, const std::vector<ShaderDiskCacheUsage>& shader_usages, | ||||
|                             const ShaderDumpsMap& dumps) { | ||||
|     const auto find_precompiled = [&gl_cache](u64 id) { | ||||
|         return std::find_if(gl_cache.begin(), gl_cache.end(), | ||||
|                             [id](const auto& entry) { return entry.unique_identifier == id; }); | ||||
|     }; | ||||
|  | ||||
|     const auto worker = [&](Core::Frontend::GraphicsContext* context, std::size_t begin, | ||||
|                             std::size_t end) { | ||||
|         context->MakeCurrent(); | ||||
|         SCOPE_EXIT({ return context->DoneCurrent(); }); | ||||
|  | ||||
|         for (std::size_t i = begin; i < end; ++i) { | ||||
|             if (stop_loading || compilation_failed) { | ||||
|             if (stop_loading) { | ||||
|                 return; | ||||
|             } | ||||
|             const auto& usage{shader_usages[i]}; | ||||
|             const auto& unspecialized{unspecialized_shaders.at(usage.unique_identifier)}; | ||||
|             const auto dump{dumps.find(usage)}; | ||||
|             const auto& entry = (*transferable)[i]; | ||||
|             const u64 uid = entry.unique_identifier; | ||||
|             const auto it = find_precompiled(uid); | ||||
|             const auto precompiled_entry = it != gl_cache.end() ? &*it : nullptr; | ||||
|  | ||||
|             CachedProgram shader; | ||||
|             if (dump != dumps.end()) { | ||||
|                 // If the shader is dumped, attempt to load it with | ||||
|                 shader = GeneratePrecompiledProgram(dump->second, supported_formats); | ||||
|                 if (!shader) { | ||||
|                     compilation_failed = true; | ||||
|                     return; | ||||
|             const bool is_compute = entry.type == ShaderType::Compute; | ||||
|             const u32 main_offset = is_compute ? KERNEL_MAIN_OFFSET : STAGE_MAIN_OFFSET; | ||||
|             auto registry = MakeRegistry(entry); | ||||
|             const ShaderIR ir(entry.code, main_offset, COMPILER_SETTINGS, *registry); | ||||
|  | ||||
|             std::shared_ptr<OGLProgram> program; | ||||
|             if (precompiled_entry) { | ||||
|                 // If the shader is precompiled, attempt to load it with | ||||
|                 program = GeneratePrecompiledProgram(entry, *precompiled_entry, supported_formats); | ||||
|                 if (!program) { | ||||
|                     gl_cache_failed = true; | ||||
|                 } | ||||
|             } | ||||
|             if (!shader) { | ||||
|                 auto locker{MakeLocker(system, unspecialized.type)}; | ||||
|                 FillLocker(*locker, usage); | ||||
|  | ||||
|                 shader = BuildShader(device, usage.unique_identifier, unspecialized.type, | ||||
|                                      unspecialized.code, unspecialized.code_b, *locker, | ||||
|                                      usage.variant, true); | ||||
|             if (!program) { | ||||
|                 // Otherwise compile it from GLSL | ||||
|                 program = BuildShader(device, entry.type, uid, ir, *registry, true); | ||||
|             } | ||||
|  | ||||
|             PrecompiledShader shader; | ||||
|             shader.program = std::move(program); | ||||
|             shader.registry = std::move(registry); | ||||
|             shader.entries = MakeEntries(ir); | ||||
|  | ||||
|             std::scoped_lock lock{mutex}; | ||||
|             if (callback) { | ||||
|                 callback(VideoCore::LoadCallbackStage::Build, ++built_shaders, | ||||
|                          shader_usages.size()); | ||||
|                          transferable->size()); | ||||
|             } | ||||
|  | ||||
|             precompiled_programs.emplace(usage, std::move(shader)); | ||||
|  | ||||
|             // TODO(Rodrigo): Is there a better way to do this? | ||||
|             precompiled_variants[usage.unique_identifier].push_back( | ||||
|                 precompiled_programs.find(usage)); | ||||
|             runtime_cache.emplace(entry.unique_identifier, std::move(shader)); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     const auto num_workers{static_cast<std::size_t>(std::thread::hardware_concurrency() + 1ULL)}; | ||||
|     const std::size_t bucket_size{shader_usages.size() / num_workers}; | ||||
|     const std::size_t bucket_size{transferable->size() / num_workers}; | ||||
|     std::vector<std::unique_ptr<Core::Frontend::GraphicsContext>> contexts(num_workers); | ||||
|     std::vector<std::thread> threads(num_workers); | ||||
|     for (std::size_t i = 0; i < num_workers; ++i) { | ||||
|         const bool is_last_worker = i + 1 == num_workers; | ||||
|         const std::size_t start{bucket_size * i}; | ||||
|         const std::size_t end{is_last_worker ? shader_usages.size() : start + bucket_size}; | ||||
|         const std::size_t end{is_last_worker ? transferable->size() : start + bucket_size}; | ||||
|  | ||||
|         // On some platforms the shared context has to be created from the GUI thread | ||||
|         contexts[i] = emu_window.CreateSharedContext(); | ||||
|         threads[i] = std::thread(Worker, contexts[i].get(), start, end, shader_usages, dumps); | ||||
|         threads[i] = std::thread(worker, contexts[i].get(), start, end); | ||||
|     } | ||||
|     for (auto& thread : threads) { | ||||
|         thread.join(); | ||||
|     } | ||||
|  | ||||
|     if (compilation_failed) { | ||||
|     if (gl_cache_failed) { | ||||
|         // Invalidate the precompiled cache if a shader dumped shader was rejected | ||||
|         disk_cache.InvalidatePrecompiled(); | ||||
|         precompiled_cache_altered = true; | ||||
| @@ -533,11 +401,12 @@ void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading, | ||||
|     // TODO(Rodrigo): Do state tracking for transferable shaders and do a dummy draw | ||||
|     // before precompiling them | ||||
|  | ||||
|     for (std::size_t i = 0; i < shader_usages.size(); ++i) { | ||||
|         const auto& usage{shader_usages[i]}; | ||||
|         if (dumps.find(usage) == dumps.end()) { | ||||
|             const auto& program{precompiled_programs.at(usage)}; | ||||
|             disk_cache.SaveDump(usage, program->handle); | ||||
|     for (std::size_t i = 0; i < transferable->size(); ++i) { | ||||
|         const u64 id = (*transferable)[i].unique_identifier; | ||||
|         const auto it = find_precompiled(id); | ||||
|         if (it == gl_cache.end()) { | ||||
|             const GLuint program = runtime_cache.at(id).program->handle; | ||||
|             disk_cache.SavePrecompiled(id, program); | ||||
|             precompiled_cache_altered = true; | ||||
|         } | ||||
|     } | ||||
| @@ -547,80 +416,29 @@ void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading, | ||||
|     } | ||||
| } | ||||
|  | ||||
| const PrecompiledVariants* ShaderCacheOpenGL::GetPrecompiledVariants(u64 unique_identifier) const { | ||||
|     const auto it = precompiled_variants.find(unique_identifier); | ||||
|     return it == precompiled_variants.end() ? nullptr : &it->second; | ||||
| } | ||||
|  | ||||
| CachedProgram ShaderCacheOpenGL::GeneratePrecompiledProgram( | ||||
|     const ShaderDiskCacheDump& dump, const std::unordered_set<GLenum>& supported_formats) { | ||||
|     if (supported_formats.find(dump.binary_format) == supported_formats.end()) { | ||||
|         LOG_INFO(Render_OpenGL, "Precompiled cache entry with unsupported format - removing"); | ||||
| std::shared_ptr<OGLProgram> ShaderCacheOpenGL::GeneratePrecompiledProgram( | ||||
|     const ShaderDiskCacheEntry& entry, const ShaderDiskCachePrecompiled& precompiled_entry, | ||||
|     const std::unordered_set<GLenum>& supported_formats) { | ||||
|     if (supported_formats.find(precompiled_entry.binary_format) == supported_formats.end()) { | ||||
|         LOG_INFO(Render_OpenGL, "Precompiled cache entry with unsupported format, removing"); | ||||
|         return {}; | ||||
|     } | ||||
|  | ||||
|     CachedProgram shader = std::make_shared<OGLProgram>(); | ||||
|     shader->handle = glCreateProgram(); | ||||
|     glProgramParameteri(shader->handle, GL_PROGRAM_SEPARABLE, GL_TRUE); | ||||
|     glProgramBinary(shader->handle, dump.binary_format, dump.binary.data(), | ||||
|                     static_cast<GLsizei>(dump.binary.size())); | ||||
|     auto program = std::make_shared<OGLProgram>(); | ||||
|     program->handle = glCreateProgram(); | ||||
|     glProgramParameteri(program->handle, GL_PROGRAM_SEPARABLE, GL_TRUE); | ||||
|     glProgramBinary(program->handle, precompiled_entry.binary_format, | ||||
|                     precompiled_entry.binary.data(), | ||||
|                     static_cast<GLsizei>(precompiled_entry.binary.size())); | ||||
|  | ||||
|     GLint link_status{}; | ||||
|     glGetProgramiv(shader->handle, GL_LINK_STATUS, &link_status); | ||||
|     GLint link_status; | ||||
|     glGetProgramiv(program->handle, GL_LINK_STATUS, &link_status); | ||||
|     if (link_status == GL_FALSE) { | ||||
|         LOG_INFO(Render_OpenGL, "Precompiled cache rejected by the driver - removing"); | ||||
|         LOG_INFO(Render_OpenGL, "Precompiled cache rejected by the driver, removing"); | ||||
|         return {}; | ||||
|     } | ||||
|  | ||||
|     return shader; | ||||
| } | ||||
|  | ||||
| bool ShaderCacheOpenGL::GenerateUnspecializedShaders( | ||||
|     const std::atomic_bool& stop_loading, const VideoCore::DiskResourceLoadCallback& callback, | ||||
|     const std::vector<ShaderDiskCacheRaw>& raws) { | ||||
|     if (callback) { | ||||
|         callback(VideoCore::LoadCallbackStage::Decompile, 0, raws.size()); | ||||
|     } | ||||
|  | ||||
|     for (std::size_t i = 0; i < raws.size(); ++i) { | ||||
|         if (stop_loading) { | ||||
|             return false; | ||||
|         } | ||||
|         const auto& raw{raws[i]}; | ||||
|         const u64 unique_identifier{raw.GetUniqueIdentifier()}; | ||||
|         const u64 calculated_hash{ | ||||
|             GetUniqueIdentifier(raw.GetType(), raw.HasProgramA(), raw.GetCode(), raw.GetCodeB())}; | ||||
|         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 false; | ||||
|         } | ||||
|  | ||||
|         const u32 main_offset = | ||||
|             raw.GetType() == ShaderType::Compute ? KERNEL_MAIN_OFFSET : STAGE_MAIN_OFFSET; | ||||
|         ConstBufferLocker locker(raw.GetType()); | ||||
|         const ShaderIR ir(raw.GetCode(), main_offset, COMPILER_SETTINGS, locker); | ||||
|         // TODO(Rodrigo): Handle VertexA shaders | ||||
|         // std::optional<ShaderIR> ir_b; | ||||
|         // if (raw.HasProgramA()) { | ||||
|         //     ir_b.emplace(raw.GetProgramCodeB(), main_offset); | ||||
|         // } | ||||
|  | ||||
|         UnspecializedShader unspecialized; | ||||
|         unspecialized.entries = GLShader::GetEntries(ir); | ||||
|         unspecialized.type = raw.GetType(); | ||||
|         unspecialized.code = raw.GetCode(); | ||||
|         unspecialized.code_b = raw.GetCodeB(); | ||||
|         unspecialized_shaders.emplace(raw.GetUniqueIdentifier(), unspecialized); | ||||
|  | ||||
|         if (callback) { | ||||
|             callback(VideoCore::LoadCallbackStage::Decompile, i, raws.size()); | ||||
|         } | ||||
|     } | ||||
|     return true; | ||||
|     return program; | ||||
| } | ||||
|  | ||||
| Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) { | ||||
| @@ -648,17 +466,17 @@ Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) { | ||||
|  | ||||
|     const auto unique_identifier = GetUniqueIdentifier( | ||||
|         GetShaderType(program), program == Maxwell::ShaderProgram::VertexA, code, code_b); | ||||
|     const auto precompiled_variants = GetPrecompiledVariants(unique_identifier); | ||||
|     const auto cpu_addr{*memory_manager.GpuToCpuAddress(address)}; | ||||
|     const ShaderParameters params{system,   disk_cache, precompiled_variants, device, | ||||
|     const ShaderParameters params{system,   disk_cache, device, | ||||
|                                   cpu_addr, host_ptr,   unique_identifier}; | ||||
|  | ||||
|     const auto found = unspecialized_shaders.find(unique_identifier); | ||||
|     if (found == unspecialized_shaders.end()) { | ||||
|     const auto found = runtime_cache.find(unique_identifier); | ||||
|     if (found == runtime_cache.end()) { | ||||
|         shader = CachedShader::CreateStageFromMemory(params, program, std::move(code), | ||||
|                                                      std::move(code_b)); | ||||
|     } else { | ||||
|         shader = CachedShader::CreateFromCache(params, found->second); | ||||
|         const std::size_t size_in_bytes = code.size() * sizeof(u64); | ||||
|         shader = CachedShader::CreateFromCache(params, found->second, size_in_bytes); | ||||
|     } | ||||
|     Register(shader); | ||||
|  | ||||
| @@ -673,19 +491,19 @@ Shader ShaderCacheOpenGL::GetComputeKernel(GPUVAddr code_addr) { | ||||
|         return kernel; | ||||
|     } | ||||
|  | ||||
|     // No kernel found - create a new one | ||||
|     // No kernel found, create a new one | ||||
|     auto code{GetShaderCode(memory_manager, code_addr, host_ptr)}; | ||||
|     const auto unique_identifier{GetUniqueIdentifier(ShaderType::Compute, false, code, {})}; | ||||
|     const auto precompiled_variants = GetPrecompiledVariants(unique_identifier); | ||||
|     const auto unique_identifier{GetUniqueIdentifier(ShaderType::Compute, false, code)}; | ||||
|     const auto cpu_addr{*memory_manager.GpuToCpuAddress(code_addr)}; | ||||
|     const ShaderParameters params{system,   disk_cache, precompiled_variants, device, | ||||
|     const ShaderParameters params{system,   disk_cache, device, | ||||
|                                   cpu_addr, host_ptr,   unique_identifier}; | ||||
|  | ||||
|     const auto found = unspecialized_shaders.find(unique_identifier); | ||||
|     if (found == unspecialized_shaders.end()) { | ||||
|     const auto found = runtime_cache.find(unique_identifier); | ||||
|     if (found == runtime_cache.end()) { | ||||
|         kernel = CachedShader::CreateKernelFromMemory(params, std::move(code)); | ||||
|     } else { | ||||
|         kernel = CachedShader::CreateFromCache(params, found->second); | ||||
|         const std::size_t size_in_bytes = code.size() * sizeof(u64); | ||||
|         kernel = CachedShader::CreateFromCache(params, found->second, size_in_bytes); | ||||
|     } | ||||
|  | ||||
|     Register(kernel); | ||||
|   | ||||
| @@ -22,7 +22,7 @@ | ||||
| #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/shader/const_buffer_locker.h" | ||||
| #include "video_core/shader/registry.h" | ||||
| #include "video_core/shader/shader_ir.h" | ||||
|  | ||||
| namespace Core { | ||||
| @@ -41,22 +41,17 @@ class RasterizerOpenGL; | ||||
| struct UnspecializedShader; | ||||
|  | ||||
| using Shader = std::shared_ptr<CachedShader>; | ||||
| using CachedProgram = std::shared_ptr<OGLProgram>; | ||||
| using Maxwell = Tegra::Engines::Maxwell3D::Regs; | ||||
| using PrecompiledPrograms = std::unordered_map<ShaderDiskCacheUsage, CachedProgram>; | ||||
| using PrecompiledVariants = std::vector<PrecompiledPrograms::iterator>; | ||||
|  | ||||
| struct UnspecializedShader { | ||||
|     GLShader::ShaderEntries entries; | ||||
|     Tegra::Engines::ShaderType type; | ||||
|     ProgramCode code; | ||||
|     ProgramCode code_b; | ||||
| struct PrecompiledShader { | ||||
|     std::shared_ptr<OGLProgram> program; | ||||
|     std::shared_ptr<VideoCommon::Shader::Registry> registry; | ||||
|     ShaderEntries entries; | ||||
| }; | ||||
|  | ||||
| struct ShaderParameters { | ||||
|     Core::System& system; | ||||
|     ShaderDiskCacheOpenGL& disk_cache; | ||||
|     const PrecompiledVariants* precompiled_variants; | ||||
|     const Device& device; | ||||
|     VAddr cpu_addr; | ||||
|     u8* host_ptr; | ||||
| @@ -65,61 +60,45 @@ struct ShaderParameters { | ||||
|  | ||||
| class CachedShader final : public RasterizerCacheObject { | ||||
| public: | ||||
|     ~CachedShader(); | ||||
|  | ||||
|     /// Gets the GL program handle for the shader | ||||
|     GLuint GetHandle() const; | ||||
|  | ||||
|     /// Returns the guest CPU address of the shader | ||||
|     VAddr GetCpuAddr() const override { | ||||
|         return cpu_addr; | ||||
|     } | ||||
|  | ||||
|     /// Returns the size in bytes of the shader | ||||
|     std::size_t GetSizeInBytes() const override { | ||||
|         return size_in_bytes; | ||||
|     } | ||||
|  | ||||
|     /// Gets the shader entries for the shader | ||||
|     const ShaderEntries& GetEntries() const { | ||||
|         return entries; | ||||
|     } | ||||
|  | ||||
|     static Shader CreateStageFromMemory(const ShaderParameters& params, | ||||
|                                         Maxwell::ShaderProgram program_type, | ||||
|                                         ProgramCode program_code, ProgramCode program_code_b); | ||||
|     static Shader CreateKernelFromMemory(const ShaderParameters& params, ProgramCode code); | ||||
|  | ||||
|     static Shader CreateFromCache(const ShaderParameters& params, | ||||
|                                   const UnspecializedShader& unspecialized); | ||||
|  | ||||
|     VAddr GetCpuAddr() const override { | ||||
|         return cpu_addr; | ||||
|     } | ||||
|  | ||||
|     std::size_t GetSizeInBytes() const override { | ||||
|         return code.size() * sizeof(u64); | ||||
|     } | ||||
|  | ||||
|     /// Gets the shader entries for the shader | ||||
|     const GLShader::ShaderEntries& GetShaderEntries() const { | ||||
|         return entries; | ||||
|     } | ||||
|  | ||||
|     /// Gets the GL program handle for the shader | ||||
|     GLuint GetHandle(const ProgramVariant& variant); | ||||
|                                   const PrecompiledShader& precompiled_shader, | ||||
|                                   std::size_t size_in_bytes); | ||||
|  | ||||
| private: | ||||
|     struct LockerVariant { | ||||
|         std::unique_ptr<VideoCommon::Shader::ConstBufferLocker> locker; | ||||
|         std::unordered_map<ProgramVariant, CachedProgram> programs; | ||||
|     }; | ||||
|     explicit CachedShader(const u8* host_ptr, VAddr cpu_addr, std::size_t size_in_bytes, | ||||
|                           std::shared_ptr<VideoCommon::Shader::Registry> registry, | ||||
|                           ShaderEntries entries, std::shared_ptr<OGLProgram> program); | ||||
|  | ||||
|     explicit CachedShader(const ShaderParameters& params, Tegra::Engines::ShaderType shader_type, | ||||
|                           GLShader::ShaderEntries entries, ProgramCode program_code, | ||||
|                           ProgramCode program_code_b); | ||||
|  | ||||
|     bool EnsureValidLockerVariant(); | ||||
|  | ||||
|     ShaderDiskCacheUsage GetUsage(const ProgramVariant& variant, | ||||
|                                   const VideoCommon::Shader::ConstBufferLocker& locker) const; | ||||
|  | ||||
|     Core::System& system; | ||||
|     ShaderDiskCacheOpenGL& disk_cache; | ||||
|     const Device& device; | ||||
|  | ||||
|     VAddr cpu_addr{}; | ||||
|  | ||||
|     u64 unique_identifier{}; | ||||
|     Tegra::Engines::ShaderType shader_type{}; | ||||
|  | ||||
|     GLShader::ShaderEntries entries; | ||||
|  | ||||
|     ProgramCode code; | ||||
|     ProgramCode code_b; | ||||
|  | ||||
|     LockerVariant* curr_locker_variant = nullptr; | ||||
|     std::vector<std::unique_ptr<LockerVariant>> locker_variants; | ||||
|     std::shared_ptr<VideoCommon::Shader::Registry> registry; | ||||
|     ShaderEntries entries; | ||||
|     VAddr cpu_addr = 0; | ||||
|     std::size_t size_in_bytes = 0; | ||||
|     std::shared_ptr<OGLProgram> program; | ||||
| }; | ||||
|  | ||||
| class ShaderCacheOpenGL final : public RasterizerCache<Shader> { | ||||
| @@ -142,25 +121,15 @@ protected: | ||||
|     void FlushObjectInner(const Shader& object) override {} | ||||
|  | ||||
| private: | ||||
|     bool GenerateUnspecializedShaders(const std::atomic_bool& stop_loading, | ||||
|                                       const VideoCore::DiskResourceLoadCallback& callback, | ||||
|                                       const std::vector<ShaderDiskCacheRaw>& raws); | ||||
|  | ||||
|     CachedProgram GeneratePrecompiledProgram(const ShaderDiskCacheDump& dump, | ||||
|                                              const std::unordered_set<GLenum>& supported_formats); | ||||
|  | ||||
|     const PrecompiledVariants* GetPrecompiledVariants(u64 unique_identifier) const; | ||||
|     std::shared_ptr<OGLProgram> GeneratePrecompiledProgram( | ||||
|         const ShaderDiskCacheEntry& entry, const ShaderDiskCachePrecompiled& precompiled_entry, | ||||
|         const std::unordered_set<GLenum>& supported_formats); | ||||
|  | ||||
|     Core::System& system; | ||||
|     Core::Frontend::EmuWindow& emu_window; | ||||
|     const Device& device; | ||||
|  | ||||
|     ShaderDiskCacheOpenGL disk_cache; | ||||
|  | ||||
|     PrecompiledPrograms precompiled_programs; | ||||
|     std::unordered_map<u64, PrecompiledVariants> precompiled_variants; | ||||
|  | ||||
|     std::unordered_map<u64, UnspecializedShader> unspecialized_shaders; | ||||
|     std::unordered_map<u64, PrecompiledShader> runtime_cache; | ||||
|  | ||||
|     std::array<Shader, Maxwell::MaxShaderProgram> last_shaders; | ||||
| }; | ||||
|   | ||||
| @@ -24,7 +24,7 @@ | ||||
| #include "video_core/shader/node.h" | ||||
| #include "video_core/shader/shader_ir.h" | ||||
|  | ||||
| namespace OpenGL::GLShader { | ||||
| namespace OpenGL { | ||||
|  | ||||
| namespace { | ||||
|  | ||||
| @@ -36,6 +36,7 @@ using Tegra::Shader::IpaInterpMode; | ||||
| using Tegra::Shader::IpaMode; | ||||
| using Tegra::Shader::IpaSampleMode; | ||||
| using Tegra::Shader::Register; | ||||
| using VideoCommon::Shader::Registry; | ||||
|  | ||||
| using namespace std::string_literals; | ||||
| using namespace VideoCommon::Shader; | ||||
| @@ -56,6 +57,25 @@ using TextureIR = std::variant<TextureOffset, TextureDerivates, TextureArgument> | ||||
| constexpr u32 MAX_CONSTBUFFER_ELEMENTS = | ||||
|     static_cast<u32>(Maxwell::MaxConstBufferSize) / (4 * sizeof(float)); | ||||
|  | ||||
| constexpr std::string_view CommonDeclarations = R"(#define ftoi floatBitsToInt | ||||
| #define ftou floatBitsToUint | ||||
| #define itof intBitsToFloat | ||||
| #define utof uintBitsToFloat | ||||
|  | ||||
| bvec2 HalfFloatNanComparison(bvec2 comparison, vec2 pair1, vec2 pair2) {{ | ||||
|     bvec2 is_nan1 = isnan(pair1); | ||||
|     bvec2 is_nan2 = isnan(pair2); | ||||
|     return bvec2(comparison.x || is_nan1.x || is_nan2.x, comparison.y || is_nan1.y || is_nan2.y); | ||||
| }} | ||||
|  | ||||
| const float fswzadd_modifiers_a[] = float[4](-1.0f,  1.0f, -1.0f,  0.0f ); | ||||
| const float fswzadd_modifiers_b[] = float[4](-1.0f, -1.0f,  1.0f, -1.0f ); | ||||
|  | ||||
| layout (std140, binding = {}) uniform vs_config {{ | ||||
|     float y_direction; | ||||
| }}; | ||||
| )"; | ||||
|  | ||||
| class ShaderWriter final { | ||||
| public: | ||||
|     void AddExpression(std::string_view text) { | ||||
| @@ -269,12 +289,41 @@ const char* GetImageTypeDeclaration(Tegra::Shader::ImageType image_type) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Describes primitive behavior on geometry shaders | ||||
| std::pair<const char*, u32> GetPrimitiveDescription(Maxwell::PrimitiveTopology topology) { | ||||
|     switch (topology) { | ||||
|     case Maxwell::PrimitiveTopology::Points: | ||||
|         return {"points", 1}; | ||||
|     case Maxwell::PrimitiveTopology::Lines: | ||||
|     case Maxwell::PrimitiveTopology::LineStrip: | ||||
|         return {"lines", 2}; | ||||
|     case Maxwell::PrimitiveTopology::LinesAdjacency: | ||||
|     case Maxwell::PrimitiveTopology::LineStripAdjacency: | ||||
|         return {"lines_adjacency", 4}; | ||||
|     case Maxwell::PrimitiveTopology::Triangles: | ||||
|     case Maxwell::PrimitiveTopology::TriangleStrip: | ||||
|     case Maxwell::PrimitiveTopology::TriangleFan: | ||||
|         return {"triangles", 3}; | ||||
|     case Maxwell::PrimitiveTopology::TrianglesAdjacency: | ||||
|     case Maxwell::PrimitiveTopology::TriangleStripAdjacency: | ||||
|         return {"triangles_adjacency", 6}; | ||||
|     default: | ||||
|         UNIMPLEMENTED_MSG("topology={}", static_cast<int>(topology)); | ||||
|         return {"points", 1}; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Generates code to use for a swizzle operation. | ||||
| constexpr const char* GetSwizzle(u32 element) { | ||||
| constexpr const char* GetSwizzle(std::size_t element) { | ||||
|     constexpr std::array swizzle = {".x", ".y", ".z", ".w"}; | ||||
|     return swizzle.at(element); | ||||
| } | ||||
|  | ||||
| constexpr const char* GetColorSwizzle(std::size_t element) { | ||||
|     constexpr std::array swizzle = {".r", ".g", ".b", ".a"}; | ||||
|     return swizzle.at(element); | ||||
| } | ||||
|  | ||||
| /// Translate topology | ||||
| std::string GetTopologyName(Tegra::Shader::OutputTopology topology) { | ||||
|     switch (topology) { | ||||
| @@ -343,9 +392,54 @@ std::string FlowStackTopName(MetaStackClass stack) { | ||||
|  | ||||
| class GLSLDecompiler final { | ||||
| public: | ||||
|     explicit GLSLDecompiler(const Device& device, const ShaderIR& ir, ShaderType stage, | ||||
|                             std::string suffix) | ||||
|         : device{device}, ir{ir}, stage{stage}, suffix{suffix}, header{ir.GetHeader()} {} | ||||
|     explicit GLSLDecompiler(const Device& device, const ShaderIR& ir, const Registry& registry, | ||||
|                             ShaderType stage, std::string_view identifier, std::string_view suffix) | ||||
|         : device{device}, ir{ir}, registry{registry}, stage{stage}, | ||||
|           identifier{identifier}, suffix{suffix}, header{ir.GetHeader()} {} | ||||
|  | ||||
|     void Decompile() { | ||||
|         DeclareHeader(); | ||||
|         DeclareVertex(); | ||||
|         DeclareGeometry(); | ||||
|         DeclareFragment(); | ||||
|         DeclareCompute(); | ||||
|         DeclareRegisters(); | ||||
|         DeclareCustomVariables(); | ||||
|         DeclarePredicates(); | ||||
|         DeclareLocalMemory(); | ||||
|         DeclareInternalFlags(); | ||||
|         DeclareInputAttributes(); | ||||
|         DeclareOutputAttributes(); | ||||
|         DeclareConstantBuffers(); | ||||
|         DeclareGlobalMemory(); | ||||
|         DeclareSamplers(); | ||||
|         DeclareImages(); | ||||
|         DeclarePhysicalAttributeReader(); | ||||
|  | ||||
|         code.AddLine("void main() {{"); | ||||
|         ++code.scope; | ||||
|  | ||||
|         if (stage == ShaderType::Vertex) { | ||||
|             code.AddLine("gl_Position = vec4(0.0f, 0.0f, 0.0f, 1.0f);"); | ||||
|         } | ||||
|  | ||||
|         if (ir.IsDecompiled()) { | ||||
|             DecompileAST(); | ||||
|         } else { | ||||
|             DecompileBranchMode(); | ||||
|         } | ||||
|  | ||||
|         --code.scope; | ||||
|         code.AddLine("}}"); | ||||
|     } | ||||
|  | ||||
|     std::string GetResult() { | ||||
|         return code.GetResult(); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     friend class ASTDecompiler; | ||||
|     friend class ExprDecompiler; | ||||
|  | ||||
|     void DecompileBranchMode() { | ||||
|         // VM's program counter | ||||
| @@ -387,43 +481,36 @@ public: | ||||
|  | ||||
|     void DecompileAST(); | ||||
|  | ||||
|     void Decompile() { | ||||
|         DeclareVertex(); | ||||
|         DeclareGeometry(); | ||||
|         DeclareRegisters(); | ||||
|         DeclareCustomVariables(); | ||||
|         DeclarePredicates(); | ||||
|         DeclareLocalMemory(); | ||||
|         DeclareInternalFlags(); | ||||
|         DeclareInputAttributes(); | ||||
|         DeclareOutputAttributes(); | ||||
|         DeclareConstantBuffers(); | ||||
|         DeclareGlobalMemory(); | ||||
|         DeclareSamplers(); | ||||
|         DeclareImages(); | ||||
|         DeclarePhysicalAttributeReader(); | ||||
|  | ||||
|         code.AddLine("void execute_{}() {{", suffix); | ||||
|         ++code.scope; | ||||
|  | ||||
|         if (ir.IsDecompiled()) { | ||||
|             DecompileAST(); | ||||
|         } else { | ||||
|             DecompileBranchMode(); | ||||
|     void DeclareHeader() { | ||||
|         if (!identifier.empty()) { | ||||
|             code.AddLine("// {}", identifier); | ||||
|         } | ||||
|         code.AddLine("#version 430 core"); | ||||
|         code.AddLine("#extension GL_ARB_separate_shader_objects : enable"); | ||||
|         if (device.HasShaderBallot()) { | ||||
|             code.AddLine("#extension GL_ARB_shader_ballot : require"); | ||||
|         } | ||||
|         if (device.HasVertexViewportLayer()) { | ||||
|             code.AddLine("#extension GL_ARB_shader_viewport_layer_array : require"); | ||||
|         } | ||||
|         if (device.HasImageLoadFormatted()) { | ||||
|             code.AddLine("#extension GL_EXT_shader_image_load_formatted : require"); | ||||
|         } | ||||
|         if (device.HasWarpIntrinsics()) { | ||||
|             code.AddLine("#extension GL_NV_gpu_shader5 : require"); | ||||
|             code.AddLine("#extension GL_NV_shader_thread_group : require"); | ||||
|             code.AddLine("#extension GL_NV_shader_thread_shuffle : require"); | ||||
|         } | ||||
|         // This pragma stops Nvidia's driver from over optimizing math (probably using fp16 | ||||
|         // operations) on places where we don't want to. | ||||
|         // Thanks to Ryujinx for finding this workaround. | ||||
|         code.AddLine("#pragma optionNV(fastmath off)"); | ||||
|  | ||||
|         --code.scope; | ||||
|         code.AddLine("}}"); | ||||
|         code.AddNewLine(); | ||||
|  | ||||
|         code.AddLine(CommonDeclarations, EmulationUniformBlockBinding); | ||||
|     } | ||||
|  | ||||
|     std::string GetResult() { | ||||
|         return code.GetResult(); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     friend class ASTDecompiler; | ||||
|     friend class ExprDecompiler; | ||||
|  | ||||
|     void DeclareVertex() { | ||||
|         if (!IsVertexShader(stage)) | ||||
|             return; | ||||
| @@ -436,9 +523,15 @@ private: | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         const auto& info = registry.GetGraphicsInfo(); | ||||
|         const auto input_topology = info.primitive_topology; | ||||
|         const auto [glsl_topology, max_vertices] = GetPrimitiveDescription(input_topology); | ||||
|         max_input_vertices = max_vertices; | ||||
|         code.AddLine("layout ({}) in;", glsl_topology); | ||||
|  | ||||
|         const auto topology = GetTopologyName(header.common3.output_topology); | ||||
|         const auto max_vertices = header.common4.max_output_vertices.Value(); | ||||
|         code.AddLine("layout ({}, max_vertices = {}) out;", topology, max_vertices); | ||||
|         const auto max_output_vertices = header.common4.max_output_vertices.Value(); | ||||
|         code.AddLine("layout ({}, max_vertices = {}) out;", topology, max_output_vertices); | ||||
|         code.AddNewLine(); | ||||
|  | ||||
|         code.AddLine("in gl_PerVertex {{"); | ||||
| @@ -450,6 +543,29 @@ private: | ||||
|         DeclareVertexRedeclarations(); | ||||
|     } | ||||
|  | ||||
|     void DeclareFragment() { | ||||
|         if (stage != ShaderType::Fragment) { | ||||
|             return; | ||||
|         } | ||||
|         for (u32 rt = 0; rt < Maxwell::NumRenderTargets; ++rt) { | ||||
|             code.AddLine("layout (location = {}) out vec4 frag_color{};", rt, rt); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void DeclareCompute() { | ||||
|         if (stage != ShaderType::Compute) { | ||||
|             return; | ||||
|         } | ||||
|         const auto& info = registry.GetComputeInfo(); | ||||
|         if (const u32 size = info.shared_memory_size_in_words; size > 0) { | ||||
|             code.AddLine("shared uint smem[{}];", size); | ||||
|             code.AddNewLine(); | ||||
|         } | ||||
|         code.AddLine("layout (local_size_x = {}, local_size_y = {}, local_size_z = {}) in;", | ||||
|                      info.workgroup_size[0], info.workgroup_size[1], info.workgroup_size[2]); | ||||
|         code.AddNewLine(); | ||||
|     } | ||||
|  | ||||
|     void DeclareVertexRedeclarations() { | ||||
|         code.AddLine("out gl_PerVertex {{"); | ||||
|         ++code.scope; | ||||
| @@ -525,18 +641,16 @@ private: | ||||
|     } | ||||
|  | ||||
|     void DeclareLocalMemory() { | ||||
|         u64 local_memory_size = 0; | ||||
|         if (stage == ShaderType::Compute) { | ||||
|             code.AddLine("#ifdef LOCAL_MEMORY_SIZE"); | ||||
|             code.AddLine("uint {}[LOCAL_MEMORY_SIZE];", GetLocalMemory()); | ||||
|             code.AddLine("#endif"); | ||||
|             return; | ||||
|             local_memory_size = registry.GetComputeInfo().local_memory_size_in_words * 4ULL; | ||||
|         } else { | ||||
|             local_memory_size = header.GetLocalMemorySize(); | ||||
|         } | ||||
|  | ||||
|         const u64 local_memory_size = header.GetLocalMemorySize(); | ||||
|         if (local_memory_size == 0) { | ||||
|             return; | ||||
|         } | ||||
|         const auto element_count = Common::AlignUp(local_memory_size, 4) / 4; | ||||
|         const u64 element_count = Common::AlignUp(local_memory_size, 4) / 4; | ||||
|         code.AddLine("uint {}[{}];", GetLocalMemory(), element_count); | ||||
|         code.AddNewLine(); | ||||
|     } | ||||
| @@ -925,7 +1039,8 @@ private: | ||||
|                 // TODO(Rodrigo): Guard geometry inputs against out of bound reads. Some games | ||||
|                 // set an 0x80000000 index for those and the shader fails to build. Find out why | ||||
|                 // this happens and what's its intent. | ||||
|                 return fmt::format("gs_{}[{} % MAX_VERTEX_INPUT]", name, Visit(buffer).AsUint()); | ||||
|                 return fmt::format("gs_{}[{} % {}]", name, Visit(buffer).AsUint(), | ||||
|                                    max_input_vertices.value()); | ||||
|             } | ||||
|             return std::string(name); | ||||
|         }; | ||||
| @@ -1945,7 +2060,7 @@ private: | ||||
|             // TODO(Subv): Figure out how dual-source blending is configured in the Switch. | ||||
|             for (u32 component = 0; component < 4; ++component) { | ||||
|                 if (header.ps.IsColorComponentOutputEnabled(render_target, component)) { | ||||
|                     code.AddLine("FragColor{}[{}] = {};", render_target, component, | ||||
|                     code.AddLine("frag_color{}{} = {};", render_target, GetColorSwizzle(component), | ||||
|                                  SafeGetRegister(current_reg).AsFloat()); | ||||
|                     ++current_reg; | ||||
|                 } | ||||
| @@ -2298,7 +2413,11 @@ private: | ||||
|     } | ||||
|  | ||||
|     std::string GetLocalMemory() const { | ||||
|         return "lmem_" + suffix; | ||||
|         if (suffix.empty()) { | ||||
|             return "lmem"; | ||||
|         } else { | ||||
|             return "lmem_" + std::string{suffix}; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     std::string GetInternalFlag(InternalFlag flag) const { | ||||
| @@ -2307,7 +2426,11 @@ private: | ||||
|         const auto index = static_cast<u32>(flag); | ||||
|         ASSERT(index < static_cast<u32>(InternalFlag::Amount)); | ||||
|  | ||||
|         return fmt::format("{}_{}", InternalFlagNames[index], suffix); | ||||
|         if (suffix.empty()) { | ||||
|             return InternalFlagNames[index]; | ||||
|         } else { | ||||
|             return fmt::format("{}_{}", InternalFlagNames[index], suffix); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     std::string GetSampler(const Sampler& sampler) const { | ||||
| @@ -2319,7 +2442,11 @@ private: | ||||
|     } | ||||
|  | ||||
|     std::string GetDeclarationWithSuffix(u32 index, std::string_view name) const { | ||||
|         return fmt::format("{}_{}_{}", name, index, suffix); | ||||
|         if (suffix.empty()) { | ||||
|             return fmt::format("{}{}", name, index); | ||||
|         } else { | ||||
|             return fmt::format("{}{}_{}", name, index, suffix); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     u32 GetNumPhysicalInputAttributes() const { | ||||
| @@ -2334,17 +2461,30 @@ private: | ||||
|         return std::min<u32>(device.GetMaxVaryings(), Maxwell::NumVaryings); | ||||
|     } | ||||
|  | ||||
|     bool IsRenderTargetEnabled(u32 render_target) const { | ||||
|         for (u32 component = 0; component < 4; ++component) { | ||||
|             if (header.ps.IsColorComponentOutputEnabled(render_target, component)) { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     const Device& device; | ||||
|     const ShaderIR& ir; | ||||
|     const Registry& registry; | ||||
|     const ShaderType stage; | ||||
|     const std::string suffix; | ||||
|     const std::string_view identifier; | ||||
|     const std::string_view suffix; | ||||
|     const Header header; | ||||
|  | ||||
|     ShaderWriter code; | ||||
|  | ||||
|     std::optional<u32> max_input_vertices; | ||||
| }; | ||||
|  | ||||
| std::string GetFlowVariable(u32 i) { | ||||
|     return fmt::format("flow_var_{}", i); | ||||
| std::string GetFlowVariable(u32 index) { | ||||
|     return fmt::format("flow_var{}", index); | ||||
| } | ||||
|  | ||||
| class ExprDecompiler { | ||||
| @@ -2531,7 +2671,7 @@ void GLSLDecompiler::DecompileAST() { | ||||
|  | ||||
| } // Anonymous namespace | ||||
|  | ||||
| ShaderEntries GetEntries(const VideoCommon::Shader::ShaderIR& ir) { | ||||
| ShaderEntries MakeEntries(const VideoCommon::Shader::ShaderIR& ir) { | ||||
|     ShaderEntries entries; | ||||
|     for (const auto& cbuf : ir.GetConstantBuffers()) { | ||||
|         entries.const_buffers.emplace_back(cbuf.second.GetMaxOffset(), cbuf.second.IsIndirect(), | ||||
| @@ -2555,28 +2695,12 @@ ShaderEntries GetEntries(const VideoCommon::Shader::ShaderIR& ir) { | ||||
|     return entries; | ||||
| } | ||||
|  | ||||
| std::string GetCommonDeclarations() { | ||||
|     return R"(#define ftoi floatBitsToInt | ||||
| #define ftou floatBitsToUint | ||||
| #define itof intBitsToFloat | ||||
| #define utof uintBitsToFloat | ||||
|  | ||||
| bvec2 HalfFloatNanComparison(bvec2 comparison, vec2 pair1, vec2 pair2) { | ||||
|     bvec2 is_nan1 = isnan(pair1); | ||||
|     bvec2 is_nan2 = isnan(pair2); | ||||
|     return bvec2(comparison.x || is_nan1.x || is_nan2.x, comparison.y || is_nan1.y || is_nan2.y); | ||||
| } | ||||
|  | ||||
| const float fswzadd_modifiers_a[] = float[4](-1.0f,  1.0f, -1.0f,  0.0f ); | ||||
| const float fswzadd_modifiers_b[] = float[4](-1.0f, -1.0f,  1.0f, -1.0f ); | ||||
| )"; | ||||
| } | ||||
|  | ||||
| std::string Decompile(const Device& device, const ShaderIR& ir, ShaderType stage, | ||||
|                       const std::string& suffix) { | ||||
|     GLSLDecompiler decompiler(device, ir, stage, suffix); | ||||
| std::string DecompileShader(const Device& device, const ShaderIR& ir, const Registry& registry, | ||||
|                             ShaderType stage, std::string_view identifier, | ||||
|                             std::string_view suffix) { | ||||
|     GLSLDecompiler decompiler(device, ir, registry, stage, identifier, suffix); | ||||
|     decompiler.Decompile(); | ||||
|     return decompiler.GetResult(); | ||||
| } | ||||
|  | ||||
| } // namespace OpenGL::GLShader | ||||
| } // namespace OpenGL | ||||
|   | ||||
| @@ -6,22 +6,18 @@ | ||||
|  | ||||
| #include <array> | ||||
| #include <string> | ||||
| #include <string_view> | ||||
| #include <utility> | ||||
| #include <vector> | ||||
| #include "common/common_types.h" | ||||
| #include "video_core/engines/maxwell_3d.h" | ||||
| #include "video_core/engines/shader_type.h" | ||||
| #include "video_core/shader/registry.h" | ||||
| #include "video_core/shader/shader_ir.h" | ||||
|  | ||||
| namespace VideoCommon::Shader { | ||||
| class ShaderIR; | ||||
| } | ||||
|  | ||||
| namespace OpenGL { | ||||
| class Device; | ||||
| } | ||||
|  | ||||
| namespace OpenGL::GLShader { | ||||
| class Device; | ||||
|  | ||||
| using Maxwell = Tegra::Engines::Maxwell3D::Regs; | ||||
| using SamplerEntry = VideoCommon::Shader::Sampler; | ||||
| @@ -78,11 +74,11 @@ struct ShaderEntries { | ||||
|     std::size_t shader_length{}; | ||||
| }; | ||||
|  | ||||
| ShaderEntries GetEntries(const VideoCommon::Shader::ShaderIR& ir); | ||||
| ShaderEntries MakeEntries(const VideoCommon::Shader::ShaderIR& ir); | ||||
|  | ||||
| std::string GetCommonDeclarations(); | ||||
| std::string DecompileShader(const Device& device, const VideoCommon::Shader::ShaderIR& ir, | ||||
|                             const VideoCommon::Shader::Registry& registry, | ||||
|                             Tegra::Engines::ShaderType stage, std::string_view identifier, | ||||
|                             std::string_view suffix = {}); | ||||
|  | ||||
| std::string Decompile(const Device& device, const VideoCommon::Shader::ShaderIR& ir, | ||||
|                       Tegra::Engines::ShaderType stage, const std::string& suffix); | ||||
|  | ||||
| } // namespace OpenGL::GLShader | ||||
| } // namespace OpenGL | ||||
|   | ||||
| @@ -31,32 +31,24 @@ namespace { | ||||
|  | ||||
| using ShaderCacheVersionHash = std::array<u8, 64>; | ||||
|  | ||||
| enum class TransferableEntryKind : u32 { | ||||
|     Raw, | ||||
|     Usage, | ||||
| }; | ||||
|  | ||||
| struct ConstBufferKey { | ||||
|     u32 cbuf{}; | ||||
|     u32 offset{}; | ||||
|     u32 value{}; | ||||
|     u32 cbuf = 0; | ||||
|     u32 offset = 0; | ||||
|     u32 value = 0; | ||||
| }; | ||||
|  | ||||
| struct BoundSamplerKey { | ||||
|     u32 offset{}; | ||||
|     Tegra::Engines::SamplerDescriptor sampler{}; | ||||
|     u32 offset = 0; | ||||
|     Tegra::Engines::SamplerDescriptor sampler; | ||||
| }; | ||||
|  | ||||
| struct BindlessSamplerKey { | ||||
|     u32 cbuf{}; | ||||
|     u32 offset{}; | ||||
|     Tegra::Engines::SamplerDescriptor sampler{}; | ||||
|     u32 cbuf = 0; | ||||
|     u32 offset = 0; | ||||
|     Tegra::Engines::SamplerDescriptor sampler; | ||||
| }; | ||||
|  | ||||
| constexpr u32 NativeVersion = 12; | ||||
|  | ||||
| // Making sure sizes doesn't change by accident | ||||
| static_assert(sizeof(ProgramVariant) == 20); | ||||
| constexpr u32 NativeVersion = 20; | ||||
|  | ||||
| ShaderCacheVersionHash GetShaderCacheVersionHash() { | ||||
|     ShaderCacheVersionHash hash{}; | ||||
| @@ -67,61 +59,124 @@ ShaderCacheVersionHash GetShaderCacheVersionHash() { | ||||
|  | ||||
| } // Anonymous namespace | ||||
|  | ||||
| ShaderDiskCacheRaw::ShaderDiskCacheRaw(u64 unique_identifier, ShaderType type, ProgramCode code, | ||||
|                                        ProgramCode code_b) | ||||
|     : unique_identifier{unique_identifier}, type{type}, code{std::move(code)}, code_b{std::move( | ||||
|                                                                                    code_b)} {} | ||||
| ShaderDiskCacheEntry::ShaderDiskCacheEntry() = default; | ||||
|  | ||||
| ShaderDiskCacheRaw::ShaderDiskCacheRaw() = default; | ||||
| ShaderDiskCacheEntry::~ShaderDiskCacheEntry() = default; | ||||
|  | ||||
| ShaderDiskCacheRaw::~ShaderDiskCacheRaw() = default; | ||||
|  | ||||
| bool ShaderDiskCacheRaw::Load(FileUtil::IOFile& file) { | ||||
|     if (file.ReadBytes(&unique_identifier, sizeof(u64)) != sizeof(u64) || | ||||
|         file.ReadBytes(&type, sizeof(u32)) != sizeof(u32)) { | ||||
| bool ShaderDiskCacheEntry::Load(FileUtil::IOFile& file) { | ||||
|     if (file.ReadBytes(&type, sizeof(u32)) != sizeof(u32)) { | ||||
|         return false; | ||||
|     } | ||||
|     u32 code_size{}; | ||||
|     u32 code_size_b{}; | ||||
|     u32 code_size; | ||||
|     u32 code_size_b; | ||||
|     if (file.ReadBytes(&code_size, sizeof(u32)) != sizeof(u32) || | ||||
|         file.ReadBytes(&code_size_b, sizeof(u32)) != sizeof(u32)) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     code.resize(code_size); | ||||
|     code_b.resize(code_size_b); | ||||
|  | ||||
|     if (file.ReadArray(code.data(), code_size) != code_size) | ||||
|     if (file.ReadArray(code.data(), code_size) != code_size) { | ||||
|         return false; | ||||
|  | ||||
|     } | ||||
|     if (HasProgramA() && file.ReadArray(code_b.data(), code_size_b) != code_size_b) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     u8 is_texture_handler_size_known; | ||||
|     u32 texture_handler_size_value; | ||||
|     u32 num_keys; | ||||
|     u32 num_bound_samplers; | ||||
|     u32 num_bindless_samplers; | ||||
|     if (file.ReadArray(&unique_identifier, 1) != 1 || file.ReadArray(&bound_buffer, 1) != 1 || | ||||
|         file.ReadArray(&is_texture_handler_size_known, 1) != 1 || | ||||
|         file.ReadArray(&texture_handler_size_value, 1) != 1 || | ||||
|         file.ReadArray(&graphics_info, 1) != 1 || file.ReadArray(&compute_info, 1) != 1 || | ||||
|         file.ReadArray(&num_keys, 1) != 1 || file.ReadArray(&num_bound_samplers, 1) != 1 || | ||||
|         file.ReadArray(&num_bindless_samplers, 1) != 1) { | ||||
|         return false; | ||||
|     } | ||||
|     if (is_texture_handler_size_known) { | ||||
|         texture_handler_size = texture_handler_size_value; | ||||
|     } | ||||
|  | ||||
|     std::vector<ConstBufferKey> flat_keys(num_keys); | ||||
|     std::vector<BoundSamplerKey> flat_bound_samplers(num_bound_samplers); | ||||
|     std::vector<BindlessSamplerKey> flat_bindless_samplers(num_bindless_samplers); | ||||
|     if (file.ReadArray(flat_keys.data(), flat_keys.size()) != flat_keys.size() || | ||||
|         file.ReadArray(flat_bound_samplers.data(), flat_bound_samplers.size()) != | ||||
|             flat_bound_samplers.size() || | ||||
|         file.ReadArray(flat_bindless_samplers.data(), flat_bindless_samplers.size()) != | ||||
|             flat_bindless_samplers.size()) { | ||||
|         return false; | ||||
|     } | ||||
|     for (const auto& key : flat_keys) { | ||||
|         keys.insert({{key.cbuf, key.offset}, key.value}); | ||||
|     } | ||||
|     for (const auto& key : flat_bound_samplers) { | ||||
|         bound_samplers.emplace(key.offset, key.sampler); | ||||
|     } | ||||
|     for (const auto& key : flat_bindless_samplers) { | ||||
|         bindless_samplers.insert({{key.cbuf, key.offset}, key.sampler}); | ||||
|     } | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool ShaderDiskCacheRaw::Save(FileUtil::IOFile& file) const { | ||||
|     if (file.WriteObject(unique_identifier) != 1 || file.WriteObject(static_cast<u32>(type)) != 1 || | ||||
| bool ShaderDiskCacheEntry::Save(FileUtil::IOFile& file) const { | ||||
|     if (file.WriteObject(static_cast<u32>(type)) != 1 || | ||||
|         file.WriteObject(static_cast<u32>(code.size())) != 1 || | ||||
|         file.WriteObject(static_cast<u32>(code_b.size())) != 1) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     if (file.WriteArray(code.data(), code.size()) != code.size()) | ||||
|     if (file.WriteArray(code.data(), code.size()) != code.size()) { | ||||
|         return false; | ||||
|  | ||||
|     } | ||||
|     if (HasProgramA() && file.WriteArray(code_b.data(), code_b.size()) != code_b.size()) { | ||||
|         return false; | ||||
|     } | ||||
|     return true; | ||||
|  | ||||
|     if (file.WriteObject(unique_identifier) != 1 || file.WriteObject(bound_buffer) != 1 || | ||||
|         file.WriteObject(static_cast<u8>(texture_handler_size.has_value())) != 1 || | ||||
|         file.WriteObject(texture_handler_size.value_or(0)) != 1 || | ||||
|         file.WriteObject(graphics_info) != 1 || file.WriteObject(compute_info) != 1 || | ||||
|         file.WriteObject(static_cast<u32>(keys.size())) != 1 || | ||||
|         file.WriteObject(static_cast<u32>(bound_samplers.size())) != 1 || | ||||
|         file.WriteObject(static_cast<u32>(bindless_samplers.size())) != 1) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     std::vector<ConstBufferKey> flat_keys; | ||||
|     flat_keys.reserve(keys.size()); | ||||
|     for (const auto& [address, value] : keys) { | ||||
|         flat_keys.push_back(ConstBufferKey{address.first, address.second, value}); | ||||
|     } | ||||
|  | ||||
|     std::vector<BoundSamplerKey> flat_bound_samplers; | ||||
|     flat_bound_samplers.reserve(bound_samplers.size()); | ||||
|     for (const auto& [address, sampler] : bound_samplers) { | ||||
|         flat_bound_samplers.push_back(BoundSamplerKey{address, sampler}); | ||||
|     } | ||||
|  | ||||
|     std::vector<BindlessSamplerKey> flat_bindless_samplers; | ||||
|     flat_bindless_samplers.reserve(bindless_samplers.size()); | ||||
|     for (const auto& [address, sampler] : bindless_samplers) { | ||||
|         flat_bindless_samplers.push_back( | ||||
|             BindlessSamplerKey{address.first, address.second, sampler}); | ||||
|     } | ||||
|  | ||||
|     return file.WriteArray(flat_keys.data(), flat_keys.size()) == flat_keys.size() && | ||||
|            file.WriteArray(flat_bound_samplers.data(), flat_bound_samplers.size()) == | ||||
|                flat_bound_samplers.size() && | ||||
|            file.WriteArray(flat_bindless_samplers.data(), flat_bindless_samplers.size()) == | ||||
|                flat_bindless_samplers.size(); | ||||
| } | ||||
|  | ||||
| ShaderDiskCacheOpenGL::ShaderDiskCacheOpenGL(Core::System& system) : system{system} {} | ||||
|  | ||||
| ShaderDiskCacheOpenGL::~ShaderDiskCacheOpenGL() = default; | ||||
|  | ||||
| std::optional<std::pair<std::vector<ShaderDiskCacheRaw>, std::vector<ShaderDiskCacheUsage>>> | ||||
| ShaderDiskCacheOpenGL::LoadTransferable() { | ||||
| std::optional<std::vector<ShaderDiskCacheEntry>> 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) { | ||||
| @@ -130,17 +185,14 @@ ShaderDiskCacheOpenGL::LoadTransferable() { | ||||
|  | ||||
|     FileUtil::IOFile file(GetTransferablePath(), "rb"); | ||||
|     if (!file.IsOpen()) { | ||||
|         LOG_INFO(Render_OpenGL, "No transferable shader cache found for game with title id={}", | ||||
|                  GetTitleID()); | ||||
|         LOG_INFO(Render_OpenGL, "No transferable shader cache found"); | ||||
|         is_usable = true; | ||||
|         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()); | ||||
|         LOG_ERROR(Render_OpenGL, "Failed to get transferable cache version, skipping it"); | ||||
|         return {}; | ||||
|     } | ||||
|  | ||||
| @@ -158,105 +210,42 @@ ShaderDiskCacheOpenGL::LoadTransferable() { | ||||
|     } | ||||
|  | ||||
|     // Version is valid, load the shaders | ||||
|     constexpr const char error_loading[] = "Failed to load transferable raw entry, skipping"; | ||||
|     std::vector<ShaderDiskCacheRaw> raws; | ||||
|     std::vector<ShaderDiskCacheUsage> usages; | ||||
|     std::vector<ShaderDiskCacheEntry> entries; | ||||
|     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, error_loading); | ||||
|                 return {}; | ||||
|             } | ||||
|             transferable.insert({entry.GetUniqueIdentifier(), {}}); | ||||
|             raws.push_back(std::move(entry)); | ||||
|             break; | ||||
|         } | ||||
|         case TransferableEntryKind::Usage: { | ||||
|             ShaderDiskCacheUsage usage; | ||||
|  | ||||
|             u32 num_keys{}; | ||||
|             u32 num_bound_samplers{}; | ||||
|             u32 num_bindless_samplers{}; | ||||
|             if (file.ReadArray(&usage.unique_identifier, 1) != 1 || | ||||
|                 file.ReadArray(&usage.variant, 1) != 1 || | ||||
|                 file.ReadArray(&usage.bound_buffer, 1) != 1 || file.ReadArray(&num_keys, 1) != 1 || | ||||
|                 file.ReadArray(&num_bound_samplers, 1) != 1 || | ||||
|                 file.ReadArray(&num_bindless_samplers, 1) != 1) { | ||||
|                 LOG_ERROR(Render_OpenGL, error_loading); | ||||
|                 return {}; | ||||
|             } | ||||
|  | ||||
|             std::vector<ConstBufferKey> keys(num_keys); | ||||
|             std::vector<BoundSamplerKey> bound_samplers(num_bound_samplers); | ||||
|             std::vector<BindlessSamplerKey> bindless_samplers(num_bindless_samplers); | ||||
|             if (file.ReadArray(keys.data(), keys.size()) != keys.size() || | ||||
|                 file.ReadArray(bound_samplers.data(), bound_samplers.size()) != | ||||
|                     bound_samplers.size() || | ||||
|                 file.ReadArray(bindless_samplers.data(), bindless_samplers.size()) != | ||||
|                     bindless_samplers.size()) { | ||||
|                 LOG_ERROR(Render_OpenGL, error_loading); | ||||
|                 return {}; | ||||
|             } | ||||
|             for (const auto& key : keys) { | ||||
|                 usage.keys.insert({{key.cbuf, key.offset}, key.value}); | ||||
|             } | ||||
|             for (const auto& key : bound_samplers) { | ||||
|                 usage.bound_samplers.emplace(key.offset, key.sampler); | ||||
|             } | ||||
|             for (const auto& key : bindless_samplers) { | ||||
|                 usage.bindless_samplers.insert({{key.cbuf, key.offset}, key.sampler}); | ||||
|             } | ||||
|  | ||||
|             usages.push_back(std::move(usage)); | ||||
|             break; | ||||
|         } | ||||
|         default: | ||||
|             LOG_ERROR(Render_OpenGL, "Unknown transferable shader cache entry kind={}, skipping", | ||||
|                       static_cast<u32>(kind)); | ||||
|         ShaderDiskCacheEntry& entry = entries.emplace_back(); | ||||
|         if (!entry.Load(file)) { | ||||
|             LOG_ERROR(Render_OpenGL, "Failed to load transferable raw entry, skipping"); | ||||
|             return {}; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     is_usable = true; | ||||
|     return {{std::move(raws), std::move(usages)}}; | ||||
|     return {std::move(entries)}; | ||||
| } | ||||
|  | ||||
| std::unordered_map<ShaderDiskCacheUsage, ShaderDiskCacheDump> | ||||
| ShaderDiskCacheOpenGL::LoadPrecompiled() { | ||||
| std::vector<ShaderDiskCachePrecompiled> ShaderDiskCacheOpenGL::LoadPrecompiled() { | ||||
|     if (!is_usable) { | ||||
|         return {}; | ||||
|     } | ||||
|  | ||||
|     std::string path = GetPrecompiledPath(); | ||||
|     FileUtil::IOFile file(path, "rb"); | ||||
|     FileUtil::IOFile file(GetPrecompiledPath(), "rb"); | ||||
|     if (!file.IsOpen()) { | ||||
|         LOG_INFO(Render_OpenGL, "No precompiled shader cache found for game with title id={}", | ||||
|                  GetTitleID()); | ||||
|         LOG_INFO(Render_OpenGL, "No precompiled shader cache found"); | ||||
|         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 {}; | ||||
|     if (const auto result = LoadPrecompiledFile(file)) { | ||||
|         return *result; | ||||
|     } | ||||
|     return *result; | ||||
|  | ||||
|     LOG_INFO(Render_OpenGL, "Failed to load precompiled cache"); | ||||
|     file.Close(); | ||||
|     InvalidatePrecompiled(); | ||||
|     return {}; | ||||
| } | ||||
|  | ||||
| std::optional<std::unordered_map<ShaderDiskCacheUsage, ShaderDiskCacheDump>> | ||||
| ShaderDiskCacheOpenGL::LoadPrecompiledFile(FileUtil::IOFile& file) { | ||||
| std::optional<std::vector<ShaderDiskCachePrecompiled>> ShaderDiskCacheOpenGL::LoadPrecompiledFile( | ||||
|     FileUtil::IOFile& file) { | ||||
|     // Read compressed file from disk and decompress to virtual precompiled cache file | ||||
|     std::vector<u8> compressed(file.GetSize()); | ||||
|     file.ReadBytes(compressed.data(), compressed.size()); | ||||
| @@ -275,58 +264,22 @@ ShaderDiskCacheOpenGL::LoadPrecompiledFile(FileUtil::IOFile& file) { | ||||
|         return {}; | ||||
|     } | ||||
|  | ||||
|     ShaderDumpsMap dumps; | ||||
|     std::vector<ShaderDiskCachePrecompiled> entries; | ||||
|     while (precompiled_cache_virtual_file_offset < precompiled_cache_virtual_file.GetSize()) { | ||||
|         u32 num_keys{}; | ||||
|         u32 num_bound_samplers{}; | ||||
|         u32 num_bindless_samplers{}; | ||||
|         ShaderDiskCacheUsage usage; | ||||
|         if (!LoadObjectFromPrecompiled(usage.unique_identifier) || | ||||
|             !LoadObjectFromPrecompiled(usage.variant) || | ||||
|             !LoadObjectFromPrecompiled(usage.bound_buffer) || | ||||
|             !LoadObjectFromPrecompiled(num_keys) || | ||||
|             !LoadObjectFromPrecompiled(num_bound_samplers) || | ||||
|             !LoadObjectFromPrecompiled(num_bindless_samplers)) { | ||||
|             return {}; | ||||
|         } | ||||
|         std::vector<ConstBufferKey> keys(num_keys); | ||||
|         std::vector<BoundSamplerKey> bound_samplers(num_bound_samplers); | ||||
|         std::vector<BindlessSamplerKey> bindless_samplers(num_bindless_samplers); | ||||
|         if (!LoadArrayFromPrecompiled(keys.data(), keys.size()) || | ||||
|             !LoadArrayFromPrecompiled(bound_samplers.data(), bound_samplers.size()) != | ||||
|                 bound_samplers.size() || | ||||
|             !LoadArrayFromPrecompiled(bindless_samplers.data(), bindless_samplers.size()) != | ||||
|                 bindless_samplers.size()) { | ||||
|             return {}; | ||||
|         } | ||||
|         for (const auto& key : keys) { | ||||
|             usage.keys.insert({{key.cbuf, key.offset}, key.value}); | ||||
|         } | ||||
|         for (const auto& key : bound_samplers) { | ||||
|             usage.bound_samplers.emplace(key.offset, key.sampler); | ||||
|         } | ||||
|         for (const auto& key : bindless_samplers) { | ||||
|             usage.bindless_samplers.insert({{key.cbuf, key.offset}, key.sampler}); | ||||
|         } | ||||
|  | ||||
|         ShaderDiskCacheDump dump; | ||||
|         if (!LoadObjectFromPrecompiled(dump.binary_format)) { | ||||
|         u32 binary_size; | ||||
|         auto& entry = entries.emplace_back(); | ||||
|         if (!LoadObjectFromPrecompiled(entry.unique_identifier) || | ||||
|             !LoadObjectFromPrecompiled(entry.binary_format) || | ||||
|             !LoadObjectFromPrecompiled(binary_size)) { | ||||
|             return {}; | ||||
|         } | ||||
|  | ||||
|         u32 binary_length{}; | ||||
|         if (!LoadObjectFromPrecompiled(binary_length)) { | ||||
|         entry.binary.resize(binary_size); | ||||
|         if (!LoadArrayFromPrecompiled(entry.binary.data(), entry.binary.size())) { | ||||
|             return {}; | ||||
|         } | ||||
|  | ||||
|         dump.binary.resize(binary_length); | ||||
|         if (!LoadArrayFromPrecompiled(dump.binary.data(), dump.binary.size())) { | ||||
|             return {}; | ||||
|         } | ||||
|  | ||||
|         dumps.emplace(std::move(usage), dump); | ||||
|     } | ||||
|     return dumps; | ||||
|     return entries; | ||||
| } | ||||
|  | ||||
| void ShaderDiskCacheOpenGL::InvalidateTransferable() { | ||||
| @@ -346,13 +299,13 @@ void ShaderDiskCacheOpenGL::InvalidatePrecompiled() { | ||||
|     } | ||||
| } | ||||
|  | ||||
| void ShaderDiskCacheOpenGL::SaveRaw(const ShaderDiskCacheRaw& entry) { | ||||
| void ShaderDiskCacheOpenGL::SaveEntry(const ShaderDiskCacheEntry& entry) { | ||||
|     if (!is_usable) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const u64 id = entry.GetUniqueIdentifier(); | ||||
|     if (transferable.find(id) != transferable.end()) { | ||||
|     const u64 id = entry.unique_identifier; | ||||
|     if (stored_transferable.find(id) != stored_transferable.end()) { | ||||
|         // The shader already exists | ||||
|         return; | ||||
|     } | ||||
| @@ -361,71 +314,17 @@ void ShaderDiskCacheOpenGL::SaveRaw(const ShaderDiskCacheRaw& entry) { | ||||
|     if (!file.IsOpen()) { | ||||
|         return; | ||||
|     } | ||||
|     if (file.WriteObject(TransferableEntryKind::Raw) != 1 || !entry.Save(file)) { | ||||
|     if (!entry.Save(file)) { | ||||
|         LOG_ERROR(Render_OpenGL, "Failed to save raw transferable cache entry, removing"); | ||||
|         file.Close(); | ||||
|         InvalidateTransferable(); | ||||
|         return; | ||||
|     } | ||||
|     transferable.insert({id, {}}); | ||||
|  | ||||
|     stored_transferable.insert(id); | ||||
| } | ||||
|  | ||||
| void ShaderDiskCacheOpenGL::SaveUsage(const ShaderDiskCacheUsage& usage) { | ||||
|     if (!is_usable) { | ||||
|         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}; | ||||
|     if (usages.find(usage) != usages.end()) { | ||||
|         // Skip this variant since the shader is already stored. | ||||
|         return; | ||||
|     } | ||||
|     usages.insert(usage); | ||||
|  | ||||
|     FileUtil::IOFile file = AppendTransferableFile(); | ||||
|     if (!file.IsOpen()) | ||||
|         return; | ||||
|     const auto Close = [&] { | ||||
|         LOG_ERROR(Render_OpenGL, "Failed to save usage transferable cache entry, removing"); | ||||
|         file.Close(); | ||||
|         InvalidateTransferable(); | ||||
|     }; | ||||
|  | ||||
|     if (file.WriteObject(TransferableEntryKind::Usage) != 1 || | ||||
|         file.WriteObject(usage.unique_identifier) != 1 || file.WriteObject(usage.variant) != 1 || | ||||
|         file.WriteObject(usage.bound_buffer) != 1 || | ||||
|         file.WriteObject(static_cast<u32>(usage.keys.size())) != 1 || | ||||
|         file.WriteObject(static_cast<u32>(usage.bound_samplers.size())) != 1 || | ||||
|         file.WriteObject(static_cast<u32>(usage.bindless_samplers.size())) != 1) { | ||||
|         Close(); | ||||
|         return; | ||||
|     } | ||||
|     for (const auto& [pair, value] : usage.keys) { | ||||
|         const auto [cbuf, offset] = pair; | ||||
|         if (file.WriteObject(ConstBufferKey{cbuf, offset, value}) != 1) { | ||||
|             Close(); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|     for (const auto& [offset, sampler] : usage.bound_samplers) { | ||||
|         if (file.WriteObject(BoundSamplerKey{offset, sampler}) != 1) { | ||||
|             Close(); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|     for (const auto& [pair, sampler] : usage.bindless_samplers) { | ||||
|         const auto [cbuf, offset] = pair; | ||||
|         if (file.WriteObject(BindlessSamplerKey{cbuf, offset, sampler}) != 1) { | ||||
|             Close(); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void ShaderDiskCacheOpenGL::SaveDump(const ShaderDiskCacheUsage& usage, GLuint program) { | ||||
| void ShaderDiskCacheOpenGL::SavePrecompiled(u64 unique_identifier, GLuint program) { | ||||
|     if (!is_usable) { | ||||
|         return; | ||||
|     } | ||||
| @@ -437,51 +336,19 @@ void ShaderDiskCacheOpenGL::SaveDump(const ShaderDiskCacheUsage& usage, GLuint p | ||||
|         SavePrecompiledHeaderToVirtualPrecompiledCache(); | ||||
|     } | ||||
|  | ||||
|     GLint binary_length{}; | ||||
|     GLint binary_length; | ||||
|     glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &binary_length); | ||||
|  | ||||
|     GLenum binary_format{}; | ||||
|     GLenum binary_format; | ||||
|     std::vector<u8> binary(binary_length); | ||||
|     glGetProgramBinary(program, binary_length, nullptr, &binary_format, binary.data()); | ||||
|  | ||||
|     const auto Close = [&] { | ||||
|         LOG_ERROR(Render_OpenGL, "Failed to save binary program file in shader={:016X}, removing", | ||||
|                   usage.unique_identifier); | ||||
|         InvalidatePrecompiled(); | ||||
|     }; | ||||
|  | ||||
|     if (!SaveObjectToPrecompiled(usage.unique_identifier) || | ||||
|         !SaveObjectToPrecompiled(usage.variant) || !SaveObjectToPrecompiled(usage.bound_buffer) || | ||||
|         !SaveObjectToPrecompiled(static_cast<u32>(usage.keys.size())) || | ||||
|         !SaveObjectToPrecompiled(static_cast<u32>(usage.bound_samplers.size())) || | ||||
|         !SaveObjectToPrecompiled(static_cast<u32>(usage.bindless_samplers.size()))) { | ||||
|         Close(); | ||||
|         return; | ||||
|     } | ||||
|     for (const auto& [pair, value] : usage.keys) { | ||||
|         const auto [cbuf, offset] = pair; | ||||
|         if (SaveObjectToPrecompiled(ConstBufferKey{cbuf, offset, value}) != 1) { | ||||
|             Close(); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|     for (const auto& [offset, sampler] : usage.bound_samplers) { | ||||
|         if (SaveObjectToPrecompiled(BoundSamplerKey{offset, sampler}) != 1) { | ||||
|             Close(); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|     for (const auto& [pair, sampler] : usage.bindless_samplers) { | ||||
|         const auto [cbuf, offset] = pair; | ||||
|         if (SaveObjectToPrecompiled(BindlessSamplerKey{cbuf, offset, sampler}) != 1) { | ||||
|             Close(); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|     if (!SaveObjectToPrecompiled(static_cast<u32>(binary_format)) || | ||||
|         !SaveObjectToPrecompiled(static_cast<u32>(binary_length)) || | ||||
|     if (!SaveObjectToPrecompiled(unique_identifier) || !SaveObjectToPrecompiled(binary_format) || | ||||
|         !SaveObjectToPrecompiled(static_cast<u32>(binary.size())) || | ||||
|         !SaveArrayToPrecompiled(binary.data(), binary.size())) { | ||||
|         Close(); | ||||
|         LOG_ERROR(Render_OpenGL, "Failed to save binary program file in shader={:016X}, removing", | ||||
|                   unique_identifier); | ||||
|         InvalidatePrecompiled(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -534,7 +401,6 @@ void ShaderDiskCacheOpenGL::SaveVirtualPrecompiledFile() { | ||||
|     if (file.WriteBytes(compressed.data(), compressed.size()) != compressed.size()) { | ||||
|         LOG_ERROR(Render_OpenGL, "Failed to write precompiled cache version in path={}", | ||||
|                   precompiled_path); | ||||
|         return; | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -19,8 +19,7 @@ | ||||
| #include "common/common_types.h" | ||||
| #include "core/file_sys/vfs_vector.h" | ||||
| #include "video_core/engines/shader_type.h" | ||||
| #include "video_core/renderer_opengl/gl_shader_gen.h" | ||||
| #include "video_core/shader/const_buffer_locker.h" | ||||
| #include "video_core/shader/registry.h" | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| @@ -32,139 +31,39 @@ class IOFile; | ||||
|  | ||||
| namespace OpenGL { | ||||
|  | ||||
| struct ShaderDiskCacheUsage; | ||||
| struct ShaderDiskCacheDump; | ||||
|  | ||||
| using ProgramCode = std::vector<u64>; | ||||
| using ShaderDumpsMap = std::unordered_map<ShaderDiskCacheUsage, ShaderDiskCacheDump>; | ||||
|  | ||||
| /// Describes the different variants a program can be compiled with. | ||||
| struct ProgramVariant final { | ||||
|     ProgramVariant() = default; | ||||
|  | ||||
|     /// Graphics constructor. | ||||
|     explicit constexpr ProgramVariant(GLenum primitive_mode) noexcept | ||||
|         : primitive_mode{primitive_mode} {} | ||||
|  | ||||
|     /// Compute constructor. | ||||
|     explicit constexpr ProgramVariant(u32 block_x, u32 block_y, u32 block_z, u32 shared_memory_size, | ||||
|                                       u32 local_memory_size) noexcept | ||||
|         : block_x{block_x}, block_y{static_cast<u16>(block_y)}, block_z{static_cast<u16>(block_z)}, | ||||
|           shared_memory_size{shared_memory_size}, local_memory_size{local_memory_size} {} | ||||
|  | ||||
|     // Graphics specific parameters. | ||||
|     GLenum primitive_mode{}; | ||||
|  | ||||
|     // Compute specific parameters. | ||||
|     u32 block_x{}; | ||||
|     u16 block_y{}; | ||||
|     u16 block_z{}; | ||||
|     u32 shared_memory_size{}; | ||||
|     u32 local_memory_size{}; | ||||
|  | ||||
|     bool operator==(const ProgramVariant& rhs) const noexcept { | ||||
|         return std::tie(primitive_mode, block_x, block_y, block_z, shared_memory_size, | ||||
|                         local_memory_size) == std::tie(rhs.primitive_mode, rhs.block_x, rhs.block_y, | ||||
|                                                        rhs.block_z, rhs.shared_memory_size, | ||||
|                                                        rhs.local_memory_size); | ||||
|     } | ||||
|  | ||||
|     bool operator!=(const ProgramVariant& rhs) const noexcept { | ||||
|         return !operator==(rhs); | ||||
|     } | ||||
| }; | ||||
| static_assert(std::is_trivially_copyable_v<ProgramVariant>); | ||||
|  | ||||
| /// Describes how a shader is used. | ||||
| struct ShaderDiskCacheUsage { | ||||
|     u64 unique_identifier{}; | ||||
|     ProgramVariant variant; | ||||
|     u32 bound_buffer{}; | ||||
|     VideoCommon::Shader::KeyMap keys; | ||||
|     VideoCommon::Shader::BoundSamplerMap bound_samplers; | ||||
|     VideoCommon::Shader::BindlessSamplerMap bindless_samplers; | ||||
|  | ||||
|     bool operator==(const ShaderDiskCacheUsage& rhs) const { | ||||
|         return std::tie(unique_identifier, variant, keys, bound_samplers, bindless_samplers) == | ||||
|                std::tie(rhs.unique_identifier, rhs.variant, rhs.keys, rhs.bound_samplers, | ||||
|                         rhs.bindless_samplers); | ||||
|     } | ||||
|  | ||||
|     bool operator!=(const ShaderDiskCacheUsage& rhs) const { | ||||
|         return !operator==(rhs); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| } // namespace OpenGL | ||||
|  | ||||
| namespace std { | ||||
|  | ||||
| template <> | ||||
| struct hash<OpenGL::ProgramVariant> { | ||||
|     std::size_t operator()(const OpenGL::ProgramVariant& variant) const noexcept { | ||||
|         return (static_cast<std::size_t>(variant.primitive_mode) << 6) ^ | ||||
|                static_cast<std::size_t>(variant.block_x) ^ | ||||
|                (static_cast<std::size_t>(variant.block_y) << 32) ^ | ||||
|                (static_cast<std::size_t>(variant.block_z) << 48) ^ | ||||
|                (static_cast<std::size_t>(variant.shared_memory_size) << 16) ^ | ||||
|                (static_cast<std::size_t>(variant.local_memory_size) << 36); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| template <> | ||||
| struct hash<OpenGL::ShaderDiskCacheUsage> { | ||||
|     std::size_t operator()(const OpenGL::ShaderDiskCacheUsage& usage) const noexcept { | ||||
|         return static_cast<std::size_t>(usage.unique_identifier) ^ | ||||
|                std::hash<OpenGL::ProgramVariant>{}(usage.variant); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| } // namespace std | ||||
|  | ||||
| namespace OpenGL { | ||||
|  | ||||
| /// Describes a shader how it's used by the guest GPU | ||||
| class ShaderDiskCacheRaw { | ||||
| public: | ||||
|     explicit ShaderDiskCacheRaw(u64 unique_identifier, Tegra::Engines::ShaderType type, | ||||
|                                 ProgramCode code, ProgramCode code_b = {}); | ||||
|     ShaderDiskCacheRaw(); | ||||
|     ~ShaderDiskCacheRaw(); | ||||
| /// Describes a shader and how it's used by the guest GPU | ||||
| struct ShaderDiskCacheEntry { | ||||
|     ShaderDiskCacheEntry(); | ||||
|     ~ShaderDiskCacheEntry(); | ||||
|  | ||||
|     bool Load(FileUtil::IOFile& file); | ||||
|  | ||||
|     bool Save(FileUtil::IOFile& file) const; | ||||
|  | ||||
|     u64 GetUniqueIdentifier() const { | ||||
|         return unique_identifier; | ||||
|     } | ||||
|  | ||||
|     bool HasProgramA() const { | ||||
|         return !code.empty() && !code_b.empty(); | ||||
|     } | ||||
|  | ||||
|     Tegra::Engines::ShaderType GetType() const { | ||||
|         return type; | ||||
|     } | ||||
|  | ||||
|     const ProgramCode& GetCode() const { | ||||
|         return code; | ||||
|     } | ||||
|  | ||||
|     const ProgramCode& GetCodeB() const { | ||||
|         return code_b; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     u64 unique_identifier{}; | ||||
|     Tegra::Engines::ShaderType type{}; | ||||
|     ProgramCode code; | ||||
|     ProgramCode code_b; | ||||
|  | ||||
|     u64 unique_identifier = 0; | ||||
|     std::optional<u32> texture_handler_size; | ||||
|     u32 bound_buffer = 0; | ||||
|     VideoCommon::Shader::GraphicsInfo graphics_info; | ||||
|     VideoCommon::Shader::ComputeInfo compute_info; | ||||
|     VideoCommon::Shader::KeyMap keys; | ||||
|     VideoCommon::Shader::BoundSamplerMap bound_samplers; | ||||
|     VideoCommon::Shader::BindlessSamplerMap bindless_samplers; | ||||
| }; | ||||
|  | ||||
| /// Contains an OpenGL dumped binary program | ||||
| struct ShaderDiskCacheDump { | ||||
|     GLenum binary_format{}; | ||||
| struct ShaderDiskCachePrecompiled { | ||||
|     u64 unique_identifier = 0; | ||||
|     GLenum binary_format = 0; | ||||
|     std::vector<u8> binary; | ||||
| }; | ||||
|  | ||||
| @@ -174,11 +73,10 @@ public: | ||||
|     ~ShaderDiskCacheOpenGL(); | ||||
|  | ||||
|     /// Loads transferable cache. If file has a old version or on failure, it deletes the file. | ||||
|     std::optional<std::pair<std::vector<ShaderDiskCacheRaw>, std::vector<ShaderDiskCacheUsage>>> | ||||
|     LoadTransferable(); | ||||
|     std::optional<std::vector<ShaderDiskCacheEntry>> LoadTransferable(); | ||||
|  | ||||
|     /// Loads current game's precompiled cache. Invalidates on failure. | ||||
|     std::unordered_map<ShaderDiskCacheUsage, ShaderDiskCacheDump> LoadPrecompiled(); | ||||
|     std::vector<ShaderDiskCachePrecompiled> LoadPrecompiled(); | ||||
|  | ||||
|     /// Removes the transferable (and precompiled) cache file. | ||||
|     void InvalidateTransferable(); | ||||
| @@ -187,21 +85,18 @@ public: | ||||
|     void InvalidatePrecompiled(); | ||||
|  | ||||
|     /// 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); | ||||
|     void SaveEntry(const ShaderDiskCacheEntry& entry); | ||||
|  | ||||
|     /// Saves a dump entry to the precompiled file. Does not check for collisions. | ||||
|     void SaveDump(const ShaderDiskCacheUsage& usage, GLuint program); | ||||
|     void SavePrecompiled(u64 unique_identifier, GLuint program); | ||||
|  | ||||
|     /// Serializes virtual precompiled shader cache file to real file | ||||
|     void SaveVirtualPrecompiledFile(); | ||||
|  | ||||
| private: | ||||
|     /// Loads the transferable cache. Returns empty on failure. | ||||
|     std::optional<std::unordered_map<ShaderDiskCacheUsage, ShaderDiskCacheDump>> | ||||
|     LoadPrecompiledFile(FileUtil::IOFile& file); | ||||
|     std::optional<std::vector<ShaderDiskCachePrecompiled>> LoadPrecompiledFile( | ||||
|         FileUtil::IOFile& file); | ||||
|  | ||||
|     /// Opens current game's transferable file and write it's header if it doesn't exist | ||||
|     FileUtil::IOFile AppendTransferableFile() const; | ||||
| @@ -270,7 +165,7 @@ private: | ||||
|     std::size_t precompiled_cache_virtual_file_offset = 0; | ||||
|  | ||||
|     // Stored transferable shaders | ||||
|     std::unordered_map<u64, std::unordered_set<ShaderDiskCacheUsage>> transferable; | ||||
|     std::unordered_set<u64> stored_transferable; | ||||
|  | ||||
|     // The cache has been loaded at boot | ||||
|     bool is_usable{}; | ||||
|   | ||||
| @@ -1,109 +0,0 @@ | ||||
| // Copyright 2018 yuzu Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include <string> | ||||
|  | ||||
| #include <fmt/format.h> | ||||
|  | ||||
| #include "video_core/engines/maxwell_3d.h" | ||||
| #include "video_core/engines/shader_type.h" | ||||
| #include "video_core/renderer_opengl/gl_device.h" | ||||
| #include "video_core/renderer_opengl/gl_shader_decompiler.h" | ||||
| #include "video_core/renderer_opengl/gl_shader_gen.h" | ||||
| #include "video_core/shader/shader_ir.h" | ||||
|  | ||||
| namespace OpenGL::GLShader { | ||||
|  | ||||
| using Tegra::Engines::Maxwell3D; | ||||
| using Tegra::Engines::ShaderType; | ||||
| using VideoCommon::Shader::CompileDepth; | ||||
| using VideoCommon::Shader::CompilerSettings; | ||||
| using VideoCommon::Shader::ProgramCode; | ||||
| using VideoCommon::Shader::ShaderIR; | ||||
|  | ||||
| std::string GenerateVertexShader(const Device& device, const ShaderIR& ir, const ShaderIR* ir_b) { | ||||
|     std::string out = GetCommonDeclarations(); | ||||
|     out += fmt::format(R"( | ||||
| layout (std140, binding = {}) uniform vs_config {{ | ||||
|     float y_direction; | ||||
| }}; | ||||
|  | ||||
| )", | ||||
|                        EmulationUniformBlockBinding); | ||||
|     out += Decompile(device, ir, ShaderType::Vertex, "vertex"); | ||||
|     if (ir_b) { | ||||
|         out += Decompile(device, *ir_b, ShaderType::Vertex, "vertex_b"); | ||||
|     } | ||||
|  | ||||
|     out += R"( | ||||
| void main() { | ||||
|     gl_Position = vec4(0.0f, 0.0f, 0.0f, 1.0f); | ||||
|     execute_vertex(); | ||||
| )"; | ||||
|     if (ir_b) { | ||||
|         out += "    execute_vertex_b();"; | ||||
|     } | ||||
|     out += "}\n"; | ||||
|     return out; | ||||
| } | ||||
|  | ||||
| std::string GenerateGeometryShader(const Device& device, const ShaderIR& ir) { | ||||
|     std::string out = GetCommonDeclarations(); | ||||
|     out += fmt::format(R"( | ||||
| layout (std140, binding = {}) uniform gs_config {{ | ||||
|     float y_direction; | ||||
| }}; | ||||
|  | ||||
| )", | ||||
|                        EmulationUniformBlockBinding); | ||||
|     out += Decompile(device, ir, ShaderType::Geometry, "geometry"); | ||||
|  | ||||
|     out += R"( | ||||
| void main() { | ||||
|     execute_geometry(); | ||||
| } | ||||
| )"; | ||||
|     return out; | ||||
| } | ||||
|  | ||||
| std::string GenerateFragmentShader(const Device& device, const ShaderIR& ir) { | ||||
|     std::string out = GetCommonDeclarations(); | ||||
|     out += fmt::format(R"( | ||||
| layout (location = 0) out vec4 FragColor0; | ||||
| layout (location = 1) out vec4 FragColor1; | ||||
| layout (location = 2) out vec4 FragColor2; | ||||
| layout (location = 3) out vec4 FragColor3; | ||||
| layout (location = 4) out vec4 FragColor4; | ||||
| layout (location = 5) out vec4 FragColor5; | ||||
| layout (location = 6) out vec4 FragColor6; | ||||
| layout (location = 7) out vec4 FragColor7; | ||||
|  | ||||
| layout (std140, binding = {}) uniform fs_config {{ | ||||
|     float y_direction; | ||||
| }}; | ||||
|  | ||||
| )", | ||||
|                        EmulationUniformBlockBinding); | ||||
|     out += Decompile(device, ir, ShaderType::Fragment, "fragment"); | ||||
|  | ||||
|     out += R"( | ||||
| void main() { | ||||
|     execute_fragment(); | ||||
| } | ||||
| )"; | ||||
|     return out; | ||||
| } | ||||
|  | ||||
| std::string GenerateComputeShader(const Device& device, const ShaderIR& ir) { | ||||
|     std::string out = GetCommonDeclarations(); | ||||
|     out += Decompile(device, ir, ShaderType::Compute, "compute"); | ||||
|     out += R"( | ||||
| void main() { | ||||
|     execute_compute(); | ||||
| } | ||||
| )"; | ||||
|     return out; | ||||
| } | ||||
|  | ||||
| } // namespace OpenGL::GLShader | ||||
| @@ -1,34 +0,0 @@ | ||||
| // Copyright 2018 yuzu Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <vector> | ||||
|  | ||||
| #include "common/common_types.h" | ||||
| #include "video_core/renderer_opengl/gl_shader_decompiler.h" | ||||
| #include "video_core/shader/shader_ir.h" | ||||
|  | ||||
| namespace OpenGL { | ||||
| class Device; | ||||
| } | ||||
|  | ||||
| namespace OpenGL::GLShader { | ||||
|  | ||||
| using VideoCommon::Shader::ProgramCode; | ||||
| using VideoCommon::Shader::ShaderIR; | ||||
|  | ||||
| /// Generates the GLSL vertex shader program source code for the given VS program | ||||
| std::string GenerateVertexShader(const Device& device, const ShaderIR& ir, const ShaderIR* ir_b); | ||||
|  | ||||
| /// Generates the GLSL geometry shader program source code for the given GS program | ||||
| std::string GenerateGeometryShader(const Device& device, const ShaderIR& ir); | ||||
|  | ||||
| /// Generates the GLSL fragment shader program source code for the given FS program | ||||
| std::string GenerateFragmentShader(const Device& device, const ShaderIR& ir); | ||||
|  | ||||
| /// Generates the GLSL compute shader program source code for the given CS program | ||||
| std::string GenerateComputeShader(const Device& device, const ShaderIR& ir); | ||||
|  | ||||
| } // namespace OpenGL::GLShader | ||||
| @@ -161,8 +161,8 @@ CachedShader::CachedShader(Core::System& system, Tegra::Engines::ShaderType stag | ||||
|                            GPUVAddr gpu_addr, VAddr cpu_addr, u8* host_ptr, | ||||
|                            ProgramCode program_code, u32 main_offset) | ||||
|     : RasterizerCacheObject{host_ptr}, gpu_addr{gpu_addr}, cpu_addr{cpu_addr}, | ||||
|       program_code{std::move(program_code)}, locker{stage, GetEngine(system, stage)}, | ||||
|       shader_ir{this->program_code, main_offset, compiler_settings, locker}, | ||||
|       program_code{std::move(program_code)}, registry{stage, GetEngine(system, stage)}, | ||||
|       shader_ir{this->program_code, main_offset, compiler_settings, registry}, | ||||
|       entries{GenerateShaderEntries(shader_ir)} {} | ||||
|  | ||||
| CachedShader::~CachedShader() = default; | ||||
|   | ||||
| @@ -25,7 +25,7 @@ | ||||
| #include "video_core/renderer_vulkan/vk_renderpass_cache.h" | ||||
| #include "video_core/renderer_vulkan/vk_resource_manager.h" | ||||
| #include "video_core/renderer_vulkan/vk_shader_decompiler.h" | ||||
| #include "video_core/shader/const_buffer_locker.h" | ||||
| #include "video_core/shader/registry.h" | ||||
| #include "video_core/shader/shader_ir.h" | ||||
| #include "video_core/surface.h" | ||||
|  | ||||
| @@ -147,7 +147,7 @@ private: | ||||
|     GPUVAddr gpu_addr{}; | ||||
|     VAddr cpu_addr{}; | ||||
|     ProgramCode program_code; | ||||
|     VideoCommon::Shader::ConstBufferLocker locker; | ||||
|     VideoCommon::Shader::Registry registry; | ||||
|     VideoCommon::Shader::ShaderIR shader_ir; | ||||
|     ShaderEntries entries; | ||||
| }; | ||||
|   | ||||
| @@ -1,126 +0,0 @@ | ||||
| // Copyright 2019 yuzu Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <tuple> | ||||
|  | ||||
| #include "common/common_types.h" | ||||
| #include "video_core/engines/maxwell_3d.h" | ||||
| #include "video_core/engines/shader_type.h" | ||||
| #include "video_core/shader/const_buffer_locker.h" | ||||
|  | ||||
| namespace VideoCommon::Shader { | ||||
|  | ||||
| using Tegra::Engines::SamplerDescriptor; | ||||
|  | ||||
| ConstBufferLocker::ConstBufferLocker(Tegra::Engines::ShaderType shader_stage) | ||||
|     : stage{shader_stage} {} | ||||
|  | ||||
| ConstBufferLocker::ConstBufferLocker(Tegra::Engines::ShaderType shader_stage, | ||||
|                                      Tegra::Engines::ConstBufferEngineInterface& engine) | ||||
|     : stage{shader_stage}, engine{&engine} {} | ||||
|  | ||||
| ConstBufferLocker::~ConstBufferLocker() = default; | ||||
|  | ||||
| std::optional<u32> ConstBufferLocker::ObtainKey(u32 buffer, u32 offset) { | ||||
|     const std::pair<u32, u32> key = {buffer, offset}; | ||||
|     const auto iter = keys.find(key); | ||||
|     if (iter != keys.end()) { | ||||
|         return iter->second; | ||||
|     } | ||||
|     if (!engine) { | ||||
|         return std::nullopt; | ||||
|     } | ||||
|     const u32 value = engine->AccessConstBuffer32(stage, buffer, offset); | ||||
|     keys.emplace(key, value); | ||||
|     return value; | ||||
| } | ||||
|  | ||||
| std::optional<SamplerDescriptor> ConstBufferLocker::ObtainBoundSampler(u32 offset) { | ||||
|     const u32 key = offset; | ||||
|     const auto iter = bound_samplers.find(key); | ||||
|     if (iter != bound_samplers.end()) { | ||||
|         return iter->second; | ||||
|     } | ||||
|     if (!engine) { | ||||
|         return std::nullopt; | ||||
|     } | ||||
|     const SamplerDescriptor value = engine->AccessBoundSampler(stage, offset); | ||||
|     bound_samplers.emplace(key, value); | ||||
|     return value; | ||||
| } | ||||
|  | ||||
| std::optional<Tegra::Engines::SamplerDescriptor> ConstBufferLocker::ObtainBindlessSampler( | ||||
|     u32 buffer, u32 offset) { | ||||
|     const std::pair key = {buffer, offset}; | ||||
|     const auto iter = bindless_samplers.find(key); | ||||
|     if (iter != bindless_samplers.end()) { | ||||
|         return iter->second; | ||||
|     } | ||||
|     if (!engine) { | ||||
|         return std::nullopt; | ||||
|     } | ||||
|     const SamplerDescriptor value = engine->AccessBindlessSampler(stage, buffer, offset); | ||||
|     bindless_samplers.emplace(key, value); | ||||
|     return value; | ||||
| } | ||||
|  | ||||
| std::optional<u32> ConstBufferLocker::ObtainBoundBuffer() { | ||||
|     if (bound_buffer_saved) { | ||||
|         return bound_buffer; | ||||
|     } | ||||
|     if (!engine) { | ||||
|         return std::nullopt; | ||||
|     } | ||||
|     bound_buffer_saved = true; | ||||
|     bound_buffer = engine->GetBoundBuffer(); | ||||
|     return bound_buffer; | ||||
| } | ||||
|  | ||||
| void ConstBufferLocker::InsertKey(u32 buffer, u32 offset, u32 value) { | ||||
|     keys.insert_or_assign({buffer, offset}, value); | ||||
| } | ||||
|  | ||||
| void ConstBufferLocker::InsertBoundSampler(u32 offset, SamplerDescriptor sampler) { | ||||
|     bound_samplers.insert_or_assign(offset, sampler); | ||||
| } | ||||
|  | ||||
| void ConstBufferLocker::InsertBindlessSampler(u32 buffer, u32 offset, SamplerDescriptor sampler) { | ||||
|     bindless_samplers.insert_or_assign({buffer, offset}, sampler); | ||||
| } | ||||
|  | ||||
| void ConstBufferLocker::SetBoundBuffer(u32 buffer) { | ||||
|     bound_buffer_saved = true; | ||||
|     bound_buffer = buffer; | ||||
| } | ||||
|  | ||||
| bool ConstBufferLocker::IsConsistent() const { | ||||
|     if (!engine) { | ||||
|         return false; | ||||
|     } | ||||
|     return std::all_of(keys.begin(), keys.end(), | ||||
|                        [this](const auto& pair) { | ||||
|                            const auto [cbuf, offset] = pair.first; | ||||
|                            const auto value = pair.second; | ||||
|                            return value == engine->AccessConstBuffer32(stage, cbuf, offset); | ||||
|                        }) && | ||||
|            std::all_of(bound_samplers.begin(), bound_samplers.end(), | ||||
|                        [this](const auto& sampler) { | ||||
|                            const auto [key, value] = sampler; | ||||
|                            return value == engine->AccessBoundSampler(stage, key); | ||||
|                        }) && | ||||
|            std::all_of(bindless_samplers.begin(), bindless_samplers.end(), | ||||
|                        [this](const auto& sampler) { | ||||
|                            const auto [cbuf, offset] = sampler.first; | ||||
|                            const auto value = sampler.second; | ||||
|                            return value == engine->AccessBindlessSampler(stage, cbuf, offset); | ||||
|                        }); | ||||
| } | ||||
|  | ||||
| bool ConstBufferLocker::HasEqualKeys(const ConstBufferLocker& rhs) const { | ||||
|     return std::tie(keys, bound_samplers, bindless_samplers) == | ||||
|            std::tie(rhs.keys, rhs.bound_samplers, rhs.bindless_samplers); | ||||
| } | ||||
|  | ||||
| } // namespace VideoCommon::Shader | ||||
| @@ -1,103 +0,0 @@ | ||||
| // Copyright 2019 yuzu Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <optional> | ||||
| #include <unordered_map> | ||||
| #include "common/common_types.h" | ||||
| #include "common/hash.h" | ||||
| #include "video_core/engines/const_buffer_engine_interface.h" | ||||
| #include "video_core/engines/shader_type.h" | ||||
| #include "video_core/guest_driver.h" | ||||
|  | ||||
| namespace VideoCommon::Shader { | ||||
|  | ||||
| using KeyMap = std::unordered_map<std::pair<u32, u32>, u32, Common::PairHash>; | ||||
| using BoundSamplerMap = std::unordered_map<u32, Tegra::Engines::SamplerDescriptor>; | ||||
| using BindlessSamplerMap = | ||||
|     std::unordered_map<std::pair<u32, u32>, Tegra::Engines::SamplerDescriptor, Common::PairHash>; | ||||
|  | ||||
| /** | ||||
|  * The ConstBufferLocker is a class use to interface the 3D and compute engines with the shader | ||||
|  * compiler. with it, the shader can obtain required data from GPU state and store it for disk | ||||
|  * shader compilation. | ||||
|  */ | ||||
| class ConstBufferLocker { | ||||
| public: | ||||
|     explicit ConstBufferLocker(Tegra::Engines::ShaderType shader_stage); | ||||
|  | ||||
|     explicit ConstBufferLocker(Tegra::Engines::ShaderType shader_stage, | ||||
|                                Tegra::Engines::ConstBufferEngineInterface& engine); | ||||
|  | ||||
|     ~ConstBufferLocker(); | ||||
|  | ||||
|     /// Retrieves a key from the locker, if it's registered, it will give the registered value, if | ||||
|     /// not it will obtain it from maxwell3d and register it. | ||||
|     std::optional<u32> ObtainKey(u32 buffer, u32 offset); | ||||
|  | ||||
|     std::optional<Tegra::Engines::SamplerDescriptor> ObtainBoundSampler(u32 offset); | ||||
|  | ||||
|     std::optional<Tegra::Engines::SamplerDescriptor> ObtainBindlessSampler(u32 buffer, u32 offset); | ||||
|  | ||||
|     std::optional<u32> ObtainBoundBuffer(); | ||||
|  | ||||
|     /// Inserts a key. | ||||
|     void InsertKey(u32 buffer, u32 offset, u32 value); | ||||
|  | ||||
|     /// Inserts a bound sampler key. | ||||
|     void InsertBoundSampler(u32 offset, Tegra::Engines::SamplerDescriptor sampler); | ||||
|  | ||||
|     /// Inserts a bindless sampler key. | ||||
|     void InsertBindlessSampler(u32 buffer, u32 offset, Tegra::Engines::SamplerDescriptor sampler); | ||||
|  | ||||
|     /// Set the bound buffer for this locker. | ||||
|     void SetBoundBuffer(u32 buffer); | ||||
|  | ||||
|     /// Checks keys and samplers against engine's current const buffers. Returns true if they are | ||||
|     /// the same value, false otherwise; | ||||
|     bool IsConsistent() const; | ||||
|  | ||||
|     /// Returns true if the keys are equal to the other ones in the locker. | ||||
|     bool HasEqualKeys(const ConstBufferLocker& rhs) const; | ||||
|  | ||||
|     /// Gives an getter to the const buffer keys in the database. | ||||
|     const KeyMap& GetKeys() const { | ||||
|         return keys; | ||||
|     } | ||||
|  | ||||
|     /// Gets samplers database. | ||||
|     const BoundSamplerMap& GetBoundSamplers() const { | ||||
|         return bound_samplers; | ||||
|     } | ||||
|  | ||||
|     /// Gets bindless samplers database. | ||||
|     const BindlessSamplerMap& GetBindlessSamplers() const { | ||||
|         return bindless_samplers; | ||||
|     } | ||||
|  | ||||
|     /// Gets bound buffer used on this shader | ||||
|     u32 GetBoundBuffer() const { | ||||
|         return bound_buffer; | ||||
|     } | ||||
|  | ||||
|     /// Obtains access to the guest driver's profile. | ||||
|     VideoCore::GuestDriverProfile* AccessGuestDriverProfile() const { | ||||
|         if (engine) { | ||||
|             return &engine->AccessGuestDriverProfile(); | ||||
|         } | ||||
|         return nullptr; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     const Tegra::Engines::ShaderType stage; | ||||
|     Tegra::Engines::ConstBufferEngineInterface* engine = nullptr; | ||||
|     KeyMap keys; | ||||
|     BoundSamplerMap bound_samplers; | ||||
|     BindlessSamplerMap bindless_samplers; | ||||
|     bool bound_buffer_saved{}; | ||||
|     u32 bound_buffer{}; | ||||
| }; | ||||
|  | ||||
| } // namespace VideoCommon::Shader | ||||
| @@ -13,6 +13,7 @@ | ||||
| #include "common/common_types.h" | ||||
| #include "video_core/shader/ast.h" | ||||
| #include "video_core/shader/control_flow.h" | ||||
| #include "video_core/shader/registry.h" | ||||
| #include "video_core/shader/shader_ir.h" | ||||
|  | ||||
| namespace VideoCommon::Shader { | ||||
| @@ -64,11 +65,11 @@ struct BlockInfo { | ||||
| }; | ||||
|  | ||||
| struct CFGRebuildState { | ||||
|     explicit CFGRebuildState(const ProgramCode& program_code, u32 start, ConstBufferLocker& locker) | ||||
|         : program_code{program_code}, locker{locker}, start{start} {} | ||||
|     explicit CFGRebuildState(const ProgramCode& program_code, u32 start, Registry& registry) | ||||
|         : program_code{program_code}, registry{registry}, start{start} {} | ||||
|  | ||||
|     const ProgramCode& program_code; | ||||
|     ConstBufferLocker& locker; | ||||
|     Registry& registry; | ||||
|     u32 start{}; | ||||
|     std::vector<BlockInfo> block_info; | ||||
|     std::list<u32> inspect_queries; | ||||
| @@ -438,7 +439,7 @@ std::pair<ParseResult, ParseInfo> ParseCode(CFGRebuildState& state, u32 address) | ||||
|             const s32 pc_target = offset + result.relative_position; | ||||
|             std::vector<CaseBranch> branches; | ||||
|             for (u32 i = 0; i < result.entries; i++) { | ||||
|                 auto key = state.locker.ObtainKey(result.buffer, result.offset + i * 4); | ||||
|                 auto key = state.registry.ObtainKey(result.buffer, result.offset + i * 4); | ||||
|                 if (!key) { | ||||
|                     return {ParseResult::AbnormalFlow, parse_info}; | ||||
|                 } | ||||
| @@ -656,14 +657,14 @@ void DecompileShader(CFGRebuildState& state) { | ||||
|  | ||||
| std::unique_ptr<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code, u32 start_address, | ||||
|                                                 const CompilerSettings& settings, | ||||
|                                                 ConstBufferLocker& locker) { | ||||
|                                                 Registry& registry) { | ||||
|     auto result_out = std::make_unique<ShaderCharacteristics>(); | ||||
|     if (settings.depth == CompileDepth::BruteForce) { | ||||
|         result_out->settings.depth = CompileDepth::BruteForce; | ||||
|         return result_out; | ||||
|     } | ||||
|  | ||||
|     CFGRebuildState state{program_code, start_address, locker}; | ||||
|     CFGRebuildState state{program_code, start_address, registry}; | ||||
|     // Inspect Code and generate blocks | ||||
|     state.labels.clear(); | ||||
|     state.labels.emplace(start_address); | ||||
|   | ||||
| @@ -12,6 +12,7 @@ | ||||
| #include "video_core/engines/shader_bytecode.h" | ||||
| #include "video_core/shader/ast.h" | ||||
| #include "video_core/shader/compiler_settings.h" | ||||
| #include "video_core/shader/registry.h" | ||||
| #include "video_core/shader/shader_ir.h" | ||||
|  | ||||
| namespace VideoCommon::Shader { | ||||
| @@ -111,6 +112,6 @@ struct ShaderCharacteristics { | ||||
|  | ||||
| std::unique_ptr<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code, u32 start_address, | ||||
|                                                 const CompilerSettings& settings, | ||||
|                                                 ConstBufferLocker& locker); | ||||
|                                                 Registry& registry); | ||||
|  | ||||
| } // namespace VideoCommon::Shader | ||||
|   | ||||
| @@ -34,13 +34,9 @@ constexpr bool IsSchedInstruction(u32 offset, u32 main_offset) { | ||||
|     return (absolute_offset % SchedPeriod) == 0; | ||||
| } | ||||
|  | ||||
| void DeduceTextureHandlerSize(VideoCore::GuestDriverProfile* gpu_driver, | ||||
| void DeduceTextureHandlerSize(VideoCore::GuestDriverProfile& gpu_driver, | ||||
|                               const std::list<Sampler>& used_samplers) { | ||||
|     if (gpu_driver == nullptr) { | ||||
|         LOG_CRITICAL(HW_GPU, "GPU driver profile has not been created yet"); | ||||
|         return; | ||||
|     } | ||||
|     if (gpu_driver->TextureHandlerSizeKnown() || used_samplers.size() <= 1) { | ||||
|     if (gpu_driver.IsTextureHandlerSizeKnown() || used_samplers.size() <= 1) { | ||||
|         return; | ||||
|     } | ||||
|     u32 count{}; | ||||
| @@ -53,17 +49,13 @@ void DeduceTextureHandlerSize(VideoCore::GuestDriverProfile* gpu_driver, | ||||
|         bound_offsets.emplace_back(sampler.GetOffset()); | ||||
|     } | ||||
|     if (count > 1) { | ||||
|         gpu_driver->DeduceTextureHandlerSize(std::move(bound_offsets)); | ||||
|         gpu_driver.DeduceTextureHandlerSize(std::move(bound_offsets)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| std::optional<u32> TryDeduceSamplerSize(const Sampler& sampler_to_deduce, | ||||
|                                         VideoCore::GuestDriverProfile* gpu_driver, | ||||
|                                         VideoCore::GuestDriverProfile& gpu_driver, | ||||
|                                         const std::list<Sampler>& used_samplers) { | ||||
|     if (gpu_driver == nullptr) { | ||||
|         LOG_CRITICAL(HW_GPU, "GPU Driver profile has not been created yet"); | ||||
|         return std::nullopt; | ||||
|     } | ||||
|     const u32 base_offset = sampler_to_deduce.GetOffset(); | ||||
|     u32 max_offset{std::numeric_limits<u32>::max()}; | ||||
|     for (const auto& sampler : used_samplers) { | ||||
| @@ -77,7 +69,7 @@ std::optional<u32> TryDeduceSamplerSize(const Sampler& sampler_to_deduce, | ||||
|     if (max_offset == std::numeric_limits<u32>::max()) { | ||||
|         return std::nullopt; | ||||
|     } | ||||
|     return ((max_offset - base_offset) * 4) / gpu_driver->GetTextureHandlerSize(); | ||||
|     return ((max_offset - base_offset) * 4) / gpu_driver.GetTextureHandlerSize(); | ||||
| } | ||||
|  | ||||
| } // Anonymous namespace | ||||
| @@ -149,7 +141,7 @@ void ShaderIR::Decode() { | ||||
|     std::memcpy(&header, program_code.data(), sizeof(Tegra::Shader::Header)); | ||||
|  | ||||
|     decompiled = false; | ||||
|     auto info = ScanFlow(program_code, main_offset, settings, locker); | ||||
|     auto info = ScanFlow(program_code, main_offset, settings, registry); | ||||
|     auto& shader_info = *info; | ||||
|     coverage_begin = shader_info.start; | ||||
|     coverage_end = shader_info.end; | ||||
| @@ -364,7 +356,7 @@ u32 ShaderIR::DecodeInstr(NodeBlock& bb, u32 pc) { | ||||
|  | ||||
| void ShaderIR::PostDecode() { | ||||
|     // Deduce texture handler size if needed | ||||
|     auto gpu_driver = locker.AccessGuestDriverProfile(); | ||||
|     auto gpu_driver = registry.AccessGuestDriverProfile(); | ||||
|     DeduceTextureHandlerSize(gpu_driver, used_samplers); | ||||
|     // Deduce Indexed Samplers | ||||
|     if (!uses_indexed_samplers) { | ||||
|   | ||||
| @@ -12,6 +12,7 @@ | ||||
| #include "common/logging/log.h" | ||||
| #include "video_core/engines/shader_bytecode.h" | ||||
| #include "video_core/shader/node_helper.h" | ||||
| #include "video_core/shader/registry.h" | ||||
| #include "video_core/shader/shader_ir.h" | ||||
|  | ||||
| namespace VideoCommon::Shader { | ||||
| @@ -359,8 +360,8 @@ ShaderIR::SamplerInfo ShaderIR::GetSamplerInfo(std::optional<SamplerInfo> sample | ||||
|     if (sampler_info) { | ||||
|         return *sampler_info; | ||||
|     } | ||||
|     const auto sampler = | ||||
|         buffer ? locker.ObtainBindlessSampler(*buffer, offset) : locker.ObtainBoundSampler(offset); | ||||
|     const auto sampler = buffer ? registry.ObtainBindlessSampler(*buffer, offset) | ||||
|                                 : registry.ObtainBoundSampler(offset); | ||||
|     if (!sampler) { | ||||
|         LOG_WARNING(HW_GPU, "Unknown sampler info"); | ||||
|         return SamplerInfo{TextureType::Texture2D, false, false, false}; | ||||
|   | ||||
							
								
								
									
										161
									
								
								src/video_core/shader/registry.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								src/video_core/shader/registry.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,161 @@ | ||||
| // Copyright 2019 yuzu Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <tuple> | ||||
|  | ||||
| #include "common/assert.h" | ||||
| #include "common/common_types.h" | ||||
| #include "video_core/engines/kepler_compute.h" | ||||
| #include "video_core/engines/maxwell_3d.h" | ||||
| #include "video_core/engines/shader_type.h" | ||||
| #include "video_core/shader/registry.h" | ||||
|  | ||||
| namespace VideoCommon::Shader { | ||||
|  | ||||
| using Tegra::Engines::ConstBufferEngineInterface; | ||||
| using Tegra::Engines::SamplerDescriptor; | ||||
| using Tegra::Engines::ShaderType; | ||||
|  | ||||
| namespace { | ||||
|  | ||||
| GraphicsInfo MakeGraphicsInfo(ShaderType shader_stage, ConstBufferEngineInterface& engine) { | ||||
|     if (shader_stage == ShaderType::Compute) { | ||||
|         return {}; | ||||
|     } | ||||
|     auto& graphics = static_cast<Tegra::Engines::Maxwell3D&>(engine); | ||||
|  | ||||
|     GraphicsInfo info; | ||||
|     info.tfb_layouts = graphics.regs.tfb_layouts; | ||||
|     info.tfb_varying_locs = graphics.regs.tfb_varying_locs; | ||||
|     info.primitive_topology = graphics.regs.draw.topology; | ||||
|     info.tessellation_primitive = graphics.regs.tess_mode.prim; | ||||
|     info.tessellation_spacing = graphics.regs.tess_mode.spacing; | ||||
|     info.tfb_enabled = graphics.regs.tfb_enabled; | ||||
|     info.tessellation_clockwise = graphics.regs.tess_mode.cw; | ||||
|     return info; | ||||
| } | ||||
|  | ||||
| ComputeInfo MakeComputeInfo(ShaderType shader_stage, ConstBufferEngineInterface& engine) { | ||||
|     if (shader_stage != ShaderType::Compute) { | ||||
|         return {}; | ||||
|     } | ||||
|     auto& compute = static_cast<Tegra::Engines::KeplerCompute&>(engine); | ||||
|     const auto& launch = compute.launch_description; | ||||
|  | ||||
|     ComputeInfo info; | ||||
|     info.workgroup_size = {launch.block_dim_x, launch.block_dim_y, launch.block_dim_z}; | ||||
|     info.local_memory_size_in_words = launch.local_pos_alloc; | ||||
|     info.shared_memory_size_in_words = launch.shared_alloc; | ||||
|     return info; | ||||
| } | ||||
|  | ||||
| } // Anonymous namespace | ||||
|  | ||||
| Registry::Registry(Tegra::Engines::ShaderType shader_stage, const SerializedRegistryInfo& info) | ||||
|     : stage{shader_stage}, stored_guest_driver_profile{info.guest_driver_profile}, | ||||
|       bound_buffer{info.bound_buffer}, graphics_info{info.graphics}, compute_info{info.compute} {} | ||||
|  | ||||
| Registry::Registry(Tegra::Engines::ShaderType shader_stage, | ||||
|                    Tegra::Engines::ConstBufferEngineInterface& engine) | ||||
|     : stage{shader_stage}, engine{&engine}, bound_buffer{engine.GetBoundBuffer()}, | ||||
|       graphics_info{MakeGraphicsInfo(shader_stage, engine)}, compute_info{MakeComputeInfo( | ||||
|                                                                  shader_stage, engine)} {} | ||||
|  | ||||
| Registry::~Registry() = default; | ||||
|  | ||||
| std::optional<u32> Registry::ObtainKey(u32 buffer, u32 offset) { | ||||
|     const std::pair<u32, u32> key = {buffer, offset}; | ||||
|     const auto iter = keys.find(key); | ||||
|     if (iter != keys.end()) { | ||||
|         return iter->second; | ||||
|     } | ||||
|     if (!engine) { | ||||
|         return std::nullopt; | ||||
|     } | ||||
|     const u32 value = engine->AccessConstBuffer32(stage, buffer, offset); | ||||
|     keys.emplace(key, value); | ||||
|     return value; | ||||
| } | ||||
|  | ||||
| std::optional<SamplerDescriptor> Registry::ObtainBoundSampler(u32 offset) { | ||||
|     const u32 key = offset; | ||||
|     const auto iter = bound_samplers.find(key); | ||||
|     if (iter != bound_samplers.end()) { | ||||
|         return iter->second; | ||||
|     } | ||||
|     if (!engine) { | ||||
|         return std::nullopt; | ||||
|     } | ||||
|     const SamplerDescriptor value = engine->AccessBoundSampler(stage, offset); | ||||
|     bound_samplers.emplace(key, value); | ||||
|     return value; | ||||
| } | ||||
|  | ||||
| std::optional<Tegra::Engines::SamplerDescriptor> Registry::ObtainBindlessSampler(u32 buffer, | ||||
|                                                                                  u32 offset) { | ||||
|     const std::pair key = {buffer, offset}; | ||||
|     const auto iter = bindless_samplers.find(key); | ||||
|     if (iter != bindless_samplers.end()) { | ||||
|         return iter->second; | ||||
|     } | ||||
|     if (!engine) { | ||||
|         return std::nullopt; | ||||
|     } | ||||
|     const SamplerDescriptor value = engine->AccessBindlessSampler(stage, buffer, offset); | ||||
|     bindless_samplers.emplace(key, value); | ||||
|     return value; | ||||
| } | ||||
|  | ||||
| void Registry::InsertKey(u32 buffer, u32 offset, u32 value) { | ||||
|     keys.insert_or_assign({buffer, offset}, value); | ||||
| } | ||||
|  | ||||
| void Registry::InsertBoundSampler(u32 offset, SamplerDescriptor sampler) { | ||||
|     bound_samplers.insert_or_assign(offset, sampler); | ||||
| } | ||||
|  | ||||
| void Registry::InsertBindlessSampler(u32 buffer, u32 offset, SamplerDescriptor sampler) { | ||||
|     bindless_samplers.insert_or_assign({buffer, offset}, sampler); | ||||
| } | ||||
|  | ||||
| bool Registry::IsConsistent() const { | ||||
|     if (!engine) { | ||||
|         return true; | ||||
|     } | ||||
|     return std::all_of(keys.begin(), keys.end(), | ||||
|                        [this](const auto& pair) { | ||||
|                            const auto [cbuf, offset] = pair.first; | ||||
|                            const auto value = pair.second; | ||||
|                            return value == engine->AccessConstBuffer32(stage, cbuf, offset); | ||||
|                        }) && | ||||
|            std::all_of(bound_samplers.begin(), bound_samplers.end(), | ||||
|                        [this](const auto& sampler) { | ||||
|                            const auto [key, value] = sampler; | ||||
|                            return value == engine->AccessBoundSampler(stage, key); | ||||
|                        }) && | ||||
|            std::all_of(bindless_samplers.begin(), bindless_samplers.end(), | ||||
|                        [this](const auto& sampler) { | ||||
|                            const auto [cbuf, offset] = sampler.first; | ||||
|                            const auto value = sampler.second; | ||||
|                            return value == engine->AccessBindlessSampler(stage, cbuf, offset); | ||||
|                        }); | ||||
| } | ||||
|  | ||||
| bool Registry::HasEqualKeys(const Registry& rhs) const { | ||||
|     return std::tie(keys, bound_samplers, bindless_samplers) == | ||||
|            std::tie(rhs.keys, rhs.bound_samplers, rhs.bindless_samplers); | ||||
| } | ||||
|  | ||||
| const GraphicsInfo& Registry::GetGraphicsInfo() const { | ||||
|     ASSERT(stage != Tegra::Engines::ShaderType::Compute); | ||||
|     return graphics_info; | ||||
| } | ||||
|  | ||||
| const ComputeInfo& Registry::GetComputeInfo() const { | ||||
|     ASSERT(stage == Tegra::Engines::ShaderType::Compute); | ||||
|     return compute_info; | ||||
| } | ||||
|  | ||||
| } // namespace VideoCommon::Shader | ||||
							
								
								
									
										137
									
								
								src/video_core/shader/registry.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								src/video_core/shader/registry.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,137 @@ | ||||
| // Copyright 2019 yuzu Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
| #include <optional> | ||||
| #include <type_traits> | ||||
| #include <unordered_map> | ||||
| #include <utility> | ||||
|  | ||||
| #include "common/common_types.h" | ||||
| #include "common/hash.h" | ||||
| #include "video_core/engines/const_buffer_engine_interface.h" | ||||
| #include "video_core/engines/maxwell_3d.h" | ||||
| #include "video_core/engines/shader_type.h" | ||||
| #include "video_core/guest_driver.h" | ||||
|  | ||||
| namespace VideoCommon::Shader { | ||||
|  | ||||
| using KeyMap = std::unordered_map<std::pair<u32, u32>, u32, Common::PairHash>; | ||||
| using BoundSamplerMap = std::unordered_map<u32, Tegra::Engines::SamplerDescriptor>; | ||||
| using BindlessSamplerMap = | ||||
|     std::unordered_map<std::pair<u32, u32>, Tegra::Engines::SamplerDescriptor, Common::PairHash>; | ||||
|  | ||||
| struct GraphicsInfo { | ||||
|     using Maxwell = Tegra::Engines::Maxwell3D::Regs; | ||||
|  | ||||
|     std::array<Maxwell::TransformFeedbackLayout, Maxwell::NumTransformFeedbackBuffers> | ||||
|         tfb_layouts{}; | ||||
|     std::array<std::array<u8, 128>, Maxwell::NumTransformFeedbackBuffers> tfb_varying_locs{}; | ||||
|     Maxwell::PrimitiveTopology primitive_topology{}; | ||||
|     Maxwell::TessellationPrimitive tessellation_primitive{}; | ||||
|     Maxwell::TessellationSpacing tessellation_spacing{}; | ||||
|     bool tfb_enabled = false; | ||||
|     bool tessellation_clockwise = false; | ||||
| }; | ||||
| static_assert(std::is_trivially_copyable_v<GraphicsInfo> && | ||||
|               std::is_standard_layout_v<GraphicsInfo>); | ||||
|  | ||||
| struct ComputeInfo { | ||||
|     std::array<u32, 3> workgroup_size{}; | ||||
|     u32 shared_memory_size_in_words = 0; | ||||
|     u32 local_memory_size_in_words = 0; | ||||
| }; | ||||
| static_assert(std::is_trivially_copyable_v<ComputeInfo> && std::is_standard_layout_v<ComputeInfo>); | ||||
|  | ||||
| struct SerializedRegistryInfo { | ||||
|     VideoCore::GuestDriverProfile guest_driver_profile; | ||||
|     u32 bound_buffer = 0; | ||||
|     GraphicsInfo graphics; | ||||
|     ComputeInfo compute; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * The Registry is a class use to interface the 3D and compute engines with the shader compiler. | ||||
|  * With it, the shader can obtain required data from GPU state and store it for disk shader | ||||
|  * compilation. | ||||
|  */ | ||||
| class Registry { | ||||
| public: | ||||
|     explicit Registry(Tegra::Engines::ShaderType shader_stage, const SerializedRegistryInfo& info); | ||||
|  | ||||
|     explicit Registry(Tegra::Engines::ShaderType shader_stage, | ||||
|                       Tegra::Engines::ConstBufferEngineInterface& engine); | ||||
|  | ||||
|     ~Registry(); | ||||
|  | ||||
|     /// Retrieves a key from the registry, if it's registered, it will give the registered value, if | ||||
|     /// not it will obtain it from maxwell3d and register it. | ||||
|     std::optional<u32> ObtainKey(u32 buffer, u32 offset); | ||||
|  | ||||
|     std::optional<Tegra::Engines::SamplerDescriptor> ObtainBoundSampler(u32 offset); | ||||
|  | ||||
|     std::optional<Tegra::Engines::SamplerDescriptor> ObtainBindlessSampler(u32 buffer, u32 offset); | ||||
|  | ||||
|     /// Inserts a key. | ||||
|     void InsertKey(u32 buffer, u32 offset, u32 value); | ||||
|  | ||||
|     /// Inserts a bound sampler key. | ||||
|     void InsertBoundSampler(u32 offset, Tegra::Engines::SamplerDescriptor sampler); | ||||
|  | ||||
|     /// Inserts a bindless sampler key. | ||||
|     void InsertBindlessSampler(u32 buffer, u32 offset, Tegra::Engines::SamplerDescriptor sampler); | ||||
|  | ||||
|     /// Checks keys and samplers against engine's current const buffers. | ||||
|     /// Returns true if they are the same value, false otherwise. | ||||
|     bool IsConsistent() const; | ||||
|  | ||||
|     /// Returns true if the keys are equal to the other ones in the registry. | ||||
|     bool HasEqualKeys(const Registry& rhs) const; | ||||
|  | ||||
|     /// Returns graphics information from this shader | ||||
|     const GraphicsInfo& GetGraphicsInfo() const; | ||||
|  | ||||
|     /// Returns compute information from this shader | ||||
|     const ComputeInfo& GetComputeInfo() const; | ||||
|  | ||||
|     /// Gives an getter to the const buffer keys in the database. | ||||
|     const KeyMap& GetKeys() const { | ||||
|         return keys; | ||||
|     } | ||||
|  | ||||
|     /// Gets samplers database. | ||||
|     const BoundSamplerMap& GetBoundSamplers() const { | ||||
|         return bound_samplers; | ||||
|     } | ||||
|  | ||||
|     /// Gets bindless samplers database. | ||||
|     const BindlessSamplerMap& GetBindlessSamplers() const { | ||||
|         return bindless_samplers; | ||||
|     } | ||||
|  | ||||
|     /// Gets bound buffer used on this shader | ||||
|     u32 GetBoundBuffer() const { | ||||
|         return bound_buffer; | ||||
|     } | ||||
|  | ||||
|     /// Obtains access to the guest driver's profile. | ||||
|     VideoCore::GuestDriverProfile& AccessGuestDriverProfile() { | ||||
|         return engine ? engine->AccessGuestDriverProfile() : stored_guest_driver_profile; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     const Tegra::Engines::ShaderType stage; | ||||
|     VideoCore::GuestDriverProfile stored_guest_driver_profile; | ||||
|     Tegra::Engines::ConstBufferEngineInterface* engine = nullptr; | ||||
|     KeyMap keys; | ||||
|     BoundSamplerMap bound_samplers; | ||||
|     BindlessSamplerMap bindless_samplers; | ||||
|     u32 bound_buffer; | ||||
|     GraphicsInfo graphics_info; | ||||
|     ComputeInfo compute_info; | ||||
| }; | ||||
|  | ||||
| } // namespace VideoCommon::Shader | ||||
| @@ -11,6 +11,7 @@ | ||||
| #include "common/logging/log.h" | ||||
| #include "video_core/engines/shader_bytecode.h" | ||||
| #include "video_core/shader/node_helper.h" | ||||
| #include "video_core/shader/registry.h" | ||||
| #include "video_core/shader/shader_ir.h" | ||||
|  | ||||
| namespace VideoCommon::Shader { | ||||
| @@ -24,8 +25,8 @@ using Tegra::Shader::PredOperation; | ||||
| using Tegra::Shader::Register; | ||||
|  | ||||
| ShaderIR::ShaderIR(const ProgramCode& program_code, u32 main_offset, CompilerSettings settings, | ||||
|                    ConstBufferLocker& locker) | ||||
|     : program_code{program_code}, main_offset{main_offset}, settings{settings}, locker{locker} { | ||||
|                    Registry& registry) | ||||
|     : program_code{program_code}, main_offset{main_offset}, settings{settings}, registry{registry} { | ||||
|     Decode(); | ||||
|     PostDecode(); | ||||
| } | ||||
|   | ||||
| @@ -18,8 +18,8 @@ | ||||
| #include "video_core/engines/shader_header.h" | ||||
| #include "video_core/shader/ast.h" | ||||
| #include "video_core/shader/compiler_settings.h" | ||||
| #include "video_core/shader/const_buffer_locker.h" | ||||
| #include "video_core/shader/node.h" | ||||
| #include "video_core/shader/registry.h" | ||||
|  | ||||
| namespace VideoCommon::Shader { | ||||
|  | ||||
| @@ -69,7 +69,7 @@ struct GlobalMemoryUsage { | ||||
| class ShaderIR final { | ||||
| public: | ||||
|     explicit ShaderIR(const ProgramCode& program_code, u32 main_offset, CompilerSettings settings, | ||||
|                       ConstBufferLocker& locker); | ||||
|                       Registry& registry); | ||||
|     ~ShaderIR(); | ||||
|  | ||||
|     const std::map<u32, NodeBlock>& GetBasicBlocks() const { | ||||
| @@ -414,7 +414,7 @@ private: | ||||
|     const ProgramCode& program_code; | ||||
|     const u32 main_offset; | ||||
|     const CompilerSettings settings; | ||||
|     ConstBufferLocker& locker; | ||||
|     Registry& registry; | ||||
|  | ||||
|     bool decompiled{}; | ||||
|     bool disable_flow_stack{}; | ||||
|   | ||||
| @@ -81,26 +81,20 @@ std::tuple<Node, TrackSampler> ShaderIR::TrackBindlessSampler(Node tracked, cons | ||||
|                 MakeTrackSampler<BindlessSamplerNode>(cbuf->GetIndex(), immediate->GetValue()); | ||||
|             return {tracked, track}; | ||||
|         } else if (const auto operation = std::get_if<OperationNode>(&*offset)) { | ||||
|             auto bound_buffer = locker.ObtainBoundBuffer(); | ||||
|             if (!bound_buffer) { | ||||
|             const u32 bound_buffer = registry.GetBoundBuffer(); | ||||
|             if (bound_buffer != cbuf->GetIndex()) { | ||||
|                 return {}; | ||||
|             } | ||||
|             if (*bound_buffer != cbuf->GetIndex()) { | ||||
|                 return {}; | ||||
|             } | ||||
|             auto pair = DecoupleIndirectRead(*operation); | ||||
|             const auto pair = DecoupleIndirectRead(*operation); | ||||
|             if (!pair) { | ||||
|                 return {}; | ||||
|             } | ||||
|             auto [gpr, base_offset] = *pair; | ||||
|             const auto offset_inm = std::get_if<ImmediateNode>(&*base_offset); | ||||
|             auto gpu_driver = locker.AccessGuestDriverProfile(); | ||||
|             if (gpu_driver == nullptr) { | ||||
|                 return {}; | ||||
|             } | ||||
|             const auto& gpu_driver = registry.AccessGuestDriverProfile(); | ||||
|             const u32 bindless_cv = NewCustomVariable(); | ||||
|             const Node op = Operation(OperationCode::UDiv, NO_PRECISE, gpr, | ||||
|                                       Immediate(gpu_driver->GetTextureHandlerSize())); | ||||
|             const Node op = | ||||
|                 Operation(OperationCode::UDiv, gpr, Immediate(gpu_driver.GetTextureHandlerSize())); | ||||
|  | ||||
|             const Node cv_node = GetCustomVariable(bindless_cv); | ||||
|             Node amend_op = Operation(OperationCode::Assign, cv_node, std::move(op)); | ||||
|   | ||||
| @@ -34,18 +34,6 @@ constexpr char PROGRESSBAR_STYLE_PREPARE[] = R"( | ||||
| QProgressBar {} | ||||
| QProgressBar::chunk {})"; | ||||
|  | ||||
| constexpr char PROGRESSBAR_STYLE_DECOMPILE[] = R"( | ||||
| QProgressBar { | ||||
|   background-color: black; | ||||
|   border: 2px solid white; | ||||
|   border-radius: 4px; | ||||
|   padding: 2px; | ||||
| } | ||||
| QProgressBar::chunk { | ||||
|   background-color: #0ab9e6; | ||||
|   width: 1px; | ||||
| })"; | ||||
|  | ||||
| constexpr char PROGRESSBAR_STYLE_BUILD[] = R"( | ||||
| QProgressBar { | ||||
|   background-color: black; | ||||
| @@ -100,13 +88,11 @@ LoadingScreen::LoadingScreen(QWidget* parent) | ||||
|  | ||||
|     stage_translations = { | ||||
|         {VideoCore::LoadCallbackStage::Prepare, tr("Loading...")}, | ||||
|         {VideoCore::LoadCallbackStage::Decompile, tr("Preparing Shaders %1 / %2")}, | ||||
|         {VideoCore::LoadCallbackStage::Build, tr("Loading Shaders %1 / %2")}, | ||||
|         {VideoCore::LoadCallbackStage::Complete, tr("Launching...")}, | ||||
|     }; | ||||
|     progressbar_style = { | ||||
|         {VideoCore::LoadCallbackStage::Prepare, PROGRESSBAR_STYLE_PREPARE}, | ||||
|         {VideoCore::LoadCallbackStage::Decompile, PROGRESSBAR_STYLE_DECOMPILE}, | ||||
|         {VideoCore::LoadCallbackStage::Build, PROGRESSBAR_STYLE_BUILD}, | ||||
|         {VideoCore::LoadCallbackStage::Complete, PROGRESSBAR_STYLE_COMPLETE}, | ||||
|     }; | ||||
| @@ -192,8 +178,7 @@ void LoadingScreen::OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size | ||||
|     } | ||||
|  | ||||
|     // update labels and progress bar | ||||
|     if (stage == VideoCore::LoadCallbackStage::Decompile || | ||||
|         stage == VideoCore::LoadCallbackStage::Build) { | ||||
|     if (stage == VideoCore::LoadCallbackStage::Build) { | ||||
|         ui->stage->setText(stage_translations[stage].arg(value).arg(total)); | ||||
|     } else { | ||||
|         ui->stage->setText(stage_translations[stage]); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user