diff options
Diffstat (limited to 'src/video_core/renderer_opengl/gl_shader_decompiler.cpp')
-rw-r--r-- | src/video_core/renderer_opengl/gl_shader_decompiler.cpp | 286 |
1 files changed, 229 insertions, 57 deletions
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 7e57de78a..2363b9d87 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -7,6 +7,7 @@ #include <string> #include <string_view> +#include <boost/optional.hpp> #include <fmt/format.h> #include "common/assert.h" @@ -29,11 +30,32 @@ using Tegra::Shader::SubOp; constexpr u32 PROGRAM_END = MAX_PROGRAM_CODE_LENGTH; constexpr u32 PROGRAM_HEADER_SIZE = sizeof(Tegra::Shader::Header); +constexpr u32 POSITION_VARYING_LOCATION = 15; + +constexpr u32 MAX_GEOMETRY_BUFFERS = 6; +constexpr u32 MAX_ATTRIBUTES = 0x100; // Size in vec4s, this value is untested + class DecompileFail : public std::runtime_error { public: using std::runtime_error::runtime_error; }; +/// Translate topology +static std::string GetTopologyName(Tegra::Shader::OutputTopology topology) { + switch (topology) { + case Tegra::Shader::OutputTopology::PointList: + return "points"; + case Tegra::Shader::OutputTopology::LineStrip: + return "line_strip"; + case Tegra::Shader::OutputTopology::TriangleStrip: + return "triangle_strip"; + default: + LOG_CRITICAL(Render_OpenGL, "Unknown output topology {}", static_cast<u32>(topology)); + UNREACHABLE(); + return "points"; + } +} + /// Describes the behaviour of code path of a given entry point and a return point. enum class ExitMethod { Undetermined, ///< Internal value. Only occur when analyzing JMP loop. @@ -253,8 +275,9 @@ enum class InternalFlag : u64 { class GLSLRegisterManager { public: GLSLRegisterManager(ShaderWriter& shader, ShaderWriter& declarations, - const Maxwell3D::Regs::ShaderStage& stage, const std::string& suffix) - : shader{shader}, declarations{declarations}, stage{stage}, suffix{suffix} { + const Maxwell3D::Regs::ShaderStage& stage, const std::string& suffix, + const Tegra::Shader::Header& header) + : shader{shader}, declarations{declarations}, stage{stage}, suffix{suffix}, header{header} { BuildRegisterList(); BuildInputList(); } @@ -358,11 +381,13 @@ public: * @param reg The destination register to use. * @param elem The element to use for the operation. * @param attribute The input attribute to use as the source value. + * @param vertex The register that decides which vertex to read from (used in GS). */ void SetRegisterToInputAttibute(const Register& reg, u64 elem, Attribute::Index attribute, - const Tegra::Shader::IpaMode& input_mode) { + const Tegra::Shader::IpaMode& input_mode, + boost::optional<Register> vertex = {}) { const std::string dest = GetRegisterAsFloat(reg); - const std::string src = GetInputAttribute(attribute, input_mode) + GetSwizzle(elem); + const std::string src = GetInputAttribute(attribute, input_mode, vertex) + GetSwizzle(elem); shader.AddLine(dest + " = " + src + ';'); } @@ -391,16 +416,29 @@ public: * are stored as floats, so this may require conversion. * @param attribute The destination output attribute. * @param elem The element to use for the operation. - * @param reg The register to use as the source value. + * @param val_reg The register to use as the source value. + * @param buf_reg The register that tells which buffer to write to (used in geometry shaders). */ - void SetOutputAttributeToRegister(Attribute::Index attribute, u64 elem, const Register& reg) { + void SetOutputAttributeToRegister(Attribute::Index attribute, u64 elem, const Register& val_reg, + const Register& buf_reg) { const std::string dest = GetOutputAttribute(attribute); - const std::string src = GetRegisterAsFloat(reg); + const std::string src = GetRegisterAsFloat(val_reg); if (!dest.empty()) { // Can happen with unknown/unimplemented output attributes, in which case we ignore the // instruction for now. - shader.AddLine(dest + GetSwizzle(elem) + " = " + src + ';'); + if (stage == Maxwell3D::Regs::ShaderStage::Geometry) { + // TODO(Rodrigo): nouveau sets some attributes after setting emitting a geometry + // shader. These instructions use a dirty register as buffer index. To avoid some + // drivers from complaining for the out of boundary writes, guard them. + const std::string buf_index{"min(" + GetRegisterAsInteger(buf_reg) + ", " + + std::to_string(MAX_GEOMETRY_BUFFERS - 1) + ')'}; + shader.AddLine("amem[" + buf_index + "][" + + std::to_string(static_cast<u32>(attribute)) + ']' + + GetSwizzle(elem) + " = " + src + ';'); + } else { + shader.AddLine(dest + GetSwizzle(elem) + " = " + src + ';'); + } } } @@ -441,41 +479,119 @@ public: } } - /// Add declarations for registers + /// Add declarations. void GenerateDeclarations(const std::string& suffix) { + GenerateRegisters(suffix); + GenerateInternalFlags(); + GenerateInputAttrs(); + GenerateOutputAttrs(); + GenerateConstBuffers(); + GenerateSamplers(); + GenerateGeometry(); + } + + /// Returns a list of constant buffer declarations. + std::vector<ConstBufferEntry> GetConstBuffersDeclarations() const { + std::vector<ConstBufferEntry> result; + std::copy_if(declr_const_buffers.begin(), declr_const_buffers.end(), + std::back_inserter(result), [](const auto& entry) { return entry.IsUsed(); }); + return result; + } + + /// Returns a list of samplers used in the shader. + const std::vector<SamplerEntry>& GetSamplers() const { + return used_samplers; + } + + /// Returns the GLSL sampler used for the input shader sampler, and creates a new one if + /// necessary. + std::string AccessSampler(const Sampler& sampler, Tegra::Shader::TextureType type, + bool is_array, bool is_shadow) { + const auto offset = static_cast<std::size_t>(sampler.index.Value()); + + // If this sampler has already been used, return the existing mapping. + const auto itr = + std::find_if(used_samplers.begin(), used_samplers.end(), + [&](const SamplerEntry& entry) { return entry.GetOffset() == offset; }); + + if (itr != used_samplers.end()) { + ASSERT(itr->GetType() == type && itr->IsArray() == is_array && + itr->IsShadow() == is_shadow); + return itr->GetName(); + } + + // Otherwise create a new mapping for this sampler + const std::size_t next_index = used_samplers.size(); + const SamplerEntry entry{stage, offset, next_index, type, is_array, is_shadow}; + used_samplers.emplace_back(entry); + return entry.GetName(); + } + +private: + /// Generates declarations for registers. + void GenerateRegisters(const std::string& suffix) { for (const auto& reg : regs) { declarations.AddLine(GLSLRegister::GetTypeString() + ' ' + reg.GetPrefixString() + std::to_string(reg.GetIndex()) + '_' + suffix + " = 0;"); } declarations.AddNewLine(); + } + /// Generates declarations for internal flags. + void GenerateInternalFlags() { for (u32 ii = 0; ii < static_cast<u64>(InternalFlag::Amount); ii++) { const InternalFlag code = static_cast<InternalFlag>(ii); declarations.AddLine("bool " + GetInternalFlag(code) + " = false;"); } declarations.AddNewLine(); + } + + /// Generates declarations for input attributes. + void GenerateInputAttrs() { + if (stage != Maxwell3D::Regs::ShaderStage::Vertex) { + const std::string attr = + stage == Maxwell3D::Regs::ShaderStage::Geometry ? "gs_position[]" : "position"; + declarations.AddLine("layout (location = " + std::to_string(POSITION_VARYING_LOCATION) + + ") in vec4 " + attr + ';'); + } for (const auto element : declr_input_attribute) { // TODO(bunnei): Use proper number of elements for these u32 idx = static_cast<u32>(element.first) - static_cast<u32>(Attribute::Index::Attribute_0); - declarations.AddLine("layout(location = " + std::to_string(idx) + ")" + - GetInputFlags(element.first) + "in vec4 " + - GetInputAttribute(element.first, element.second) + ';'); + ASSERT(idx != POSITION_VARYING_LOCATION); + + std::string attr{GetInputAttribute(element.first, element.second)}; + if (stage == Maxwell3D::Regs::ShaderStage::Geometry) { + attr = "gs_" + attr + "[]"; + } + declarations.AddLine("layout (location = " + std::to_string(idx) + ") " + + GetInputFlags(element.first) + "in vec4 " + attr + ';'); } + declarations.AddNewLine(); + } + /// Generates declarations for output attributes. + void GenerateOutputAttrs() { + if (stage != Maxwell3D::Regs::ShaderStage::Fragment) { + declarations.AddLine("layout (location = " + std::to_string(POSITION_VARYING_LOCATION) + + ") out vec4 position;"); + } for (const auto& index : declr_output_attribute) { // TODO(bunnei): Use proper number of elements for these - declarations.AddLine("layout(location = " + + declarations.AddLine("layout (location = " + std::to_string(static_cast<u32>(index) - static_cast<u32>(Attribute::Index::Attribute_0)) + ") out vec4 " + GetOutputAttribute(index) + ';'); } declarations.AddNewLine(); + } + /// Generates declarations for constant buffers. + void GenerateConstBuffers() { for (const auto& entry : GetConstBuffersDeclarations()) { - declarations.AddLine("layout(std140) uniform " + entry.GetName()); + declarations.AddLine("layout (std140) uniform " + entry.GetName()); declarations.AddLine('{'); declarations.AddLine(" vec4 c" + std::to_string(entry.GetIndex()) + "[MAX_CONSTBUFFER_ELEMENTS];"); @@ -483,7 +599,10 @@ public: declarations.AddNewLine(); } declarations.AddNewLine(); + } + /// Generates declarations for samplers. + void GenerateSamplers() { const auto& samplers = GetSamplers(); for (const auto& sampler : samplers) { declarations.AddLine("uniform " + sampler.GetTypeString() + ' ' + sampler.GetName() + @@ -492,44 +611,42 @@ public: declarations.AddNewLine(); } - /// Returns a list of constant buffer declarations - std::vector<ConstBufferEntry> GetConstBuffersDeclarations() const { - std::vector<ConstBufferEntry> result; - std::copy_if(declr_const_buffers.begin(), declr_const_buffers.end(), - std::back_inserter(result), [](const auto& entry) { return entry.IsUsed(); }); - return result; - } - - /// Returns a list of samplers used in the shader - const std::vector<SamplerEntry>& GetSamplers() const { - return used_samplers; - } - - /// Returns the GLSL sampler used for the input shader sampler, and creates a new one if - /// necessary. - std::string AccessSampler(const Sampler& sampler, Tegra::Shader::TextureType type, - bool is_array, bool is_shadow) { - const std::size_t offset = static_cast<std::size_t>(sampler.index.Value()); + /// Generates declarations used for geometry shaders. + void GenerateGeometry() { + if (stage != Maxwell3D::Regs::ShaderStage::Geometry) + return; - // If this sampler has already been used, return the existing mapping. - const auto itr = - std::find_if(used_samplers.begin(), used_samplers.end(), - [&](const SamplerEntry& entry) { return entry.GetOffset() == offset; }); + declarations.AddLine( + "layout (" + GetTopologyName(header.common3.output_topology) + + ", max_vertices = " + std::to_string(header.common4.max_output_vertices) + ") out;"); + declarations.AddNewLine(); - if (itr != used_samplers.end()) { - ASSERT(itr->GetType() == type && itr->IsArray() == is_array && - itr->IsShadow() == is_shadow); - return itr->GetName(); - } + declarations.AddLine("vec4 amem[" + std::to_string(MAX_GEOMETRY_BUFFERS) + "][" + + std::to_string(MAX_ATTRIBUTES) + "];"); + declarations.AddNewLine(); - // Otherwise create a new mapping for this sampler - const std::size_t next_index = used_samplers.size(); - const SamplerEntry entry{stage, offset, next_index, type, is_array, is_shadow}; - used_samplers.emplace_back(entry); - return entry.GetName(); + constexpr char buffer[] = "amem[output_buffer]"; + declarations.AddLine("void emit_vertex(uint output_buffer) {"); + ++declarations.scope; + for (const auto element : declr_output_attribute) { + declarations.AddLine(GetOutputAttribute(element) + " = " + buffer + '[' + + std::to_string(static_cast<u32>(element)) + "];"); + } + + declarations.AddLine("position = " + std::string(buffer) + '[' + + std::to_string(static_cast<u32>(Attribute::Index::Position)) + "];"); + + // If a geometry shader is attached, it will always flip (it's the last stage before + // fragment). For more info about flipping, refer to gl_shader_gen.cpp. + declarations.AddLine("position.xy *= viewport_flip.xy;"); + declarations.AddLine("gl_Position = position;"); + declarations.AddLine("position.w = 1.0;"); + declarations.AddLine("EmitVertex();"); + --declarations.scope; + declarations.AddLine('}'); + declarations.AddNewLine(); } -private: /// Generates code representing a temporary (GPR) register. std::string GetRegister(const Register& reg, unsigned elem) { if (reg == Register::ZeroIndex) { @@ -586,11 +703,19 @@ private: /// Generates code representing an input attribute register. std::string GetInputAttribute(Attribute::Index attribute, - const Tegra::Shader::IpaMode& input_mode) { + const Tegra::Shader::IpaMode& input_mode, + boost::optional<Register> vertex = {}) { + auto GeometryPass = [&](const std::string& name) { + if (stage == Maxwell3D::Regs::ShaderStage::Geometry && vertex) { + return "gs_" + name + '[' + GetRegisterAsInteger(vertex.value(), 0, false) + ']'; + } + return name; + }; + switch (attribute) { case Attribute::Index::Position: if (stage != Maxwell3D::Regs::ShaderStage::Fragment) { - return "position"; + return GeometryPass("position"); } else { return "vec4(gl_FragCoord.x, gl_FragCoord.y, gl_FragCoord.z, 1.0)"; } @@ -619,7 +744,7 @@ private: UNREACHABLE(); } } - return "input_attribute_" + std::to_string(index); + return GeometryPass("input_attribute_" + std::to_string(index)); } LOG_CRITICAL(HW_GPU, "Unhandled input attribute: {}", static_cast<u32>(attribute)); @@ -672,7 +797,7 @@ private: return out; } - /// Generates code representing an output attribute register. + /// Generates code representing the declaration name of an output attribute register. std::string GetOutputAttribute(Attribute::Index attribute) { switch (attribute) { case Attribute::Index::Position: @@ -708,6 +833,7 @@ private: std::vector<SamplerEntry> used_samplers; const Maxwell3D::Regs::ShaderStage& stage; const std::string& suffix; + const Tegra::Shader::Header& header; }; class GLSLGenerator { @@ -1103,8 +1229,8 @@ private: return offset + 1; } - shader.AddLine("// " + std::to_string(offset) + ": " + opcode->GetName() + " (" + - std::to_string(instr.value) + ')'); + shader.AddLine( + fmt::format("// {}: {} (0x{:016x})", offset, opcode->GetName(), instr.value)); using Tegra::Shader::Pred; ASSERT_MSG(instr.pred.full_pred != Pred::NeverExecute, @@ -1826,7 +1952,7 @@ private: const auto LoadNextElement = [&](u32 reg_offset) { regs.SetRegisterToInputAttibute(instr.gpr0.Value() + reg_offset, next_element, static_cast<Attribute::Index>(next_index), - input_mode); + input_mode, instr.gpr39.Value()); // Load the next attribute element into the following register. If the element // to load goes beyond the vec4 size, load the first element of the next @@ -1890,8 +2016,8 @@ private: const auto StoreNextElement = [&](u32 reg_offset) { regs.SetOutputAttributeToRegister(static_cast<Attribute::Index>(next_index), - next_element, - instr.gpr0.Value() + reg_offset); + next_element, instr.gpr0.Value() + reg_offset, + instr.gpr39.Value()); // Load the next attribute element into the following register. If the element // to load goes beyond the vec4 size, load the first element of the next @@ -2738,6 +2864,52 @@ private: break; } + case OpCode::Id::OUT_R: { + ASSERT(instr.gpr20.Value() == Register::ZeroIndex); + ASSERT_MSG(stage == Maxwell3D::Regs::ShaderStage::Geometry, + "OUT is expected to be used in a geometry shader."); + + if (instr.out.emit) { + // gpr0 is used to store the next address. Hardware returns a pointer but + // we just return the next index with a cyclic cap. + const std::string current{regs.GetRegisterAsInteger(instr.gpr8, 0, false)}; + const std::string next = "((" + current + " + 1" + ") % " + + std::to_string(MAX_GEOMETRY_BUFFERS) + ')'; + shader.AddLine("emit_vertex(" + current + ");"); + regs.SetRegisterToInteger(instr.gpr0, false, 0, next, 1, 1); + } + if (instr.out.cut) { + shader.AddLine("EndPrimitive();"); + } + + break; + } + case OpCode::Id::MOV_SYS: { + switch (instr.sys20) { + case Tegra::Shader::SystemVariable::InvocationInfo: { + LOG_WARNING(HW_GPU, "MOV_SYS instruction with InvocationInfo is incomplete"); + regs.SetRegisterToInteger(instr.gpr0, false, 0, "0u", 1, 1); + break; + } + default: { + LOG_CRITICAL(HW_GPU, "Unhandled system move: {}", + static_cast<u32>(instr.sys20.Value())); + UNREACHABLE(); + } + } + break; + } + case OpCode::Id::ISBERD: { + ASSERT(instr.isberd.o == 0); + ASSERT(instr.isberd.skew == 0); + ASSERT(instr.isberd.shift == Tegra::Shader::IsberdShift::None); + ASSERT(instr.isberd.mode == Tegra::Shader::IsberdMode::None); + ASSERT_MSG(stage == Maxwell3D::Regs::ShaderStage::Geometry, + "ISBERD is expected to be used in a geometry shader."); + LOG_WARNING(HW_GPU, "ISBERD instruction is incomplete"); + regs.SetRegisterToFloat(instr.gpr0, 0, regs.GetRegisterAsFloat(instr.gpr8), 1, 1); + break; + } case OpCode::Id::BRA: { ASSERT_MSG(instr.bra.constant_buffer == 0, "BRA with constant buffers are not implemented"); @@ -2911,7 +3083,7 @@ private: ShaderWriter shader; ShaderWriter declarations; - GLSLRegisterManager regs{shader, declarations, stage, suffix}; + GLSLRegisterManager regs{shader, declarations, stage, suffix, header}; // Declarations std::set<std::string> declr_predicates; |