diff options
Diffstat (limited to '')
-rw-r--r-- | src/video_core/renderer_opengl/gl_shader_decompiler.cpp | 581 |
1 files changed, 441 insertions, 140 deletions
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index f886e49ca..65fed77ef 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -16,11 +16,11 @@ namespace Decompiler { using Tegra::Shader::Attribute; using Tegra::Shader::Instruction; +using Tegra::Shader::LogicOperation; using Tegra::Shader::OpCode; using Tegra::Shader::Register; using Tegra::Shader::Sampler; using Tegra::Shader::SubOp; -using Tegra::Shader::Uniform; constexpr u32 PROGRAM_END = MAX_PROGRAM_CODE_LENGTH; @@ -267,6 +267,27 @@ public: } /** + * Returns code that does an integer size conversion for the specified size. + * @param value Value to perform integer size conversion on. + * @param size Register size to use for conversion instructions. + * @returns GLSL string corresponding to the value converted to the specified size. + */ + static std::string ConvertIntegerSize(const std::string& value, Register::Size size) { + switch (size) { + case Register::Size::Byte: + return "((" + value + " << 24) >> 24)"; + case Register::Size::Short: + return "((" + value + " << 16) >> 16)"; + case Register::Size::Word: + // Default - do nothing + return value; + default: + NGLOG_CRITICAL(HW_GPU, "Unimplemented conversion size {}", static_cast<u32>(size)); + UNREACHABLE(); + } + } + + /** * Gets a register as an float. * @param reg The register to get. * @param elem The element to use for the operation. @@ -282,15 +303,18 @@ public: * @param reg The register to get. * @param elem The element to use for the operation. * @param is_signed Whether to get the register as a signed (or unsigned) integer. + * @param size Register size to use for conversion instructions. * @returns GLSL string corresponding to the register as an integer. */ - std::string GetRegisterAsInteger(const Register& reg, unsigned elem = 0, - bool is_signed = true) { + std::string GetRegisterAsInteger(const Register& reg, unsigned elem = 0, bool is_signed = true, + Register::Size size = Register::Size::Word) { const std::string func = GetGLSLConversionFunc( GLSLRegister::Type::Float, is_signed ? GLSLRegister::Type::Integer : GLSLRegister::Type::UnsignedInteger); - return func + '(' + GetRegister(reg, elem) + ')'; + std::string value = func + '(' + GetRegister(reg, elem) + ')'; + + return ConvertIntegerSize(value, size); } /** @@ -300,13 +324,15 @@ public: * @param value The code representing the value to assign. * @param dest_num_components Number of components in the destination. * @param value_num_components Number of components in the value. - * @param is_abs Optional, when True, applies absolute value to output. + * @param is_saturated Optional, when True, saturates the provided value. * @param dest_elem Optional, the destination element to use for the operation. */ void SetRegisterToFloat(const Register& reg, u64 elem, const std::string& value, - u64 dest_num_components, u64 value_num_components, bool is_abs = false, - u64 dest_elem = 0) { - SetRegister(reg, elem, value, dest_num_components, value_num_components, is_abs, dest_elem); + u64 dest_num_components, u64 value_num_components, + bool is_saturated = false, u64 dest_elem = 0) { + + SetRegister(reg, elem, is_saturated ? "clamp(" + value + ", 0.0, 1.0)" : value, + dest_num_components, value_num_components, dest_elem); } /** @@ -316,18 +342,22 @@ public: * @param value The code representing the value to assign. * @param dest_num_components Number of components in the destination. * @param value_num_components Number of components in the value. - * @param is_abs Optional, when True, applies absolute value to output. + * @param is_saturated Optional, when True, saturates the provided value. * @param dest_elem Optional, the destination element to use for the operation. + * @param size Register size to use for conversion instructions. */ void SetRegisterToInteger(const Register& reg, bool is_signed, u64 elem, const std::string& value, u64 dest_num_components, - u64 value_num_components, bool is_abs = false, u64 dest_elem = 0) { + u64 value_num_components, bool is_saturated = false, + u64 dest_elem = 0, Register::Size size = Register::Size::Word) { + ASSERT_MSG(!is_saturated, "Unimplemented"); + const std::string func = GetGLSLConversionFunc( is_signed ? GLSLRegister::Type::Integer : GLSLRegister::Type::UnsignedInteger, GLSLRegister::Type::Float); - SetRegister(reg, elem, func + '(' + value + ')', dest_num_components, value_num_components, - is_abs, dest_elem); + SetRegister(reg, elem, func + '(' + ConvertIntegerSize(value, size) + ')', + dest_num_components, value_num_components, dest_elem); } /** @@ -365,11 +395,9 @@ public: } /// Generates code representing a uniform (C buffer) register, interpreted as the input type. - std::string GetUniform(const Uniform& uniform, GLSLRegister::Type type) { - declr_const_buffers[uniform.index].MarkAsUsed(static_cast<unsigned>(uniform.index), - static_cast<unsigned>(uniform.offset), stage); - std::string value = - 'c' + std::to_string(uniform.index) + '[' + std::to_string(uniform.offset) + ']'; + std::string GetUniform(u64 index, u64 offset, GLSLRegister::Type type) { + declr_const_buffers[index].MarkAsUsed(index, offset, stage); + std::string value = 'c' + std::to_string(index) + '[' + std::to_string(offset) + ']'; if (type == GLSLRegister::Type::Float) { return value; @@ -380,10 +408,19 @@ public: } } - /// Generates code representing a uniform (C buffer) register, interpreted as the type of the - /// destination register. - std::string GetUniform(const Uniform& uniform, const Register& dest_reg) { - return GetUniform(uniform, regs[dest_reg].GetActiveType()); + std::string GetUniformIndirect(u64 index, s64 offset, const Register& index_reg, + GLSLRegister::Type type) { + declr_const_buffers[index].MarkAsUsedIndirect(index, stage); + std::string value = 'c' + std::to_string(index) + "[(floatBitsToInt(" + + GetRegister(index_reg, 0) + ") + " + std::to_string(offset) + ") / 4]"; + + if (type == GLSLRegister::Type::Float) { + return value; + } else if (type == GLSLRegister::Type::Integer) { + return "floatBitsToInt(" + value + ')'; + } else { + UNREACHABLE(); + } } /// Add declarations for registers @@ -425,6 +462,14 @@ public: ++const_buffer_layout; } declarations.AddNewLine(); + + // Append the sampler2D array for the used textures. + size_t num_samplers = GetSamplers().size(); + if (num_samplers > 0) { + declarations.AddLine("uniform sampler2D " + SamplerEntry::GetArrayName(stage) + '[' + + std::to_string(num_samplers) + "];"); + declarations.AddNewLine(); + } } /// Returns a list of constant buffer declarations @@ -435,6 +480,32 @@ public: return result; } + /// Returns a list of samplers used in the shader + 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) { + size_t offset = static_cast<size_t>(sampler.index.Value()); + + // If this sampler has already been used, return the existing mapping. + auto itr = + std::find_if(used_samplers.begin(), used_samplers.end(), + [&](const SamplerEntry& entry) { return entry.GetOffset() == offset; }); + + if (itr != used_samplers.end()) { + return itr->GetName(); + } + + // Otherwise create a new mapping for this sampler + size_t next_index = used_samplers.size(); + SamplerEntry entry{stage, offset, next_index}; + used_samplers.emplace_back(entry); + return entry.GetName(); + } + private: /// Build GLSL conversion function, e.g. floatBitsToInt, intBitsToFloat, etc. const std::string GetGLSLConversionFunc(GLSLRegister::Type src, GLSLRegister::Type dest) const { @@ -460,13 +531,11 @@ private: * @param value The code representing the value to assign. * @param dest_num_components Number of components in the destination. * @param value_num_components Number of components in the value. - * @param is_abs Optional, when True, applies absolute value to output. * @param dest_elem Optional, the destination element to use for the operation. */ void SetRegister(const Register& reg, u64 elem, const std::string& value, - u64 dest_num_components, u64 value_num_components, bool is_abs, - u64 dest_elem) { - std::string dest = GetRegister(reg, dest_elem); + u64 dest_num_components, u64 value_num_components, u64 dest_elem) { + std::string dest = GetRegister(reg, static_cast<u32>(dest_elem)); if (dest_num_components > 1) { dest += GetSwizzle(elem); } @@ -476,8 +545,6 @@ private: src += GetSwizzle(elem); } - src = is_abs ? "abs(" + src + ')' : src; - shader.AddLine(dest + " = " + src + ';'); } @@ -498,7 +565,7 @@ private: // vertex shader, and what's the value of the fourth element when inside a Tess Eval // shader. ASSERT(stage == Maxwell3D::Regs::ShaderStage::Vertex); - return "vec4(0, 0, gl_InstanceID, gl_VertexID)"; + return "vec4(0, 0, uintBitsToFloat(gl_InstanceID), uintBitsToFloat(gl_VertexID))"; default: const u32 index{static_cast<u32>(attribute) - static_cast<u32>(Attribute::Index::Attribute_0)}; @@ -544,6 +611,7 @@ private: std::set<Attribute::Index> declr_input_attribute; std::set<Attribute::Index> declr_output_attribute; std::array<ConstBufferEntry, Maxwell3D::Regs::MaxConstBuffers> declr_const_buffers; + std::vector<SamplerEntry> used_samplers; const Maxwell3D::Regs::ShaderStage& stage; }; @@ -563,7 +631,7 @@ public: /// Returns entries in the shader that are useful for external functions ShaderEntries GetEntries() const { - return {regs.GetConstBuffersDeclarations()}; + return {regs.GetConstBuffersDeclarations(), regs.GetSamplers()}; } private: @@ -585,12 +653,8 @@ private: } /// Generates code representing a texture sampler. - std::string GetSampler(const Sampler& sampler) const { - // TODO(Subv): Support more than just texture sampler 0 - ASSERT_MSG(sampler.index == Sampler::Index::Sampler_0, "unsupported"); - const unsigned index{static_cast<unsigned>(sampler.index.Value()) - - static_cast<unsigned>(Sampler::Index::Sampler_0)}; - return "tex[" + std::to_string(index) + ']'; + std::string GetSampler(const Sampler& sampler) { + return regs.AccessSampler(sampler); } /** @@ -696,6 +760,31 @@ private: return (absolute_offset % SchedPeriod) == 0; } + void WriteLogicOperation(Register dest, LogicOperation logic_op, const std::string& op_a, + const std::string& op_b) { + switch (logic_op) { + case LogicOperation::And: { + regs.SetRegisterToInteger(dest, true, 0, '(' + op_a + " & " + op_b + ')', 1, 1); + break; + } + case LogicOperation::Or: { + regs.SetRegisterToInteger(dest, true, 0, '(' + op_a + " | " + op_b + ')', 1, 1); + break; + } + case LogicOperation::Xor: { + regs.SetRegisterToInteger(dest, true, 0, '(' + op_a + " ^ " + op_b + ')', 1, 1); + break; + } + case LogicOperation::PassB: { + regs.SetRegisterToInteger(dest, true, 0, op_b, 1, 1); + break; + } + default: + NGLOG_CRITICAL(HW_GPU, "Unimplemented logic operation: {}", static_cast<u32>(logic_op)); + UNREACHABLE(); + } + } + /** * Compiles a single instruction from Tegra to GLSL. * @param offset the offset of the Tegra shader instruction. @@ -733,21 +822,25 @@ private: switch (opcode->GetType()) { case OpCode::Type::Arithmetic: { - std::string op_a = instr.alu.negate_a ? "-" : ""; - op_a += regs.GetRegisterAsFloat(instr.gpr8); + std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); if (instr.alu.abs_a) { op_a = "abs(" + op_a + ')'; } - std::string op_b = instr.alu.negate_b ? "-" : ""; + if (instr.alu.negate_a) { + op_a = "-(" + op_a + ')'; + } + + std::string op_b; if (instr.is_b_imm) { - op_b += GetImmediate19(instr); + op_b = GetImmediate19(instr); } else { if (instr.is_b_gpr) { - op_b += regs.GetRegisterAsFloat(instr.gpr20); + op_b = regs.GetRegisterAsFloat(instr.gpr20); } else { - op_b += regs.GetUniform(instr.uniform, instr.gpr0); + op_b = regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset, + GLSLRegister::Type::Float); } } @@ -755,6 +848,10 @@ private: op_b = "abs(" + op_b + ')'; } + if (instr.alu.negate_b) { + op_b = "-(" + op_b + ')'; + } + switch (opcode->GetId()) { case OpCode::Id::MOV_C: case OpCode::Id::MOV_R: { @@ -762,58 +859,49 @@ private: break; } - case OpCode::Id::MOV32_IMM: { - // mov32i doesn't have abs or neg bits. - regs.SetRegisterToFloat(instr.gpr0, 0, GetImmediate32(instr), 1, 1); - break; - } case OpCode::Id::FMUL_C: case OpCode::Id::FMUL_R: case OpCode::Id::FMUL_IMM: { - regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " * " + op_b, 1, 1, instr.alu.abs_d); - break; - } - case OpCode::Id::FMUL32_IMM: { - // fmul32i doesn't have abs or neg bits. - regs.SetRegisterToFloat( - instr.gpr0, 0, - regs.GetRegisterAsFloat(instr.gpr8) + " * " + GetImmediate32(instr), 1, 1); + regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " * " + op_b, 1, 1, + instr.alu.saturate_d); break; } case OpCode::Id::FADD_C: case OpCode::Id::FADD_R: case OpCode::Id::FADD_IMM: { - regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " + " + op_b, 1, 1, instr.alu.abs_d); + regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " + " + op_b, 1, 1, + instr.alu.saturate_d); break; } case OpCode::Id::MUFU: { switch (instr.sub_op) { case SubOp::Cos: regs.SetRegisterToFloat(instr.gpr0, 0, "cos(" + op_a + ')', 1, 1, - instr.alu.abs_d); + instr.alu.saturate_d); break; case SubOp::Sin: regs.SetRegisterToFloat(instr.gpr0, 0, "sin(" + op_a + ')', 1, 1, - instr.alu.abs_d); + instr.alu.saturate_d); break; case SubOp::Ex2: regs.SetRegisterToFloat(instr.gpr0, 0, "exp2(" + op_a + ')', 1, 1, - instr.alu.abs_d); + instr.alu.saturate_d); break; case SubOp::Lg2: regs.SetRegisterToFloat(instr.gpr0, 0, "log2(" + op_a + ')', 1, 1, - instr.alu.abs_d); + instr.alu.saturate_d); break; case SubOp::Rcp: - regs.SetRegisterToFloat(instr.gpr0, 0, "1.0 / " + op_a, 1, 1, instr.alu.abs_d); + regs.SetRegisterToFloat(instr.gpr0, 0, "1.0 / " + op_a, 1, 1, + instr.alu.saturate_d); break; case SubOp::Rsq: regs.SetRegisterToFloat(instr.gpr0, 0, "inversesqrt(" + op_a + ')', 1, 1, - instr.alu.abs_d); + instr.alu.saturate_d); break; case SubOp::Min: regs.SetRegisterToFloat(instr.gpr0, 0, "min(" + op_a + "," + op_b + ')', 1, 1, - instr.alu.abs_d); + instr.alu.saturate_d); break; default: NGLOG_CRITICAL(HW_GPU, "Unhandled MUFU sub op: {0:x}", @@ -850,52 +938,49 @@ private: } break; } - case OpCode::Type::Logic: { - std::string op_a = regs.GetRegisterAsInteger(instr.gpr8, 0, false); - - if (instr.alu.lop.invert_a) - op_a = "~(" + op_a + ')'; - + case OpCode::Type::ArithmeticImmediate: { switch (opcode->GetId()) { - case OpCode::Id::LOP32I: { - u32 imm = static_cast<u32>(instr.alu.imm20_32.Value()); + case OpCode::Id::MOV32_IMM: { + regs.SetRegisterToFloat(instr.gpr0, 0, GetImmediate32(instr), 1, 1); + break; + } + case OpCode::Id::FMUL32_IMM: { + regs.SetRegisterToFloat( + instr.gpr0, 0, + regs.GetRegisterAsFloat(instr.gpr8) + " * " + GetImmediate32(instr), 1, 1); + break; + } + } + break; + } + case OpCode::Type::Bfe: { + ASSERT_MSG(!instr.bfe.negate_b, "Unimplemented"); - if (instr.alu.lop.invert_b) - imm = ~imm; + std::string op_a = instr.bfe.negate_a ? "-" : ""; + op_a += regs.GetRegisterAsInteger(instr.gpr8); - switch (instr.alu.lop.operation) { - case Tegra::Shader::LogicOperation::And: { - regs.SetRegisterToInteger(instr.gpr0, false, 0, - '(' + op_a + " & " + std::to_string(imm) + ')', 1, 1); - break; - } - case Tegra::Shader::LogicOperation::Or: { - regs.SetRegisterToInteger(instr.gpr0, false, 0, - '(' + op_a + " | " + std::to_string(imm) + ')', 1, 1); - break; - } - case Tegra::Shader::LogicOperation::Xor: { - regs.SetRegisterToInteger(instr.gpr0, false, 0, - '(' + op_a + " ^ " + std::to_string(imm) + ')', 1, 1); - break; - } - default: - NGLOG_CRITICAL(HW_GPU, "Unimplemented lop32i operation: {}", - static_cast<u32>(instr.alu.lop.operation.Value())); - UNREACHABLE(); - } + switch (opcode->GetId()) { + case OpCode::Id::BFE_IMM: { + std::string inner_shift = + '(' + op_a + " << " + std::to_string(instr.bfe.GetLeftShiftValue()) + ')'; + std::string outer_shift = + '(' + inner_shift + " >> " + + std::to_string(instr.bfe.GetLeftShiftValue() + instr.bfe.shift_position) + ')'; + + regs.SetRegisterToInteger(instr.gpr0, true, 0, outer_shift, 1, 1); break; } default: { - NGLOG_CRITICAL(HW_GPU, "Unhandled logic instruction: {}", opcode->GetName()); + NGLOG_CRITICAL(HW_GPU, "Unhandled BFE instruction: {}", opcode->GetName()); UNREACHABLE(); } } + break; } case OpCode::Type::Shift: { - std::string op_a = regs.GetRegisterAsInteger(instr.gpr8, 0, false); + std::string op_a = regs.GetRegisterAsInteger(instr.gpr8, 0, true); std::string op_b; if (instr.is_b_imm) { @@ -904,11 +989,25 @@ private: if (instr.is_b_gpr) { op_b += regs.GetRegisterAsInteger(instr.gpr20); } else { - op_b += regs.GetUniform(instr.uniform, GLSLRegister::Type::Integer); + op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset, + GLSLRegister::Type::Integer); } } switch (opcode->GetId()) { + case OpCode::Id::SHR_C: + case OpCode::Id::SHR_R: + case OpCode::Id::SHR_IMM: { + if (!instr.shift.is_signed) { + // Logical shift right + op_a = "uint(" + op_a + ')'; + } + + // Cast to int is superfluous for arithmetic shift, it's only for a logical shift + regs.SetRegisterToInteger(instr.gpr0, true, 0, "int(" + op_a + " >> " + op_b + ')', + 1, 1); + break; + } case OpCode::Id::SHL_C: case OpCode::Id::SHL_R: case OpCode::Id::SHL_IMM: @@ -922,28 +1021,101 @@ private: break; } - case OpCode::Type::ScaledAdd: { + case OpCode::Type::ArithmeticIntegerImmediate: { std::string op_a = regs.GetRegisterAsInteger(instr.gpr8); + std::string op_b = std::to_string(instr.alu.imm20_32.Value()); - if (instr.iscadd.negate_a) - op_a = '-' + op_a; + switch (opcode->GetId()) { + case OpCode::Id::IADD32I: + if (instr.iadd32i.negate_a) + op_a = "-(" + op_a + ')'; + + regs.SetRegisterToInteger(instr.gpr0, true, 0, op_a + " + " + op_b, 1, 1, + instr.iadd32i.saturate != 0); + break; + case OpCode::Id::LOP32I: { + if (instr.alu.lop32i.invert_a) + op_a = "~(" + op_a + ')'; - std::string op_b = instr.iscadd.negate_b ? "-" : ""; + if (instr.alu.lop32i.invert_b) + op_b = "~(" + op_b + ')'; + WriteLogicOperation(instr.gpr0, instr.alu.lop32i.operation, op_a, op_b); + break; + } + default: { + NGLOG_CRITICAL(HW_GPU, "Unhandled ArithmeticIntegerImmediate instruction: {}", + opcode->GetName()); + UNREACHABLE(); + } + } + break; + } + case OpCode::Type::ArithmeticInteger: { + std::string op_a = regs.GetRegisterAsInteger(instr.gpr8); + std::string op_b; if (instr.is_b_imm) { op_b += '(' + std::to_string(instr.alu.GetSignedImm20_20()) + ')'; } else { if (instr.is_b_gpr) { op_b += regs.GetRegisterAsInteger(instr.gpr20); } else { - op_b += regs.GetUniform(instr.uniform, GLSLRegister::Type::Integer); + op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset, + GLSLRegister::Type::Integer); } } - std::string shift = std::to_string(instr.iscadd.shift_amount.Value()); + switch (opcode->GetId()) { + case OpCode::Id::IADD_C: + case OpCode::Id::IADD_R: + case OpCode::Id::IADD_IMM: { + if (instr.alu_integer.negate_a) + op_a = "-(" + op_a + ')'; + + if (instr.alu_integer.negate_b) + op_b = "-(" + op_b + ')'; + + regs.SetRegisterToInteger(instr.gpr0, true, 0, op_a + " + " + op_b, 1, 1, + instr.alu.saturate_d); + break; + } + case OpCode::Id::ISCADD_C: + case OpCode::Id::ISCADD_R: + case OpCode::Id::ISCADD_IMM: { + if (instr.alu_integer.negate_a) + op_a = "-(" + op_a + ')'; + + if (instr.alu_integer.negate_b) + op_b = "-(" + op_b + ')'; + + std::string shift = std::to_string(instr.alu_integer.shift_amount.Value()); + + regs.SetRegisterToInteger(instr.gpr0, true, 0, + "((" + op_a + " << " + shift + ") + " + op_b + ')', 1, 1); + break; + } + case OpCode::Id::LOP_C: + case OpCode::Id::LOP_R: + case OpCode::Id::LOP_IMM: { + ASSERT_MSG(!instr.alu.lop.unk44, "Unimplemented"); + ASSERT_MSG(instr.alu.lop.pred48 == Pred::UnusedIndex, "Unimplemented"); + + if (instr.alu.lop.invert_a) + op_a = "~(" + op_a + ')'; + + if (instr.alu.lop.invert_b) + op_b = "~(" + op_b + ')'; + + WriteLogicOperation(instr.gpr0, instr.alu.lop.operation, op_a, op_b); + break; + } + default: { + NGLOG_CRITICAL(HW_GPU, "Unhandled ArithmeticInteger instruction: {}", + opcode->GetName()); + UNREACHABLE(); + } + } - regs.SetRegisterToInteger(instr.gpr0, true, 0, - "((" + op_a + " << " + shift + ") + " + op_b + ')', 1, 1); break; } case OpCode::Type::Ffma: { @@ -953,7 +1125,8 @@ private: switch (opcode->GetId()) { case OpCode::Id::FFMA_CR: { - op_b += regs.GetUniform(instr.uniform, instr.gpr0); + op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset, + GLSLRegister::Type::Float); op_c += regs.GetRegisterAsFloat(instr.gpr39); break; } @@ -964,7 +1137,8 @@ private: } case OpCode::Id::FFMA_RC: { op_b += regs.GetRegisterAsFloat(instr.gpr39); - op_c += regs.GetUniform(instr.uniform, instr.gpr0); + op_c += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset, + GLSLRegister::Type::Float); break; } case OpCode::Id::FFMA_IMM: { @@ -978,31 +1152,33 @@ private: } } - regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " * " + op_b + " + " + op_c, 1, 1); + regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " * " + op_b + " + " + op_c, 1, 1, + instr.alu.saturate_d); break; } case OpCode::Type::Conversion: { - ASSERT_MSG(instr.conversion.size == Register::Size::Word, "Unimplemented"); ASSERT_MSG(!instr.conversion.negate_a, "Unimplemented"); - ASSERT_MSG(!instr.conversion.saturate_a, "Unimplemented"); switch (opcode->GetId()) { case OpCode::Id::I2I_R: { ASSERT_MSG(!instr.conversion.selector, "Unimplemented"); - std::string op_a = - regs.GetRegisterAsInteger(instr.gpr20, 0, instr.conversion.is_signed); + std::string op_a = regs.GetRegisterAsInteger( + instr.gpr20, 0, instr.conversion.is_input_signed, instr.conversion.src_size); if (instr.conversion.abs_a) { op_a = "abs(" + op_a + ')'; } - regs.SetRegisterToInteger(instr.gpr0, instr.conversion.is_signed, 0, op_a, 1, 1); + regs.SetRegisterToInteger(instr.gpr0, instr.conversion.is_output_signed, 0, op_a, 1, + 1, instr.alu.saturate_d, 0, instr.conversion.dest_size); break; } case OpCode::Id::I2F_R: { - std::string op_a = - regs.GetRegisterAsInteger(instr.gpr20, 0, instr.conversion.is_signed); + ASSERT_MSG(instr.conversion.dest_size == Register::Size::Word, "Unimplemented"); + ASSERT_MSG(!instr.conversion.selector, "Unimplemented"); + std::string op_a = regs.GetRegisterAsInteger( + instr.gpr20, 0, instr.conversion.is_input_signed, instr.conversion.src_size); if (instr.conversion.abs_a) { op_a = "abs(" + op_a + ')'; @@ -1012,13 +1188,71 @@ private: break; } case OpCode::Id::F2F_R: { + ASSERT_MSG(instr.conversion.dest_size == Register::Size::Word, "Unimplemented"); + ASSERT_MSG(instr.conversion.src_size == Register::Size::Word, "Unimplemented"); std::string op_a = regs.GetRegisterAsFloat(instr.gpr20); + switch (instr.conversion.f2f.rounding) { + case Tegra::Shader::F2fRoundingOp::None: + break; + case Tegra::Shader::F2fRoundingOp::Floor: + op_a = "floor(" + op_a + ')'; + break; + case Tegra::Shader::F2fRoundingOp::Ceil: + op_a = "ceil(" + op_a + ')'; + break; + case Tegra::Shader::F2fRoundingOp::Trunc: + op_a = "trunc(" + op_a + ')'; + break; + default: + NGLOG_CRITICAL(HW_GPU, "Unimplemented f2f rounding mode {}", + static_cast<u32>(instr.conversion.f2f.rounding.Value())); + UNREACHABLE(); + break; + } + if (instr.conversion.abs_a) { op_a = "abs(" + op_a + ')'; } - regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1); + regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1, instr.alu.saturate_d); + break; + } + case OpCode::Id::F2I_R: { + ASSERT_MSG(instr.conversion.src_size == Register::Size::Word, "Unimplemented"); + std::string op_a = regs.GetRegisterAsFloat(instr.gpr20); + + if (instr.conversion.abs_a) { + op_a = "abs(" + op_a + ')'; + } + + switch (instr.conversion.f2i.rounding) { + case Tegra::Shader::F2iRoundingOp::None: + break; + case Tegra::Shader::F2iRoundingOp::Floor: + op_a = "floor(" + op_a + ')'; + break; + case Tegra::Shader::F2iRoundingOp::Ceil: + op_a = "ceil(" + op_a + ')'; + break; + case Tegra::Shader::F2iRoundingOp::Trunc: + op_a = "trunc(" + op_a + ')'; + break; + default: + NGLOG_CRITICAL(HW_GPU, "Unimplemented f2i rounding mode {}", + static_cast<u32>(instr.conversion.f2i.rounding.Value())); + UNREACHABLE(); + break; + } + + if (instr.conversion.is_output_signed) { + op_a = "int(" + op_a + ')'; + } else { + op_a = "uint(" + op_a + ')'; + } + + regs.SetRegisterToInteger(instr.gpr0, instr.conversion.is_output_signed, 0, op_a, 1, + 1, false, 0, instr.conversion.dest_size); break; } default: { @@ -1029,36 +1263,60 @@ private: break; } case OpCode::Type::Memory: { - const Attribute::Index attribute = instr.attribute.fmt20.index; - switch (opcode->GetId()) { case OpCode::Id::LD_A: { ASSERT_MSG(instr.attribute.fmt20.size == 0, "untested"); regs.SetRegisterToInputAttibute(instr.gpr0, instr.attribute.fmt20.element, - attribute); + instr.attribute.fmt20.index); + break; + } + case OpCode::Id::LD_C: { + ASSERT_MSG(instr.ld_c.unknown == 0, "Unimplemented"); + + std::string op_a = + regs.GetUniformIndirect(instr.cbuf36.index, instr.cbuf36.offset + 0, instr.gpr8, + GLSLRegister::Type::Float); + std::string op_b = + regs.GetUniformIndirect(instr.cbuf36.index, instr.cbuf36.offset + 4, instr.gpr8, + GLSLRegister::Type::Float); + + switch (instr.ld_c.type.Value()) { + case Tegra::Shader::UniformType::Single: + regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1); + break; + + case Tegra::Shader::UniformType::Double: + regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1); + regs.SetRegisterToFloat(instr.gpr0.Value() + 1, 0, op_b, 1, 1); + break; + + default: + NGLOG_CRITICAL(HW_GPU, "Unhandled type: {}", + static_cast<unsigned>(instr.ld_c.type.Value())); + UNREACHABLE(); + } break; } case OpCode::Id::ST_A: { ASSERT_MSG(instr.attribute.fmt20.size == 0, "untested"); - regs.SetOutputAttributeToRegister(attribute, instr.attribute.fmt20.element, - instr.gpr0); + regs.SetOutputAttributeToRegister(instr.attribute.fmt20.index, + instr.attribute.fmt20.element, instr.gpr0); break; } case OpCode::Id::TEX: { - ASSERT_MSG(instr.attribute.fmt20.size == 4, "untested"); const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); const std::string op_b = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); const std::string sampler = GetSampler(instr.sampler); const std::string coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");"; - // Add an extra scope and declare the texture coords inside to prevent overwriting - // them in case they are used as outputs of the texs instruction. + // Add an extra scope and declare the texture coords inside to prevent + // overwriting them in case they are used as outputs of the texs instruction. shader.AddLine("{"); ++shader.scope; shader.AddLine(coord); const std::string texture = "texture(" + sampler + ", coords)"; size_t dest_elem{}; - for (size_t elem = 0; elem < instr.attribute.fmt20.size; ++elem) { + for (size_t elem = 0; elem < 4; ++elem) { if (!instr.tex.IsComponentEnabled(elem)) { // Skip disabled components continue; @@ -1071,7 +1329,6 @@ private: break; } case OpCode::Id::TEXS: { - ASSERT_MSG(instr.attribute.fmt20.size == 4, "untested"); const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20); const std::string sampler = GetSampler(instr.sampler); @@ -1083,8 +1340,8 @@ private: shader.AddLine(coord); const std::string texture = "texture(" + sampler + ", coords)"; - // TEXS has two destination registers. RG goes into gpr0+0 and gpr0+1, and BA goes - // into gpr28+0 and gpr28+1 + // TEXS has two destination registers. RG goes into gpr0+0 and gpr0+1, and BA + // goes into gpr28+0 and gpr28+1 size_t offset{}; for (const auto& dest : {instr.gpr0.Value(), instr.gpr28.Value()}) { @@ -1134,7 +1391,8 @@ private: if (instr.is_b_gpr) { op_b += regs.GetRegisterAsFloat(instr.gpr20); } else { - op_b += regs.GetUniform(instr.uniform, GLSLRegister::Type::Float); + op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset, + GLSLRegister::Type::Float); } } @@ -1167,15 +1425,17 @@ private: } case OpCode::Type::IntegerSetPredicate: { std::string op_a = regs.GetRegisterAsInteger(instr.gpr8, 0, instr.isetp.is_signed); + std::string op_b; - std::string op_b{}; - - ASSERT_MSG(!instr.is_b_imm, "ISETP_IMM not implemented"); - - if (instr.is_b_gpr) { - op_b += regs.GetRegisterAsInteger(instr.gpr20, 0, instr.isetp.is_signed); + if (instr.is_b_imm) { + op_b += '(' + std::to_string(instr.alu.GetSignedImm20_20()) + ')'; } else { - op_b += regs.GetUniform(instr.uniform, GLSLRegister::Type::Integer); + if (instr.is_b_gpr) { + op_b += regs.GetRegisterAsInteger(instr.gpr20, 0, instr.isetp.is_signed); + } else { + op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset, + GLSLRegister::Type::Integer); + } } using Tegra::Shader::Pred; @@ -1221,7 +1481,8 @@ private: if (instr.is_b_gpr) { op_b += regs.GetRegisterAsFloat(instr.gpr20); } else { - op_b += regs.GetUniform(instr.uniform, GLSLRegister::Type::Float); + op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset, + GLSLRegister::Type::Float); } } @@ -1229,8 +1490,8 @@ private: op_b = "abs(" + op_b + ')'; } - // The fset instruction sets a register to 1.0 if the condition is true, and to 0 - // otherwise. + // The fset instruction sets a register to 1.0 or -1 (depending on the bf bit) if the + // condition is true, and to 0 otherwise. std::string second_pred = GetPredicateCondition(instr.fset.pred39, instr.fset.neg_pred != 0); @@ -1248,6 +1509,41 @@ private: } break; } + case OpCode::Type::IntegerSet: { + std::string op_a = regs.GetRegisterAsInteger(instr.gpr8, 0, instr.iset.is_signed); + + std::string op_b; + + if (instr.is_b_imm) { + op_b = std::to_string(instr.alu.GetSignedImm20_20()); + } else { + if (instr.is_b_gpr) { + op_b = regs.GetRegisterAsInteger(instr.gpr20, 0, instr.iset.is_signed); + } else { + op_b = regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset, + GLSLRegister::Type::Integer); + } + } + + // The iset instruction sets a register to 1.0 or -1 (depending on the bf bit) if the + // condition is true, and to 0 otherwise. + std::string second_pred = + GetPredicateCondition(instr.iset.pred39, instr.iset.neg_pred != 0); + + std::string comparator = GetPredicateComparison(instr.iset.cond); + std::string combiner = GetPredicateCombiner(instr.iset.op); + + std::string predicate = "(((" + op_a + ") " + comparator + " (" + op_b + ")) " + + combiner + " (" + second_pred + "))"; + + if (instr.iset.bf) { + regs.SetRegisterToFloat(instr.gpr0, 0, predicate + " ? 1.0 : 0.0", 1, 1); + } else { + regs.SetRegisterToInteger(instr.gpr0, false, 0, predicate + " ? 0xFFFFFFFF : 0", 1, + 1); + } + break; + } default: { switch (opcode->GetId()) { case OpCode::Id::EXIT: { @@ -1261,8 +1557,8 @@ private: shader.AddLine("return true;"); if (instr.pred.pred_index == static_cast<u64>(Pred::UnusedIndex)) { - // If this is an unconditional exit then just end processing here, otherwise we - // have to account for the possibility of the condition not being met, so + // If this is an unconditional exit then just end processing here, otherwise + // we have to account for the possibility of the condition not being met, so // continue processing the next instruction. offset = PROGRAM_END - 1; } @@ -1284,6 +1580,11 @@ private: regs.SetRegisterToInputAttibute(instr.gpr0, attribute.element, attribute.index); break; } + case OpCode::Id::SSY: { + // The SSY opcode tells the GPU where to re-converge divergent execution paths, we + // can ignore this when generating GLSL code. + break; + } default: { NGLOG_CRITICAL(HW_GPU, "Unhandled instruction: {}", opcode->GetName()); UNREACHABLE(); |