From f8a292f920fe89b42fb961e6d849595ba2c9bdfb Mon Sep 17 00:00:00 2001 From: wwylele Date: Sat, 24 Mar 2018 02:51:06 +0300 Subject: [PATCH 1/4] renderer_opengl: add PICA->GLSL shader decompiler --- src/video_core/CMakeLists.txt | 2 + .../renderer_opengl/gl_shader_decompiler.cpp | 911 ++++++++++++++++++ .../renderer_opengl/gl_shader_decompiler.h | 27 + 3 files changed, 940 insertions(+) create mode 100644 src/video_core/renderer_opengl/gl_shader_decompiler.cpp create mode 100644 src/video_core/renderer_opengl/gl_shader_decompiler.h diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 0bb491976..6a5d5764b 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -28,6 +28,8 @@ add_library(video_core STATIC renderer_opengl/gl_rasterizer_cache.cpp renderer_opengl/gl_rasterizer_cache.h renderer_opengl/gl_resource_manager.h + renderer_opengl/gl_shader_decompiler.cpp + renderer_opengl/gl_shader_decompiler.h renderer_opengl/gl_shader_gen.cpp renderer_opengl/gl_shader_gen.h renderer_opengl/gl_shader_util.cpp diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp new file mode 100644 index 000000000..e7767faf6 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -0,0 +1,911 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include "common/assert.h" +#include "common/common_types.h" +#include "video_core/renderer_opengl/gl_shader_decompiler.h" + +namespace Pica { +namespace Shader { +namespace Decompiler { + +using nihstro::Instruction; +using nihstro::OpCode; +using nihstro::RegisterType; +using nihstro::SourceRegister; +using nihstro::SwizzlePattern; + +constexpr u32 PROGRAM_END = MAX_PROGRAM_CODE_LENGTH; + +/// 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. + AlwaysReturn, ///< All code paths reach the return point. + Conditional, ///< Code path reaches the return point or an END instruction conditionally. + AlwaysEnd, ///< All code paths reach a END instruction. +}; + +/// A subroutine is a range of code refereced by a CALL, IF or LOOP instruction. +struct Subroutine { + /// Generates a name suitable for GLSL source code. + std::string GetName() const { + return "sub_" + std::to_string(begin) + "_" + std::to_string(end); + } + + u32 begin; ///< Entry point of the subroutine. + u32 end; ///< Return point of the subroutine. + ExitMethod exit_method; ///< Exit method of the subroutine. + std::set labels; ///< Addresses refereced by JMP instructions. + + bool operator<(const Subroutine& rhs) const { + if (begin == rhs.begin) { + return end < rhs.end; + } + return begin < rhs.begin; + } +}; + +/// Analyzes shader code and produces a set of subroutines. +class ControlFlowAnalyzer { +public: + ControlFlowAnalyzer(const ProgramCode& program_code, u32 main_offset) + : program_code(program_code) { + + // Recursively finds all subroutines. + const Subroutine& program_main = AddSubroutine(main_offset, PROGRAM_END); + ASSERT(program_main.exit_method == ExitMethod::AlwaysEnd); + } + + std::set GetSubroutines() { + return std::move(subroutines); + } + +private: + const ProgramCode& program_code; + std::set subroutines; + std::map, 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}); + if (iter != subroutines.end()) + return *iter; + + Subroutine subroutine{begin, end}; + subroutine.exit_method = Scan(begin, end, subroutine.labels); + return *subroutines.insert(std::move(subroutine)).first; + } + + /// Merges exit method of two parallel branches. + static ExitMethod ParallelExit(ExitMethod a, ExitMethod b) { + if (a == ExitMethod::Undetermined) { + return b; + } + if (b == ExitMethod::Undetermined) { + return a; + } + if (a == b) { + return a; + } + return ExitMethod::Conditional; + } + + /// Cascades exit method of two blocks of code. + static ExitMethod SeriesExit(ExitMethod a, ExitMethod b) { + // This should be handled before evaluating b. + DEBUG_ASSERT(a != ExitMethod::AlwaysEnd); + + if (a == ExitMethod::Undetermined) { + return ExitMethod::Undetermined; + } + + if (a == ExitMethod::AlwaysReturn) { + return b; + } + + if (b == ExitMethod::Undetermined || b == ExitMethod::AlwaysEnd) { + return ExitMethod::AlwaysEnd; + } + + return ExitMethod::Conditional; + } + + /// Scans a range of code for labels and determines the exit method. + ExitMethod Scan(u32 begin, u32 end, std::set& labels) { + auto [iter, inserted] = + exit_method_map.emplace(std::make_pair(begin, end), ExitMethod::Undetermined); + ExitMethod& exit_method = iter->second; + if (!inserted) + return exit_method; + + u32 offset = begin; + for (u32 offset = begin; offset < (begin > end ? PROGRAM_END : end); ++offset) { + const Instruction instr = {program_code[offset]}; + switch (instr.opcode.Value()) { + case OpCode::Id::END: { + return exit_method = ExitMethod::AlwaysEnd; + } + case OpCode::Id::JMPC: + case OpCode::Id::JMPU: { + labels.insert(instr.flow_control.dest_offset); + ExitMethod no_jmp = Scan(offset + 1, end, labels); + ExitMethod jmp = Scan(instr.flow_control.dest_offset, end, labels); + return exit_method = ParallelExit(no_jmp, jmp); + } + case OpCode::Id::CALL: { + auto& call = AddSubroutine(instr.flow_control.dest_offset, + instr.flow_control.dest_offset + + instr.flow_control.num_instructions); + if (call.exit_method == ExitMethod::AlwaysEnd) + return exit_method = ExitMethod::AlwaysEnd; + ExitMethod after_call = Scan(offset + 1, end, labels); + return exit_method = SeriesExit(call.exit_method, after_call); + } + case OpCode::Id::LOOP: { + auto& loop = AddSubroutine(offset + 1, instr.flow_control.dest_offset + 1); + if (loop.exit_method == ExitMethod::AlwaysEnd) + return exit_method = ExitMethod::AlwaysEnd; + ExitMethod after_loop = Scan(instr.flow_control.dest_offset + 1, end, labels); + return exit_method = SeriesExit(loop.exit_method, after_loop); + } + case OpCode::Id::CALLC: + case OpCode::Id::CALLU: { + auto& call = AddSubroutine(instr.flow_control.dest_offset, + instr.flow_control.dest_offset + + instr.flow_control.num_instructions); + ExitMethod after_call = Scan(offset + 1, end, labels); + return exit_method = SeriesExit( + ParallelExit(call.exit_method, ExitMethod::AlwaysReturn), after_call); + } + case OpCode::Id::IFU: + case OpCode::Id::IFC: { + auto& if_sub = AddSubroutine(offset + 1, instr.flow_control.dest_offset); + ExitMethod else_method; + if (instr.flow_control.num_instructions != 0) { + auto& else_sub = AddSubroutine(instr.flow_control.dest_offset, + instr.flow_control.dest_offset + + instr.flow_control.num_instructions); + else_method = else_sub.exit_method; + } else { + else_method = ExitMethod::AlwaysReturn; + } + + ExitMethod both = ParallelExit(if_sub.exit_method, else_method); + if (both == ExitMethod::AlwaysEnd) + return exit_method = ExitMethod::AlwaysEnd; + ExitMethod after_call = + Scan(instr.flow_control.dest_offset + instr.flow_control.num_instructions, end, + labels); + return exit_method = SeriesExit(both, after_call); + } + } + } + return exit_method = ExitMethod::AlwaysReturn; + } +}; + +class ShaderWriter { +public: + void AddLine(const std::string& text) { + ASSERT(scope >= 0); + if (!text.empty()) { + shader_source += std::string(static_cast(scope) * 4, ' '); + } + shader_source += text + '\n'; + } + + std::string GetResult() { + return std::move(shader_source); + } + + int scope = 0; + +private: + std::string shader_source; +}; + +/// An adaptor for getting swizzle pattern string from nihstro interfaces. +template +std::string GetSelectorSrc(const SwizzlePattern& pattern) { + std::string out; + for (std::size_t i = 0; i < 4; ++i) { + switch ((pattern.*getter)(i)) { + case SwizzlePattern::Selector::x: + out += "x"; + break; + case SwizzlePattern::Selector::y: + out += "y"; + break; + case SwizzlePattern::Selector::z: + out += "z"; + break; + case SwizzlePattern::Selector::w: + out += "w"; + break; + default: + UNREACHABLE(); + return ""; + } + } + return out; +} + +constexpr auto GetSelectorSrc1 = GetSelectorSrc<&SwizzlePattern::GetSelectorSrc1>; +constexpr auto GetSelectorSrc2 = GetSelectorSrc<&SwizzlePattern::GetSelectorSrc2>; +constexpr auto GetSelectorSrc3 = GetSelectorSrc<&SwizzlePattern::GetSelectorSrc3>; + +class GLSLGenerator { +public: + GLSLGenerator(const std::set& subroutines, const ProgramCode& program_code, + const SwizzleData& swizzle_data, u32 main_offset, + const RegGetter& inputreg_getter, const RegGetter& outputreg_getter, + bool sanitize_mul, bool is_gs) + : subroutines(subroutines), program_code(program_code), swizzle_data(swizzle_data), + main_offset(main_offset), inputreg_getter(inputreg_getter), + outputreg_getter(outputreg_getter), sanitize_mul(sanitize_mul), is_gs(is_gs) { + + Generate(); + } + + std::string GetShaderCode() { + return shader.GetResult(); + } + +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}); + ASSERT(iter != subroutines.end()); + return *iter; + } + + /// Generates condition evaluation code for the flow control instruction. + static std::string EvaluateCondition(Instruction::FlowControlType flow_control) { + using Op = Instruction::FlowControlType::Op; + + std::string result_x = + flow_control.refx.Value() ? "conditional_code.x" : "!conditional_code.x"; + std::string result_y = + flow_control.refy.Value() ? "conditional_code.y" : "!conditional_code.y"; + + switch (flow_control.op) { + case Op::JustX: + return result_x; + case Op::JustY: + return result_y; + case Op::Or: + case Op::And: { + std::string and_or = flow_control.op == Op::Or ? "any" : "all"; + std::string bvec; + if (flow_control.refx.Value() && flow_control.refy.Value()) { + bvec = "conditional_code"; + } else if (!flow_control.refx.Value() && !flow_control.refy.Value()) { + bvec = "not(conditional_code)"; + } else { + bvec = "bvec2(" + result_x + ", " + result_y + ")"; + } + return and_or + "(" + bvec + ")"; + } + default: + UNREACHABLE(); + return ""; + } + }; + + /// Generates code representing a source register. + std::string GetSourceRegister(const SourceRegister& source_reg, + u32 address_register_index) const { + u32 index = static_cast(source_reg.GetIndex()); + std::string index_str = std::to_string(index); + + switch (source_reg.GetRegisterType()) { + case RegisterType::Input: + return inputreg_getter(index); + case RegisterType::Temporary: + return "reg_tmp" + index_str; + case RegisterType::FloatUniform: + if (address_register_index != 0) { + index_str += + std::string(" + address_registers.") + "xyz"[address_register_index - 1]; + } + return "uniforms.f[" + index_str + "]"; + default: + UNREACHABLE(); + return ""; + } + }; + + /// Generates code representing a destination register. + std::string GetDestRegister(const DestRegister& dest_reg) const { + u32 index = static_cast(dest_reg.GetIndex()); + + switch (dest_reg.GetRegisterType()) { + case RegisterType::Output: + return outputreg_getter(index); + case RegisterType::Temporary: + return "reg_tmp" + std::to_string(index); + default: + UNREACHABLE(); + return ""; + } + }; + + /// Generates code representing a bool uniform + std::string GetUniformBool(u32 index) const { + if (is_gs && index == 15) { + // The uniform b15 is set to true after every geometry shader invocation. + return "((gl_PrimitiveIDIn == 0) || uniforms.b[15])"; + } + return "uniforms.b[" + std::to_string(index) + "]"; + }; + + /** + * Adds code that calls a subroutine. + * @param subroutine the subroutine to call. + */ + void CallSubroutine(const Subroutine& subroutine) { + if (subroutine.exit_method == ExitMethod::AlwaysEnd) { + shader.AddLine(subroutine.GetName() + "();"); + shader.AddLine("return true;"); + } else if (subroutine.exit_method == ExitMethod::Conditional) { + shader.AddLine("if (" + subroutine.GetName() + "()) { return true; }"); + } else { + shader.AddLine(subroutine.GetName() + "();"); + } + }; + + /** + * Writes code that does an assignment operation. + * @param swizzle the swizzle data of the current instruction. + * @param reg the destination register code. + * @param value the code representing the value to assign. + * @param dest_num_components number of components of the destination register. + * @param value_num_components number of components of the value to assign. + */ + void SetDest(const SwizzlePattern& swizzle, const std::string& reg, const std::string& value, + u32 dest_num_components, u32 value_num_components) { + u32 dest_mask_num_components = 0; + std::string dest_mask_swizzle = "."; + + for (u32 i = 0; i < dest_num_components; ++i) { + if (swizzle.DestComponentEnabled(static_cast(i))) { + dest_mask_swizzle += "xyzw"[i]; + ++dest_mask_num_components; + } + } + + if (reg.empty() || dest_mask_num_components == 0) { + return; + } + ASSERT(value_num_components >= dest_num_components || value_num_components == 1); + + std::string dest = reg + (dest_num_components != 1 ? dest_mask_swizzle : ""); + + std::string src = value; + if (value_num_components == 1) { + if (dest_mask_num_components != 1) { + src = "vec" + std::to_string(dest_mask_num_components) + "(" + value + ")"; + } + } else if (value_num_components != dest_mask_num_components) { + src = "(" + value + ")" + dest_mask_swizzle; + } + + shader.AddLine(dest + " = " + src + ";"); + }; + + /** + * Compiles a single instruction from PICA to GLSL. + * @param offset the offset of the PICA shader instruction. + * @return the offset of the next instruction to execute. Usually it is the current offset + 1. + * If the current instruction is IF or LOOP, the next instruction is after the IF or LOOP block. + * If the current instruction always terminates the program, returns PROGRAM_END. + */ + u32 CompileInstr(u32 offset) { + const Instruction instr = {program_code[offset]}; + + size_t swizzle_offset = instr.opcode.Value().GetInfo().type == OpCode::Type::MultiplyAdd + ? instr.mad.operand_desc_id + : instr.common.operand_desc_id; + const SwizzlePattern swizzle = {swizzle_data[swizzle_offset]}; + + shader.AddLine("// " + std::to_string(offset) + ": " + instr.opcode.Value().GetInfo().name); + + switch (instr.opcode.Value().GetInfo().type) { + case OpCode::Type::Arithmetic: { + const bool is_inverted = + (0 != (instr.opcode.Value().GetInfo().subtype & OpCode::Info::SrcInversed)); + + std::string src1 = swizzle.negate_src1 ? "-" : ""; + src1 += GetSourceRegister(instr.common.GetSrc1(is_inverted), + !is_inverted * instr.common.address_register_index); + src1 += "." + GetSelectorSrc1(swizzle); + + std::string src2 = swizzle.negate_src2 ? "-" : ""; + src2 += GetSourceRegister(instr.common.GetSrc2(is_inverted), + is_inverted * instr.common.address_register_index); + src2 += "." + GetSelectorSrc2(swizzle); + + std::string dest_reg = GetDestRegister(instr.common.dest.Value()); + + switch (instr.opcode.Value().EffectiveOpCode()) { + case OpCode::Id::ADD: { + SetDest(swizzle, dest_reg, src1 + " + " + src2, 4, 4); + break; + } + + case OpCode::Id::MUL: { + if (sanitize_mul) { + SetDest(swizzle, dest_reg, "sanitize_mul(" + src1 + ", " + src2 + ")", 4, 4); + } else { + SetDest(swizzle, dest_reg, src1 + " * " + src2, 4, 4); + } + break; + } + + case OpCode::Id::FLR: { + SetDest(swizzle, dest_reg, "floor(" + src1 + ")", 4, 4); + break; + } + + case OpCode::Id::MAX: { + SetDest(swizzle, dest_reg, "max(" + src1 + ", " + src2 + ")", 4, 4); + break; + } + + case OpCode::Id::MIN: { + SetDest(swizzle, dest_reg, "min(" + src1 + ", " + src2 + ")", 4, 4); + break; + } + + case OpCode::Id::DP3: + case OpCode::Id::DP4: + case OpCode::Id::DPH: + case OpCode::Id::DPHI: { + OpCode::Id opcode = instr.opcode.Value().EffectiveOpCode(); + std::string dot; + if (opcode == OpCode::Id::DP3) { + if (sanitize_mul) { + dot = "dot(vec3(sanitize_mul(" + src1 + ", " + src2 + ")), vec3(1.0))"; + } else { + dot = "dot(vec3(" + src1 + "), vec3(" + src2 + "))"; + } + } else { + std::string src1_ = (opcode == OpCode::Id::DPH || opcode == OpCode::Id::DPHI) + ? "vec4(" + src1 + ".xyz, 1.0)" + : src1; + if (sanitize_mul) { + dot = "dot(sanitize_mul(" + src1_ + ", " + src2 + "), vec4(1.0))"; + } else { + dot = "dot(" + src1 + ", " + src2 + ")"; + } + } + + SetDest(swizzle, dest_reg, dot, 4, 1); + break; + } + + case OpCode::Id::RCP: { + SetDest(swizzle, dest_reg, "(1.0 / " + src1 + ".x)", 4, 1); + break; + } + + case OpCode::Id::RSQ: { + SetDest(swizzle, dest_reg, "inversesqrt(" + src1 + ".x)", 4, 1); + break; + } + + case OpCode::Id::MOVA: { + SetDest(swizzle, "address_registers", "ivec2(" + src1 + ")", 2, 2); + break; + } + + case OpCode::Id::MOV: { + SetDest(swizzle, dest_reg, src1, 4, 4); + break; + } + + case OpCode::Id::SGE: + case OpCode::Id::SGEI: { + SetDest(swizzle, dest_reg, "vec4(greaterThanEqual(" + src1 + "," + src2 + "))", 4, + 4); + break; + } + + case OpCode::Id::SLT: + case OpCode::Id::SLTI: { + SetDest(swizzle, dest_reg, "vec4(lessThan(" + src1 + "," + src2 + "))", 4, 4); + break; + } + + case OpCode::Id::CMP: { + using CompareOp = Instruction::Common::CompareOpType::Op; + const std::map> cmp_ops{ + {CompareOp::Equal, {"==", "equal"}}, + {CompareOp::NotEqual, {"!=", "notEqual"}}, + {CompareOp::LessThan, {"<", "lessThan"}}, + {CompareOp::LessEqual, {"<=", "lessThanEqual"}}, + {CompareOp::GreaterThan, {">", "greaterThan"}}, + {CompareOp::GreaterEqual, {">=", "greaterThanEqual"}}}; + + const CompareOp op_x = instr.common.compare_op.x.Value(); + const CompareOp op_y = instr.common.compare_op.y.Value(); + + if (cmp_ops.find(op_x) == cmp_ops.end()) { + LOG_ERROR(HW_GPU, "Unknown compare mode %x", static_cast(op_x)); + } else if (cmp_ops.find(op_y) == cmp_ops.end()) { + LOG_ERROR(HW_GPU, "Unknown compare mode %x", static_cast(op_y)); + } else if (op_x != op_y) { + shader.AddLine("conditional_code.x = " + src1 + ".x " + + cmp_ops.find(op_x)->second.first + " " + src2 + ".x;"); + shader.AddLine("conditional_code.y = " + src1 + ".y " + + cmp_ops.find(op_y)->second.first + " " + src2 + ".y;"); + } else { + shader.AddLine("conditional_code = " + cmp_ops.find(op_x)->second.second + + "(vec2(" + src1 + "), vec2(" + src2 + "));"); + } + break; + } + + case OpCode::Id::EX2: { + SetDest(swizzle, dest_reg, "exp2(" + src1 + ".x)", 4, 1); + break; + } + + case OpCode::Id::LG2: { + SetDest(swizzle, dest_reg, "log2(" + src1 + ".x)", 4, 1); + break; + } + + default: { + LOG_ERROR(HW_GPU, "Unhandled arithmetic instruction: 0x%02x (%s): 0x%08x", + (int)instr.opcode.Value().EffectiveOpCode(), + instr.opcode.Value().GetInfo().name, instr.hex); + DEBUG_ASSERT(false); + break; + } + } + + break; + } + + case OpCode::Type::MultiplyAdd: { + if ((instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MAD) || + (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MADI)) { + bool is_inverted = (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MADI); + + std::string src1 = swizzle.negate_src1 ? "-" : ""; + src1 += GetSourceRegister(instr.mad.GetSrc1(is_inverted), 0); + src1 += "." + GetSelectorSrc1(swizzle); + + std::string src2 = swizzle.negate_src2 ? "-" : ""; + src2 += GetSourceRegister(instr.mad.GetSrc2(is_inverted), + !is_inverted * instr.mad.address_register_index); + src2 += "." + GetSelectorSrc2(swizzle); + + std::string src3 = swizzle.negate_src3 ? "-" : ""; + src3 += GetSourceRegister(instr.mad.GetSrc3(is_inverted), + is_inverted * instr.mad.address_register_index); + src3 += "." + GetSelectorSrc3(swizzle); + + std::string dest_reg = + (instr.mad.dest.Value() < 0x10) + ? outputreg_getter(static_cast(instr.mad.dest.Value().GetIndex())) + : (instr.mad.dest.Value() < 0x20) + ? "reg_tmp" + std::to_string(instr.mad.dest.Value().GetIndex()) + : ""; + + if (sanitize_mul) { + SetDest(swizzle, dest_reg, "sanitize_mul(" + src1 + ", " + src2 + ") + " + src3, + 4, 4); + } else { + SetDest(swizzle, dest_reg, src1 + " * " + src2 + " + " + src3, 4, 4); + } + } else { + LOG_ERROR(HW_GPU, "Unhandled multiply-add instruction: 0x%02x (%s): 0x%08x", + (int)instr.opcode.Value().EffectiveOpCode(), + instr.opcode.Value().GetInfo().name, instr.hex); + } + break; + } + + default: { + switch (instr.opcode.Value()) { + case OpCode::Id::END: { + shader.AddLine("return true;"); + offset = PROGRAM_END - 1; + break; + } + + case OpCode::Id::JMPC: + case OpCode::Id::JMPU: { + std::string condition; + if (instr.opcode.Value() == OpCode::Id::JMPC) { + condition = EvaluateCondition(instr.flow_control); + } else { + bool invert_test = instr.flow_control.num_instructions & 1; + condition = (invert_test ? "!" : "") + + GetUniformBool(instr.flow_control.bool_uniform_id); + } + + shader.AddLine("if (" + condition + ") {"); + ++shader.scope; + shader.AddLine("{ jmp_to = " + std::to_string(instr.flow_control.dest_offset) + + "u; break; }"); + + --shader.scope; + shader.AddLine("}"); + break; + } + + case OpCode::Id::CALL: + case OpCode::Id::CALLC: + case OpCode::Id::CALLU: { + std::string condition; + if (instr.opcode.Value() == OpCode::Id::CALLC) { + condition = EvaluateCondition(instr.flow_control); + } else if (instr.opcode.Value() == OpCode::Id::CALLU) { + condition = GetUniformBool(instr.flow_control.bool_uniform_id); + } + + shader.AddLine(condition.empty() ? "{" : "if (" + condition + ") {"); + ++shader.scope; + + auto& call_sub = GetSubroutine(instr.flow_control.dest_offset, + instr.flow_control.dest_offset + + instr.flow_control.num_instructions); + + CallSubroutine(call_sub); + if (instr.opcode.Value() == OpCode::Id::CALL && + call_sub.exit_method == ExitMethod::AlwaysEnd) { + offset = PROGRAM_END - 1; + } + + --shader.scope; + shader.AddLine("}"); + break; + } + + case OpCode::Id::NOP: { + break; + } + + case OpCode::Id::IFC: + case OpCode::Id::IFU: { + std::string condition; + if (instr.opcode.Value() == OpCode::Id::IFC) { + condition = EvaluateCondition(instr.flow_control); + } else { + condition = GetUniformBool(instr.flow_control.bool_uniform_id); + } + + const u32 if_offset = offset + 1; + const u32 else_offset = instr.flow_control.dest_offset; + const u32 endif_offset = + instr.flow_control.dest_offset + instr.flow_control.num_instructions; + + shader.AddLine("if (" + condition + ") {"); + ++shader.scope; + + auto& if_sub = GetSubroutine(if_offset, else_offset); + CallSubroutine(if_sub); + offset = else_offset - 1; + + if (instr.flow_control.num_instructions != 0) { + --shader.scope; + shader.AddLine("} else {"); + ++shader.scope; + + auto& else_sub = GetSubroutine(else_offset, endif_offset); + CallSubroutine(else_sub); + offset = endif_offset - 1; + + if (if_sub.exit_method == ExitMethod::AlwaysEnd && + else_sub.exit_method == ExitMethod::AlwaysEnd) { + offset = PROGRAM_END - 1; + } + } + + --shader.scope; + shader.AddLine("}"); + break; + } + + case OpCode::Id::LOOP: { + std::string int_uniform = + "uniforms.i[" + std::to_string(instr.flow_control.int_uniform_id) + "]"; + + shader.AddLine("address_registers.z = int(" + int_uniform + ".y);"); + + std::string loop_var = "loop" + std::to_string(offset); + shader.AddLine("for (uint " + loop_var + " = 0u; " + loop_var + + " <= " + int_uniform + ".x; address_registers.z += int(" + + int_uniform + ".z), ++" + loop_var + ") {"); + ++shader.scope; + + auto& loop_sub = GetSubroutine(offset + 1, instr.flow_control.dest_offset + 1); + CallSubroutine(loop_sub); + offset = instr.flow_control.dest_offset; + + --shader.scope; + shader.AddLine("}"); + + if (loop_sub.exit_method == ExitMethod::AlwaysEnd) { + offset = PROGRAM_END - 1; + } + + break; + } + + case OpCode::Id::EMIT: { + if (is_gs) { + shader.AddLine("emit();"); + } + break; + } + + case OpCode::Id::SETEMIT: { + if (is_gs) { + ASSERT(instr.setemit.vertex_id < 3); + shader.AddLine("setemit(" + std::to_string(instr.setemit.vertex_id) + "u, " + + ((instr.setemit.prim_emit != 0) ? "true" : "false") + ", " + + ((instr.setemit.winding != 0) ? "true" : "false") + ");"); + } + break; + } + + default: { + LOG_ERROR(HW_GPU, "Unhandled instruction: 0x%02x (%s): 0x%08x", + (int)instr.opcode.Value().EffectiveOpCode(), + instr.opcode.Value().GetInfo().name, instr.hex); + break; + } + } + + break; + } + } + return offset + 1; + }; + + /** + * Compiles a range of instructions from PICA to GLSL. + * @param begin the offset of the starting instruction. + * @param end the offset where the compilation should stop (exclusive). + * @return the offset of the next instruction to compile. PROGRAM_END if the program terminates. + */ + u32 CompileRange(u32 begin, u32 end) { + u32 program_counter; + for (program_counter = begin; program_counter < (begin > end ? PROGRAM_END : end);) { + program_counter = CompileInstr(program_counter); + } + return program_counter; + }; + + void Generate() { + if (sanitize_mul) { + shader.AddLine("vec4 sanitize_mul(vec4 lhs, vec4 rhs) {"); + ++shader.scope; + shader.AddLine("vec4 product = lhs * rhs;"); + shader.AddLine("return mix(product, mix(mix(vec4(0.0), product, isnan(rhs)), product, " + "isnan(lhs)), isnan(product));"); + --shader.scope; + shader.AddLine("}\n"); + } + + // Add declarations for registers + shader.AddLine("bvec2 conditional_code = bvec2(false);"); + shader.AddLine("ivec3 address_registers = ivec3(0);"); + for (int i = 0; i < 16; ++i) { + shader.AddLine("vec4 reg_tmp" + std::to_string(i) + " = vec4(0.0, 0.0, 0.0, 1.0);"); + } + shader.AddLine(""); + + // Add declarations for all subroutines + for (const auto& subroutine : subroutines) { + shader.AddLine("bool " + subroutine.GetName() + "();"); + } + shader.AddLine(""); + + // Add the main entry point + shader.AddLine("bool exec_shader() {"); + ++shader.scope; + CallSubroutine(GetSubroutine(main_offset, PROGRAM_END)); + --shader.scope; + shader.AddLine("}\n"); + + // Add definitions for all subroutines + for (const auto& subroutine : subroutines) { + std::set labels = subroutine.labels; + + shader.AddLine("bool " + subroutine.GetName() + "() {"); + ++shader.scope; + + if (labels.empty()) { + if (CompileRange(subroutine.begin, subroutine.end) != PROGRAM_END) { + shader.AddLine("return false;"); + } + } else { + labels.insert(subroutine.begin); + shader.AddLine("uint jmp_to = " + std::to_string(subroutine.begin) + "u;"); + shader.AddLine("while (true) {"); + ++shader.scope; + + shader.AddLine("switch (jmp_to) {"); + + for (auto label : labels) { + shader.AddLine("case " + std::to_string(label) + "u: {"); + ++shader.scope; + + auto next_it = labels.lower_bound(label + 1); + u32 next_label = next_it == labels.end() ? subroutine.end : *next_it; + + u32 compile_end = CompileRange(label, next_label); + if (compile_end > next_label && compile_end != PROGRAM_END) { + // This happens only when there is a label inside a IF/LOOP block + shader.AddLine("{ jmp_to = " + std::to_string(compile_end) + "u; break; }"); + labels.emplace(compile_end); + } + + --shader.scope; + shader.AddLine("}"); + } + + shader.AddLine("default: return false;"); + shader.AddLine("}"); + + --shader.scope; + shader.AddLine("}"); + + shader.AddLine("return false;"); + } + + --shader.scope; + shader.AddLine("}\n"); + + ASSERT(shader.scope == 0); + } + } + +private: + const std::set& subroutines; + const ProgramCode& program_code; + const SwizzleData& swizzle_data; + const u32 main_offset; + const RegGetter& inputreg_getter; + const RegGetter& outputreg_getter; + const bool sanitize_mul; + const bool is_gs; + + ShaderWriter shader; +}; + +std::string GetCommonDeclarations() { + return R"( +struct pica_uniforms { + bool b[16]; + uvec4 i[4]; + vec4 f[96]; +}; + +bool exec_shader(); + +)"; +} + +std::string DecompileProgram(const ProgramCode& program_code, const SwizzleData& swizzle_data, + u32 main_offset, const RegGetter& inputreg_getter, + const RegGetter& outputreg_getter, bool sanitize_mul, bool is_gs) { + + auto subroutines = ControlFlowAnalyzer(program_code, main_offset).GetSubroutines(); + GLSLGenerator generator(subroutines, program_code, swizzle_data, main_offset, inputreg_getter, + outputreg_getter, sanitize_mul, is_gs); + return generator.GetShaderCode(); +} + +} // namespace Decompiler +} // namespace Shader +} // namespace Pica diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.h b/src/video_core/renderer_opengl/gl_shader_decompiler.h new file mode 100644 index 000000000..d1717147c --- /dev/null +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.h @@ -0,0 +1,27 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include "common/common_types.h" +#include "video_core/shader/shader.h" + +namespace Pica { +namespace Shader { +namespace Decompiler { + +using ProgramCode = std::array; +using SwizzleData = std::array; +using RegGetter = std::function; + +std::string GetCommonDeclarations(); + +std::string DecompileProgram(const ProgramCode& program_code, const SwizzleData& swizzle_data, + u32 main_offset, const RegGetter& inputreg_getter, + const RegGetter& outputreg_getter, bool sanitize_mul, bool is_gs); + +} // namespace Decompiler +} // namespace Shader +} // namespace Pica From 4991b15ee5e68b6e932ce0b2007483a7e9d43c05 Mon Sep 17 00:00:00 2001 From: wwylele Date: Sun, 25 Mar 2018 15:49:46 +0300 Subject: [PATCH 2/4] gl_shader_decompiler: some small fixes - remove unnecessary ";" - use std::tie for lexicographical ordering - simplify loop condition The offset always has step +1 on each iteration, so it would just hit one of the two boundary anyway --- .../renderer_opengl/gl_shader_decompiler.cpp | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index e7767faf6..ad4d56932 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -42,10 +42,7 @@ struct Subroutine { std::set labels; ///< Addresses refereced by JMP instructions. bool operator<(const Subroutine& rhs) const { - if (begin == rhs.begin) { - return end < rhs.end; - } - return begin < rhs.begin; + return std::tie(begin, end) < std::tie(rhs.begin, rhs.end); } }; @@ -122,8 +119,7 @@ private: if (!inserted) return exit_method; - u32 offset = begin; - for (u32 offset = begin; offset < (begin > end ? PROGRAM_END : end); ++offset) { + for (u32 offset = begin; offset != end && offset != PROGRAM_END; ++offset) { const Instruction instr = {program_code[offset]}; switch (instr.opcode.Value()) { case OpCode::Id::END: { @@ -294,7 +290,7 @@ private: UNREACHABLE(); return ""; } - }; + } /// Generates code representing a source register. std::string GetSourceRegister(const SourceRegister& source_reg, @@ -317,7 +313,7 @@ private: UNREACHABLE(); return ""; } - }; + } /// Generates code representing a destination register. std::string GetDestRegister(const DestRegister& dest_reg) const { @@ -332,7 +328,7 @@ private: UNREACHABLE(); return ""; } - }; + } /// Generates code representing a bool uniform std::string GetUniformBool(u32 index) const { @@ -341,7 +337,7 @@ private: return "((gl_PrimitiveIDIn == 0) || uniforms.b[15])"; } return "uniforms.b[" + std::to_string(index) + "]"; - }; + } /** * Adds code that calls a subroutine. @@ -356,7 +352,7 @@ private: } else { shader.AddLine(subroutine.GetName() + "();"); } - }; + } /** * Writes code that does an assignment operation. @@ -395,7 +391,7 @@ private: } shader.AddLine(dest + " = " + src + ";"); - }; + } /** * Compiles a single instruction from PICA to GLSL. @@ -769,7 +765,7 @@ private: } } return offset + 1; - }; + } /** * Compiles a range of instructions from PICA to GLSL. @@ -783,7 +779,7 @@ private: program_counter = CompileInstr(program_counter); } return program_counter; - }; + } void Generate() { if (sanitize_mul) { From 11c2f11872fa8db5f3a07705078aacf6662c0b63 Mon Sep 17 00:00:00 2001 From: wwylele Date: Sun, 25 Mar 2018 16:08:27 +0300 Subject: [PATCH 3/4] gl_shader_decompiler: return error on decompilation failure Internally these errors are handled by exceptions. Only fallbackable errors (that can be handled by CPU shader emulation) is reported. Completely ill-formed shader is still ASSERTed. Code logic related stuff is DEBUG_ASSERTed --- .../renderer_opengl/gl_shader_decompiler.cpp | 42 +++++++++++++------ .../renderer_opengl/gl_shader_decompiler.h | 9 ++-- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index ad4d56932..6b5a75497 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include #include #include @@ -21,6 +22,11 @@ using nihstro::SwizzlePattern; constexpr u32 PROGRAM_END = MAX_PROGRAM_CODE_LENGTH; +class DecompileFail : public std::runtime_error { +public: + using std::runtime_error::runtime_error; +}; + /// 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. @@ -54,7 +60,8 @@ public: // Recursively finds all subroutines. const Subroutine& program_main = AddSubroutine(main_offset, PROGRAM_END); - ASSERT(program_main.exit_method == ExitMethod::AlwaysEnd); + if (program_main.exit_method != ExitMethod::AlwaysEnd) + throw DecompileFail("Program does not always end"); } std::set GetSubroutines() { @@ -74,6 +81,8 @@ private: Subroutine subroutine{begin, end}; subroutine.exit_method = Scan(begin, end, subroutine.labels); + if (subroutine.exit_method == ExitMethod::Undetermined) + throw DecompileFail("Recursive function detected"); return *subroutines.insert(std::move(subroutine)).first; } @@ -187,7 +196,7 @@ private: class ShaderWriter { public: void AddLine(const std::string& text) { - ASSERT(scope >= 0); + DEBUG_ASSERT(scope >= 0); if (!text.empty()) { shader_source += std::string(static_cast(scope) * 4, ' '); } @@ -377,7 +386,7 @@ private: if (reg.empty() || dest_mask_num_components == 0) { return; } - ASSERT(value_num_components >= dest_num_components || value_num_components == 1); + DEBUG_ASSERT(value_num_components >= dest_num_components || value_num_components == 1); std::string dest = reg + (dest_num_components != 1 ? dest_mask_swizzle : ""); @@ -560,7 +569,7 @@ private: LOG_ERROR(HW_GPU, "Unhandled arithmetic instruction: 0x%02x (%s): 0x%08x", (int)instr.opcode.Value().EffectiveOpCode(), instr.opcode.Value().GetInfo().name, instr.hex); - DEBUG_ASSERT(false); + throw DecompileFail("Unhandled instruction"); break; } } @@ -604,6 +613,7 @@ private: LOG_ERROR(HW_GPU, "Unhandled multiply-add instruction: 0x%02x (%s): 0x%08x", (int)instr.opcode.Value().EffectiveOpCode(), instr.opcode.Value().GetInfo().name, instr.hex); + throw DecompileFail("Unhandled instruction"); } break; } @@ -757,6 +767,7 @@ private: LOG_ERROR(HW_GPU, "Unhandled instruction: 0x%02x (%s): 0x%08x", (int)instr.opcode.Value().EffectiveOpCode(), instr.opcode.Value().GetInfo().name, instr.hex); + throw DecompileFail("Unhandled instruction"); break; } } @@ -862,7 +873,7 @@ private: --shader.scope; shader.AddLine("}\n"); - ASSERT(shader.scope == 0); + DEBUG_ASSERT(shader.scope == 0); } } @@ -892,14 +903,21 @@ bool exec_shader(); )"; } -std::string DecompileProgram(const ProgramCode& program_code, const SwizzleData& swizzle_data, - u32 main_offset, const RegGetter& inputreg_getter, - const RegGetter& outputreg_getter, bool sanitize_mul, bool is_gs) { +boost::optional DecompileProgram(const ProgramCode& program_code, + const SwizzleData& swizzle_data, u32 main_offset, + const RegGetter& inputreg_getter, + const RegGetter& outputreg_getter, bool sanitize_mul, + bool is_gs) { - auto subroutines = ControlFlowAnalyzer(program_code, main_offset).GetSubroutines(); - GLSLGenerator generator(subroutines, program_code, swizzle_data, main_offset, inputreg_getter, - outputreg_getter, sanitize_mul, is_gs); - return generator.GetShaderCode(); + try { + auto subroutines = ControlFlowAnalyzer(program_code, main_offset).GetSubroutines(); + GLSLGenerator generator(subroutines, program_code, swizzle_data, main_offset, + inputreg_getter, outputreg_getter, sanitize_mul, is_gs); + return generator.GetShaderCode(); + } catch (const DecompileFail& exception) { + LOG_ERROR(HW_GPU, "Shader decompilation failed: %s", exception.what()); + return boost::none; + } } } // namespace Decompiler diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.h b/src/video_core/renderer_opengl/gl_shader_decompiler.h index d1717147c..91a71ce13 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.h +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "common/common_types.h" #include "video_core/shader/shader.h" @@ -18,9 +19,11 @@ using RegGetter = std::function; std::string GetCommonDeclarations(); -std::string DecompileProgram(const ProgramCode& program_code, const SwizzleData& swizzle_data, - u32 main_offset, const RegGetter& inputreg_getter, - const RegGetter& outputreg_getter, bool sanitize_mul, bool is_gs); +boost::optional DecompileProgram(const ProgramCode& program_code, + const SwizzleData& swizzle_data, u32 main_offset, + const RegGetter& inputreg_getter, + const RegGetter& outputreg_getter, bool sanitize_mul, + bool is_gs); } // namespace Decompiler } // namespace Shader From 9ffd4006851db6416cb62bef7c2528e9e456d79e Mon Sep 17 00:00:00 2001 From: wwylele Date: Tue, 27 Mar 2018 13:14:24 +0300 Subject: [PATCH 4/4] gl_shader_decompiler: add missing headers/rename GetXXX to MoveXXX to reflect that they move the data --- .../renderer_opengl/gl_shader_decompiler.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 6b5a75497..cac646b5d 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -3,8 +3,11 @@ // Refer to the license.txt file included. #include +#include #include #include +#include +#include #include #include "common/assert.h" #include "common/common_types.h" @@ -64,7 +67,7 @@ public: throw DecompileFail("Program does not always end"); } - std::set GetSubroutines() { + std::set MoveSubroutines() { return std::move(subroutines); } @@ -203,7 +206,7 @@ public: shader_source += text + '\n'; } - std::string GetResult() { + std::string MoveResult() { return std::move(shader_source); } @@ -256,8 +259,8 @@ public: Generate(); } - std::string GetShaderCode() { - return shader.GetResult(); + std::string MoveShaderCode() { + return shader.MoveResult(); } private: @@ -910,10 +913,10 @@ boost::optional DecompileProgram(const ProgramCode& program_code, bool is_gs) { try { - auto subroutines = ControlFlowAnalyzer(program_code, main_offset).GetSubroutines(); + auto subroutines = ControlFlowAnalyzer(program_code, main_offset).MoveSubroutines(); GLSLGenerator generator(subroutines, program_code, swizzle_data, main_offset, inputreg_getter, outputreg_getter, sanitize_mul, is_gs); - return generator.GetShaderCode(); + return generator.MoveShaderCode(); } catch (const DecompileFail& exception) { LOG_ERROR(HW_GPU, "Shader decompilation failed: %s", exception.what()); return boost::none;