diff options
author | bunnei <bunneidev@gmail.com> | 2018-07-13 16:08:54 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-07-13 16:08:54 +0200 |
commit | 9fc0d1d701dbf4f775b25ee3f5b02a850eb5ffee (patch) | |
tree | b1468d27f6a5281cfc79dc29668a1a3d0519df04 | |
parent | More improvements to GDBStub (#653) (diff) | |
parent | gl_rasterizer: Fix check for if a shader stage is enabled. (diff) | |
download | yuzu-9fc0d1d701dbf4f775b25ee3f5b02a850eb5ffee.tar yuzu-9fc0d1d701dbf4f775b25ee3f5b02a850eb5ffee.tar.gz yuzu-9fc0d1d701dbf4f775b25ee3f5b02a850eb5ffee.tar.bz2 yuzu-9fc0d1d701dbf4f775b25ee3f5b02a850eb5ffee.tar.lz yuzu-9fc0d1d701dbf4f775b25ee3f5b02a850eb5ffee.tar.xz yuzu-9fc0d1d701dbf4f775b25ee3f5b02a850eb5ffee.tar.zst yuzu-9fc0d1d701dbf4f775b25ee3f5b02a850eb5ffee.zip |
Diffstat (limited to '')
-rw-r--r-- | src/video_core/engines/maxwell_3d.cpp | 21 | ||||
-rw-r--r-- | src/video_core/engines/maxwell_3d.h | 11 | ||||
-rw-r--r-- | src/video_core/renderer_opengl/gl_rasterizer.cpp | 55 | ||||
-rw-r--r-- | src/video_core/renderer_opengl/gl_shader_decompiler.cpp | 71 | ||||
-rw-r--r-- | src/video_core/renderer_opengl/gl_shader_decompiler.h | 3 | ||||
-rw-r--r-- | src/video_core/renderer_opengl/gl_shader_gen.cpp | 44 | ||||
-rw-r--r-- | src/video_core/renderer_opengl/gl_shader_gen.h | 33 |
7 files changed, 149 insertions, 89 deletions
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 3bca16364..dfbf80abd 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -398,27 +398,6 @@ u32 Maxwell3D::GetRegisterValue(u32 method) const { return regs.reg_array[method]; } -bool Maxwell3D::IsShaderStageEnabled(Regs::ShaderStage stage) const { - // The Vertex stage is always enabled. - if (stage == Regs::ShaderStage::Vertex) - return true; - - switch (stage) { - case Regs::ShaderStage::TesselationControl: - return regs.shader_config[static_cast<size_t>(Regs::ShaderProgram::TesselationControl)] - .enable != 0; - case Regs::ShaderStage::TesselationEval: - return regs.shader_config[static_cast<size_t>(Regs::ShaderProgram::TesselationEval)] - .enable != 0; - case Regs::ShaderStage::Geometry: - return regs.shader_config[static_cast<size_t>(Regs::ShaderProgram::Geometry)].enable != 0; - case Regs::ShaderStage::Fragment: - return regs.shader_config[static_cast<size_t>(Regs::ShaderProgram::Fragment)].enable != 0; - } - - UNREACHABLE(); -} - void Maxwell3D::ProcessClearBuffers() { ASSERT(regs.clear_buffers.R == regs.clear_buffers.G && regs.clear_buffers.R == regs.clear_buffers.B && diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 5a7cf0107..6f0170ff7 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -379,6 +379,14 @@ public: } }; + bool IsShaderConfigEnabled(size_t index) const { + // The VertexB is always enabled. + if (index == static_cast<size_t>(Regs::ShaderProgram::VertexB)) { + return true; + } + return shader_config[index].enable != 0; + } + union { struct { INSERT_PADDING_WORDS(0x45); @@ -780,9 +788,6 @@ public: /// Returns the texture information for a specific texture in a specific shader stage. Texture::FullTextureInfo GetStageTexture(Regs::ShaderStage stage, size_t offset) const; - /// Returns whether the specified shader stage is enabled or not. - bool IsShaderStageEnabled(Regs::ShaderStage stage) const; - private: std::unordered_map<u32, std::vector<u32>> uploaded_macros; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index ea138d402..4072a12b4 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -181,6 +181,19 @@ std::pair<u8*, GLintptr> RasterizerOpenGL::SetupVertexArrays(u8* array_ptr, return {array_ptr, buffer_offset}; } +static GLShader::ProgramCode GetShaderProgramCode(Maxwell::ShaderProgram program) { + auto& gpu = Core::System().GetInstance().GPU().Maxwell3D(); + + // Fetch program code from memory + GLShader::ProgramCode program_code; + auto& shader_config = gpu.regs.shader_config[static_cast<size_t>(program)]; + const u64 gpu_address{gpu.regs.code_address.CodeAddress() + shader_config.offset}; + const boost::optional<VAddr> cpu_address{gpu.memory_manager.GpuToCpuAddress(gpu_address)}; + Memory::ReadBlock(*cpu_address, program_code.data(), program_code.size() * sizeof(u64)); + + return program_code; +} + void RasterizerOpenGL::SetupShaders(u8* buffer_ptr, GLintptr buffer_offset) { // Helper function for uploading uniform data const auto copy_buffer = [&](GLuint handle, GLintptr offset, GLsizeiptr size) { @@ -193,26 +206,23 @@ void RasterizerOpenGL::SetupShaders(u8* buffer_ptr, GLintptr buffer_offset) { }; auto& gpu = Core::System().GetInstance().GPU().Maxwell3D(); - ASSERT_MSG(!gpu.regs.shader_config[0].enable, "VertexA is unsupported!"); // Next available bindpoints to use when uploading the const buffers and textures to the GLSL // shaders. The constbuffer bindpoint starts after the shader stage configuration bind points. u32 current_constbuffer_bindpoint = uniform_buffers.size(); u32 current_texture_bindpoint = 0; - for (unsigned index = 1; index < Maxwell::MaxShaderProgram; ++index) { + for (size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) { auto& shader_config = gpu.regs.shader_config[index]; const Maxwell::ShaderProgram program{static_cast<Maxwell::ShaderProgram>(index)}; - const auto& stage = index - 1; // Stage indices are 0 - 5 - - const bool is_enabled = gpu.IsShaderStageEnabled(static_cast<Maxwell::ShaderStage>(stage)); - // Skip stages that are not enabled - if (!is_enabled) { + if (!gpu.regs.IsShaderConfigEnabled(index)) { continue; } + const size_t stage{index == 0 ? 0 : index - 1}; // Stage indices are 0 - 5 + GLShader::MaxwellUniformData ubo{}; ubo.SetFromRegs(gpu.state.shader_stages[stage]); std::memcpy(buffer_ptr, &ubo, sizeof(ubo)); @@ -228,16 +238,21 @@ void RasterizerOpenGL::SetupShaders(u8* buffer_ptr, GLintptr buffer_offset) { buffer_ptr += sizeof(GLShader::MaxwellUniformData); buffer_offset += sizeof(GLShader::MaxwellUniformData); - // Fetch program code from memory - GLShader::ProgramCode program_code; - const u64 gpu_address{gpu.regs.code_address.CodeAddress() + shader_config.offset}; - const boost::optional<VAddr> cpu_address{gpu.memory_manager.GpuToCpuAddress(gpu_address)}; - Memory::ReadBlock(*cpu_address, program_code.data(), program_code.size() * sizeof(u64)); - GLShader::ShaderSetup setup{std::move(program_code)}; - + GLShader::ShaderSetup setup{GetShaderProgramCode(program)}; GLShader::ShaderEntries shader_resources; switch (program) { + case Maxwell::ShaderProgram::VertexA: { + // VertexB is always enabled, so when VertexA is enabled, we have two vertex shaders. + // Conventional HW does not support this, so we combine VertexA and VertexB into one + // stage here. + setup.SetProgramB(GetShaderProgramCode(Maxwell::ShaderProgram::VertexB)); + GLShader::MaxwellVSConfig vs_config{setup}; + shader_resources = + shader_program_manager->UseProgrammableVertexShader(vs_config, setup); + break; + } + case Maxwell::ShaderProgram::VertexB: { GLShader::MaxwellVSConfig vs_config{setup}; shader_resources = @@ -268,6 +283,12 @@ void RasterizerOpenGL::SetupShaders(u8* buffer_ptr, GLintptr buffer_offset) { current_texture_bindpoint = SetupTextures(static_cast<Maxwell::ShaderStage>(stage), gl_stage_program, current_texture_bindpoint, shader_resources.texture_samplers); + + // When VertexA is enabled, we have dual vertex shaders + if (program == Maxwell::ShaderProgram::VertexA) { + // VertexB was combined with VertexA, so we skip the VertexB iteration + index++; + } } shader_program_manager->UseTrivialGeometryShader(); @@ -605,9 +626,6 @@ u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, GLuint progr auto& gpu = Core::System::GetInstance().GPU(); auto& maxwell3d = gpu.Get3DEngine(); - ASSERT_MSG(maxwell3d.IsShaderStageEnabled(stage), - "Attempted to upload constbuffer of disabled shader stage"); - // Reset all buffer draw state for this stage. for (auto& buffer : state.draw.const_buffers[static_cast<size_t>(stage)]) { buffer.bindpoint = 0; @@ -674,9 +692,6 @@ u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, GLuint program, auto& gpu = Core::System::GetInstance().GPU(); auto& maxwell3d = gpu.Get3DEngine(); - ASSERT_MSG(maxwell3d.IsShaderStageEnabled(stage), - "Attempted to upload textures of disabled shader stage"); - ASSERT_MSG(current_unit + entries.size() <= std::size(state.texture_units), "Exceeded the number of active textures."); diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 96a4ca6fe..5fae95788 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -42,13 +42,14 @@ enum class ExitMethod { struct Subroutine { /// Generates a name suitable for GLSL source code. std::string GetName() const { - return "sub_" + std::to_string(begin) + '_' + std::to_string(end); + return "sub_" + std::to_string(begin) + '_' + std::to_string(end) + '_' + suffix; } - u32 begin; ///< Entry point of the subroutine. - u32 end; ///< Return point of the subroutine. - ExitMethod exit_method; ///< Exit method of the subroutine. - std::set<u32> labels; ///< Addresses refereced by JMP instructions. + u32 begin; ///< Entry point of the subroutine. + u32 end; ///< Return point of the subroutine. + const std::string& suffix; ///< Suffix of the shader, used to make a unique subroutine name + ExitMethod exit_method; ///< Exit method of the subroutine. + std::set<u32> labels; ///< Addresses refereced by JMP instructions. bool operator<(const Subroutine& rhs) const { return std::tie(begin, end) < std::tie(rhs.begin, rhs.end); @@ -58,11 +59,11 @@ struct Subroutine { /// Analyzes shader code and produces a set of subroutines. class ControlFlowAnalyzer { public: - ControlFlowAnalyzer(const ProgramCode& program_code, u32 main_offset) + ControlFlowAnalyzer(const ProgramCode& program_code, u32 main_offset, const std::string& suffix) : program_code(program_code) { // Recursively finds all subroutines. - const Subroutine& program_main = AddSubroutine(main_offset, PROGRAM_END); + const Subroutine& program_main = AddSubroutine(main_offset, PROGRAM_END, suffix); if (program_main.exit_method != ExitMethod::AlwaysEnd) throw DecompileFail("Program does not always end"); } @@ -77,12 +78,12 @@ private: std::map<std::pair<u32, u32>, ExitMethod> exit_method_map; /// Adds and analyzes a new subroutine if it is not added yet. - const Subroutine& AddSubroutine(u32 begin, u32 end) { - auto iter = subroutines.find(Subroutine{begin, end}); + const Subroutine& AddSubroutine(u32 begin, u32 end, const std::string& suffix) { + auto iter = subroutines.find(Subroutine{begin, end, suffix}); if (iter != subroutines.end()) return *iter; - Subroutine subroutine{begin, end}; + Subroutine subroutine{begin, end, suffix}; subroutine.exit_method = Scan(begin, end, subroutine.labels); if (subroutine.exit_method == ExitMethod::Undetermined) throw DecompileFail("Recursive function detected"); @@ -191,7 +192,8 @@ public: UnsignedInteger, }; - GLSLRegister(size_t index, ShaderWriter& shader) : index{index}, shader{shader} {} + GLSLRegister(size_t index, ShaderWriter& shader, const std::string& suffix) + : index{index}, shader{shader}, suffix{suffix} {} /// Gets the GLSL type string for a register static std::string GetTypeString(Type type) { @@ -216,7 +218,7 @@ public: /// Returns a GLSL string representing the current state of the register const std::string GetActiveString() { declr_type.insert(active_type); - return GetPrefixString(active_type) + std::to_string(index); + return GetPrefixString(active_type) + std::to_string(index) + '_' + suffix; } /// Returns true if the active type is a float @@ -251,6 +253,7 @@ private: ShaderWriter& shader; Type active_type{Type::Float}; std::set<Type> declr_type; + const std::string& suffix; }; /** @@ -262,8 +265,8 @@ private: class GLSLRegisterManager { public: GLSLRegisterManager(ShaderWriter& shader, ShaderWriter& declarations, - const Maxwell3D::Regs::ShaderStage& stage) - : shader{shader}, declarations{declarations}, stage{stage} { + const Maxwell3D::Regs::ShaderStage& stage, const std::string& suffix) + : shader{shader}, declarations{declarations}, stage{stage}, suffix{suffix} { BuildRegisterList(); } @@ -430,12 +433,12 @@ public: } /// Add declarations for registers - void GenerateDeclarations() { + void GenerateDeclarations(const std::string& suffix) { for (const auto& reg : regs) { for (const auto& type : reg.DeclaredTypes()) { declarations.AddLine(GLSLRegister::GetTypeString(type) + ' ' + - GLSLRegister::GetPrefixString(type) + - std::to_string(reg.GetIndex()) + " = 0;"); + reg.GetPrefixString(type) + std::to_string(reg.GetIndex()) + + '_' + suffix + " = 0;"); } } declarations.AddNewLine(); @@ -558,7 +561,7 @@ private: /// Build the GLSL register list. void BuildRegisterList() { for (size_t index = 0; index < Register::NumRegisters; ++index) { - regs.emplace_back(index, shader); + regs.emplace_back(index, shader, suffix); } } @@ -620,16 +623,17 @@ private: std::array<ConstBufferEntry, Maxwell3D::Regs::MaxConstBuffers> declr_const_buffers; std::vector<SamplerEntry> used_samplers; const Maxwell3D::Regs::ShaderStage& stage; + const std::string& suffix; }; class GLSLGenerator { public: GLSLGenerator(const std::set<Subroutine>& subroutines, const ProgramCode& program_code, - u32 main_offset, Maxwell3D::Regs::ShaderStage stage) + u32 main_offset, Maxwell3D::Regs::ShaderStage stage, const std::string& suffix) : subroutines(subroutines), program_code(program_code), main_offset(main_offset), - stage(stage) { + stage(stage), suffix(suffix) { - Generate(); + Generate(suffix); } std::string GetShaderCode() { @@ -644,7 +648,7 @@ public: private: /// Gets the Subroutine object corresponding to the specified address. const Subroutine& GetSubroutine(u32 begin, u32 end) const { - auto iter = subroutines.find(Subroutine{begin, end}); + auto iter = subroutines.find(Subroutine{begin, end, suffix}); ASSERT(iter != subroutines.end()); return *iter; } @@ -689,7 +693,7 @@ private: // Can't assign to the constant predicate. ASSERT(pred != static_cast<u64>(Pred::UnusedIndex)); - std::string variable = 'p' + std::to_string(pred); + std::string variable = 'p' + std::to_string(pred) + '_' + suffix; shader.AddLine(variable + " = " + value + ';'); declr_predicates.insert(std::move(variable)); } @@ -707,7 +711,7 @@ private: if (index == static_cast<u64>(Pred::UnusedIndex)) variable = "true"; else - variable = 'p' + std::to_string(index); + variable = 'p' + std::to_string(index) + '_' + suffix; if (negate) { return "!(" + variable + ')'; @@ -1728,7 +1732,7 @@ private: return program_counter; } - void Generate() { + void Generate(const std::string& suffix) { // Add declarations for all subroutines for (const auto& subroutine : subroutines) { shader.AddLine("bool " + subroutine.GetName() + "();"); @@ -1736,7 +1740,7 @@ private: shader.AddNewLine(); // Add the main entry point - shader.AddLine("bool exec_shader() {"); + shader.AddLine("bool exec_" + suffix + "() {"); ++shader.scope; CallSubroutine(GetSubroutine(main_offset, PROGRAM_END)); --shader.scope; @@ -1799,7 +1803,7 @@ private: /// Add declarations for registers void GenerateDeclarations() { - regs.GenerateDeclarations(); + regs.GenerateDeclarations(suffix); for (const auto& pred : declr_predicates) { declarations.AddLine("bool " + pred + " = false;"); @@ -1812,27 +1816,30 @@ private: const ProgramCode& program_code; const u32 main_offset; Maxwell3D::Regs::ShaderStage stage; + const std::string& suffix; ShaderWriter shader; ShaderWriter declarations; - GLSLRegisterManager regs{shader, declarations, stage}; + GLSLRegisterManager regs{shader, declarations, stage, suffix}; // Declarations std::set<std::string> declr_predicates; }; // namespace Decompiler std::string GetCommonDeclarations() { - std::string declarations = "bool exec_shader();\n"; + std::string declarations; declarations += "#define MAX_CONSTBUFFER_ELEMENTS " + std::to_string(RasterizerOpenGL::MaxConstbufferSize / (sizeof(GLvec4))); + declarations += '\n'; return declarations; } boost::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code, u32 main_offset, - Maxwell3D::Regs::ShaderStage stage) { + Maxwell3D::Regs::ShaderStage stage, + const std::string& suffix) { try { - auto subroutines = ControlFlowAnalyzer(program_code, main_offset).GetSubroutines(); - GLSLGenerator generator(subroutines, program_code, main_offset, stage); + auto subroutines = ControlFlowAnalyzer(program_code, main_offset, suffix).GetSubroutines(); + GLSLGenerator generator(subroutines, program_code, main_offset, stage, suffix); return ProgramResult{generator.GetShaderCode(), generator.GetEntries()}; } catch (const DecompileFail& exception) { LOG_ERROR(HW_GPU, "Shader decompilation failed: {}", exception.what()); diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.h b/src/video_core/renderer_opengl/gl_shader_decompiler.h index 382c76b7a..7610dad3a 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.h +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.h @@ -20,7 +20,8 @@ using Tegra::Engines::Maxwell3D; std::string GetCommonDeclarations(); boost::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code, u32 main_offset, - Maxwell3D::Regs::ShaderStage stage); + Maxwell3D::Regs::ShaderStage stage, + const std::string& suffix); } // namespace Decompiler } // namespace GLShader diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index c1e6fac9f..129c777d1 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -17,10 +17,17 @@ ProgramResult GenerateVertexShader(const ShaderSetup& setup, const MaxwellVSConf std::string out = "#version 430 core\n"; out += "#extension GL_ARB_separate_shader_objects : enable\n\n"; out += Decompiler::GetCommonDeclarations(); + out += "bool exec_vertex();\n"; + + if (setup.IsDualProgram()) { + out += "bool exec_vertex_b();\n"; + } + + ProgramResult program = + Decompiler::DecompileProgram(setup.program.code, PROGRAM_OFFSET, + Maxwell3D::Regs::ShaderStage::Vertex, "vertex") + .get_value_or({}); - ProgramResult program = Decompiler::DecompileProgram(setup.program_code, PROGRAM_OFFSET, - Maxwell3D::Regs::ShaderStage::Vertex) - .get_value_or({}); out += R"( out gl_PerVertex { @@ -34,7 +41,14 @@ layout (std140) uniform vs_config { }; void main() { - exec_shader(); + exec_vertex(); +)"; + + if (setup.IsDualProgram()) { + out += " exec_vertex_b();"; + } + + out += R"( // Viewport can be flipped, which is unsupported by glViewport position.xy *= viewport_flip.xy; @@ -44,8 +58,19 @@ void main() { // For now, this is here to bring order in lieu of proper emulation position.w = 1.0; } + )"; + out += program.first; + + if (setup.IsDualProgram()) { + ProgramResult program_b = + Decompiler::DecompileProgram(setup.program.code_b, PROGRAM_OFFSET, + Maxwell3D::Regs::ShaderStage::Vertex, "vertex_b") + .get_value_or({}); + out += program_b.first; + } + return {out, program.second}; } @@ -53,12 +78,13 @@ ProgramResult GenerateFragmentShader(const ShaderSetup& setup, const MaxwellFSCo std::string out = "#version 430 core\n"; out += "#extension GL_ARB_separate_shader_objects : enable\n\n"; out += Decompiler::GetCommonDeclarations(); + out += "bool exec_fragment();\n"; - ProgramResult program = Decompiler::DecompileProgram(setup.program_code, PROGRAM_OFFSET, - Maxwell3D::Regs::ShaderStage::Fragment) - .get_value_or({}); + ProgramResult program = + Decompiler::DecompileProgram(setup.program.code, PROGRAM_OFFSET, + Maxwell3D::Regs::ShaderStage::Fragment, "fragment") + .get_value_or({}); out += R"( - in vec4 position; out vec4 color; @@ -67,7 +93,7 @@ layout (std140) uniform fs_config { }; void main() { - exec_shader(); + exec_fragment(); } )"; diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h index ed890e0f9..4729ce0fc 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.h +++ b/src/video_core/renderer_opengl/gl_shader_gen.h @@ -115,21 +115,48 @@ struct ShaderEntries { using ProgramResult = std::pair<std::string, ShaderEntries>; struct ShaderSetup { - ShaderSetup(ProgramCode&& program_code) : program_code(std::move(program_code)) {} + ShaderSetup(const ProgramCode& program_code) { + program.code = program_code; + } + + struct { + ProgramCode code; + ProgramCode code_b; // Used for dual vertex shaders + } program; - ProgramCode program_code; bool program_code_hash_dirty = true; u64 GetProgramCodeHash() { if (program_code_hash_dirty) { - program_code_hash = Common::ComputeHash64(&program_code, sizeof(program_code)); + program_code_hash = GetNewHash(); program_code_hash_dirty = false; } return program_code_hash; } + /// Used in scenarios where we have a dual vertex shaders + void SetProgramB(const ProgramCode& program_b) { + program.code_b = program_b; + has_program_b = true; + } + + bool IsDualProgram() const { + return has_program_b; + } + private: + u64 GetNewHash() const { + if (has_program_b) { + // Compute hash over dual shader programs + return Common::ComputeHash64(&program, sizeof(program)); + } else { + // Compute hash over a single shader program + return Common::ComputeHash64(&program.code, program.code.size()); + } + } + u64 program_code_hash{}; + bool has_program_b{}; }; struct MaxwellShaderConfigCommon { |