diff options
| author | 2018-04-04 21:44:35 -0400 | |
|---|---|---|
| committer | 2018-04-13 23:48:20 -0400 | |
| commit | ed7e597b4494f770f4907560af0aa778d7762226 (patch) | |
| tree | e52e78746d267dc85545338de95789d3a54150a9 /src | |
| parent | shader_bytecode: Add initial module for shader decoding. (diff) | |
| download | yuzu-ed7e597b4494f770f4907560af0aa778d7762226.tar.gz yuzu-ed7e597b4494f770f4907560af0aa778d7762226.tar.xz yuzu-ed7e597b4494f770f4907560af0aa778d7762226.zip | |
gl_shader_decompiler: Add skeleton code from Citra for shader analysis.
Diffstat (limited to 'src')
| -rw-r--r-- | src/video_core/renderer_opengl/gl_shader_decompiler.cpp | 167 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/gl_shader_decompiler.h | 19 |
2 files changed, 142 insertions, 44 deletions
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 564ea8f9e..3fc420649 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp | |||
| @@ -2,57 +2,158 @@ | |||
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2 or any later version |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <map> | ||
| 6 | #include <set> | ||
| 5 | #include <string> | 7 | #include <string> |
| 6 | #include <queue> | ||
| 7 | #include "common/assert.h" | 8 | #include "common/assert.h" |
| 8 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | #include "video_core/engines/shader_bytecode.h" | ||
| 9 | #include "video_core/renderer_opengl/gl_shader_decompiler.h" | 11 | #include "video_core/renderer_opengl/gl_shader_decompiler.h" |
| 10 | 12 | ||
| 11 | namespace Maxwell3D { | 13 | namespace Tegra { |
| 12 | namespace Shader { | 14 | namespace Shader { |
| 13 | namespace Decompiler { | 15 | namespace Decompiler { |
| 14 | 16 | ||
| 15 | constexpr u32 PROGRAM_END = MAX_PROGRAM_CODE_LENGTH; | 17 | constexpr u32 PROGRAM_END = MAX_PROGRAM_CODE_LENGTH; |
| 16 | 18 | ||
| 17 | class Impl { | 19 | class DecompileFail : public std::runtime_error { |
| 18 | public: | 20 | public: |
| 19 | Impl(const std::array<u32, MAX_PROGRAM_CODE_LENGTH>& program_code, | 21 | using std::runtime_error::runtime_error; |
| 20 | const std::array<u32, MAX_SWIZZLE_DATA_LENGTH>& swizzle_data, u32 main_offset, | 22 | }; |
| 21 | const std::function<std::string(u32)>& inputreg_getter, | 23 | |
| 22 | const std::function<std::string(u32)>& outputreg_getter, bool sanitize_mul, | 24 | /// Describes the behaviour of code path of a given entry point and a return point. |
| 23 | const std::string& emit_cb, const std::string& setemit_cb) | 25 | enum class ExitMethod { |
| 24 | : program_code(program_code), swizzle_data(swizzle_data), main_offset(main_offset), | 26 | Undetermined, ///< Internal value. Only occur when analyzing JMP loop. |
| 25 | inputreg_getter(inputreg_getter), outputreg_getter(outputreg_getter), | 27 | AlwaysReturn, ///< All code paths reach the return point. |
| 26 | sanitize_mul(sanitize_mul), emit_cb(emit_cb), setemit_cb(setemit_cb) {} | 28 | Conditional, ///< Code path reaches the return point or an END instruction conditionally. |
| 29 | AlwaysEnd, ///< All code paths reach a END instruction. | ||
| 30 | }; | ||
| 31 | |||
| 32 | /// A subroutine is a range of code refereced by a CALL, IF or LOOP instruction. | ||
| 33 | struct Subroutine { | ||
| 34 | /// Generates a name suitable for GLSL source code. | ||
| 35 | std::string GetName() const { | ||
| 36 | return "sub_" + std::to_string(begin) + "_" + std::to_string(end); | ||
| 37 | } | ||
| 38 | |||
| 39 | u32 begin; ///< Entry point of the subroutine. | ||
| 40 | u32 end; ///< Return point of the subroutine. | ||
| 41 | ExitMethod exit_method; ///< Exit method of the subroutine. | ||
| 42 | std::set<u32> labels; ///< Addresses refereced by JMP instructions. | ||
| 43 | |||
| 44 | bool operator<(const Subroutine& rhs) const { | ||
| 45 | return std::tie(begin, end) < std::tie(rhs.begin, rhs.end); | ||
| 46 | } | ||
| 47 | }; | ||
| 48 | |||
| 49 | /// Analyzes shader code and produces a set of subroutines. | ||
| 50 | class ControlFlowAnalyzer { | ||
| 51 | public: | ||
| 52 | ControlFlowAnalyzer(const ProgramCode& program_code, u32 main_offset) | ||
| 53 | : program_code(program_code) { | ||
| 54 | |||
| 55 | // Recursively finds all subroutines. | ||
| 56 | const Subroutine& program_main = AddSubroutine(main_offset, PROGRAM_END); | ||
| 57 | if (program_main.exit_method != ExitMethod::AlwaysEnd) | ||
| 58 | throw DecompileFail("Program does not always end"); | ||
| 59 | } | ||
| 60 | |||
| 61 | std::set<Subroutine> GetSubroutines() { | ||
| 62 | return std::move(subroutines); | ||
| 63 | } | ||
| 64 | |||
| 65 | private: | ||
| 66 | const ProgramCode& program_code; | ||
| 67 | std::set<Subroutine> subroutines; | ||
| 68 | std::map<std::pair<u32, u32>, ExitMethod> exit_method_map; | ||
| 69 | |||
| 70 | /// Adds and analyzes a new subroutine if it is not added yet. | ||
| 71 | const Subroutine& AddSubroutine(u32 begin, u32 end) { | ||
| 72 | auto iter = subroutines.find(Subroutine{begin, end}); | ||
| 73 | if (iter != subroutines.end()) | ||
| 74 | return *iter; | ||
| 75 | |||
| 76 | Subroutine subroutine{begin, end}; | ||
| 77 | subroutine.exit_method = Scan(begin, end, subroutine.labels); | ||
| 78 | if (subroutine.exit_method == ExitMethod::Undetermined) | ||
| 79 | throw DecompileFail("Recursive function detected"); | ||
| 80 | return *subroutines.insert(std::move(subroutine)).first; | ||
| 81 | } | ||
| 27 | 82 | ||
| 28 | std::string Decompile() { | 83 | /// Scans a range of code for labels and determines the exit method. |
| 29 | UNREACHABLE(); | 84 | ExitMethod Scan(u32 begin, u32 end, std::set<u32>& labels) { |
| 30 | return {}; | 85 | auto [iter, inserted] = |
| 86 | exit_method_map.emplace(std::make_pair(begin, end), ExitMethod::Undetermined); | ||
| 87 | ExitMethod& exit_method = iter->second; | ||
| 88 | if (!inserted) | ||
| 89 | return exit_method; | ||
| 90 | |||
| 91 | for (u32 offset = begin; offset != end && offset != PROGRAM_END; ++offset) { | ||
| 92 | const Instruction instr = {program_code[offset]}; | ||
| 93 | switch (instr.opcode.Value().EffectiveOpCode()) { | ||
| 94 | case OpCode::Id::EXIT: { | ||
| 95 | return exit_method = ExitMethod::AlwaysEnd; | ||
| 96 | } | ||
| 97 | } | ||
| 98 | } | ||
| 99 | return exit_method = ExitMethod::AlwaysReturn; | ||
| 100 | } | ||
| 101 | }; | ||
| 102 | |||
| 103 | class ShaderWriter { | ||
| 104 | public: | ||
| 105 | void AddLine(const std::string& text) { | ||
| 106 | DEBUG_ASSERT(scope >= 0); | ||
| 107 | if (!text.empty()) { | ||
| 108 | shader_source += std::string(static_cast<size_t>(scope) * 4, ' '); | ||
| 109 | } | ||
| 110 | shader_source += text + '\n'; | ||
| 111 | } | ||
| 112 | |||
| 113 | std::string GetResult() { | ||
| 114 | return std::move(shader_source); | ||
| 115 | } | ||
| 116 | |||
| 117 | int scope = 0; | ||
| 118 | |||
| 119 | private: | ||
| 120 | std::string shader_source; | ||
| 121 | }; | ||
| 122 | |||
| 123 | class GLSLGenerator { | ||
| 124 | public: | ||
| 125 | GLSLGenerator(const std::set<Subroutine>& subroutines, const ProgramCode& program_code, | ||
| 126 | u32 main_offset) | ||
| 127 | : subroutines(subroutines), program_code(program_code), main_offset(main_offset) { | ||
| 128 | |||
| 129 | Generate(); | ||
| 130 | } | ||
| 131 | |||
| 132 | std::string GetShaderCode() { | ||
| 133 | return shader.GetResult(); | ||
| 31 | } | 134 | } |
| 32 | 135 | ||
| 33 | private: | 136 | private: |
| 34 | const std::array<u32, MAX_PROGRAM_CODE_LENGTH>& program_code; | 137 | const std::set<Subroutine>& subroutines; |
| 35 | const std::array<u32, MAX_SWIZZLE_DATA_LENGTH>& swizzle_data; | 138 | const ProgramCode& program_code; |
| 36 | u32 main_offset; | 139 | const u32 main_offset; |
| 37 | const std::function<std::string(u32)>& inputreg_getter; | 140 | |
| 38 | const std::function<std::string(u32)>& outputreg_getter; | 141 | ShaderWriter shader; |
| 39 | bool sanitize_mul; | 142 | |
| 40 | const std::string& emit_cb; | 143 | void Generate() {} |
| 41 | const std::string& setemit_cb; | ||
| 42 | }; | 144 | }; |
| 43 | 145 | ||
| 44 | std::string DecompileProgram(const std::array<u32, MAX_PROGRAM_CODE_LENGTH>& program_code, | 146 | boost::optional<std::string> DecompileProgram(const ProgramCode& program_code, u32 main_offset) { |
| 45 | const std::array<u32, MAX_SWIZZLE_DATA_LENGTH>& swizzle_data, | 147 | try { |
| 46 | u32 main_offset, | 148 | auto subroutines = ControlFlowAnalyzer(program_code, main_offset).GetSubroutines(); |
| 47 | const std::function<std::string(u32)>& inputreg_getter, | 149 | GLSLGenerator generator(subroutines, program_code, main_offset); |
| 48 | const std::function<std::string(u32)>& outputreg_getter, | 150 | return generator.GetShaderCode(); |
| 49 | bool sanitize_mul, const std::string& emit_cb, | 151 | } catch (const DecompileFail& exception) { |
| 50 | const std::string& setemit_cb) { | 152 | LOG_ERROR(HW_GPU, "Shader decompilation failed: %s", exception.what()); |
| 51 | Impl impl(program_code, swizzle_data, main_offset, inputreg_getter, outputreg_getter, | 153 | } |
| 52 | sanitize_mul, emit_cb, setemit_cb); | 154 | return boost::none; |
| 53 | return impl.Decompile(); | ||
| 54 | } | 155 | } |
| 55 | 156 | ||
| 56 | } // namespace Decompiler | 157 | } // namespace Decompiler |
| 57 | } // namespace Shader | 158 | } // namespace Shader |
| 58 | } // namespace Maxwell3D | 159 | } // namespace Tegra |
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.h b/src/video_core/renderer_opengl/gl_shader_decompiler.h index 02ebfcbe8..628f02c93 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.h +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.h | |||
| @@ -5,23 +5,20 @@ | |||
| 5 | #include <array> | 5 | #include <array> |
| 6 | #include <functional> | 6 | #include <functional> |
| 7 | #include <string> | 7 | #include <string> |
| 8 | #include <boost/optional.hpp> | ||
| 8 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 9 | 10 | ||
| 10 | namespace Maxwell3D { | 11 | namespace Tegra { |
| 11 | namespace Shader { | 12 | namespace Shader { |
| 12 | namespace Decompiler { | 13 | namespace Decompiler { |
| 13 | 14 | ||
| 14 | constexpr size_t MAX_PROGRAM_CODE_LENGTH{0x100000}; | 15 | constexpr size_t MAX_PROGRAM_CODE_LENGTH{0x100}; |
| 15 | constexpr size_t MAX_SWIZZLE_DATA_LENGTH{0x100000}; | 16 | constexpr size_t MAX_SWIZZLE_DATA_LENGTH{0x100}; |
| 16 | 17 | ||
| 17 | std::string DecompileProgram(const std::array<u32, MAX_PROGRAM_CODE_LENGTH>& program_code, | 18 | using ProgramCode = std::array<u64, MAX_PROGRAM_CODE_LENGTH>; |
| 18 | const std::array<u32, MAX_SWIZZLE_DATA_LENGTH>& swizzle_data, | 19 | |
| 19 | u32 main_offset, | 20 | boost::optional<std::string> DecompileProgram(const ProgramCode& program_code, u32 main_offset); |
| 20 | const std::function<std::string(u32)>& inputreg_getter, | ||
| 21 | const std::function<std::string(u32)>& outputreg_getter, | ||
| 22 | bool sanitize_mul, const std::string& emit_cb = "", | ||
| 23 | const std::string& setemit_cb = ""); | ||
| 24 | 21 | ||
| 25 | } // namespace Decompiler | 22 | } // namespace Decompiler |
| 26 | } // namespace Shader | 23 | } // namespace Shader |
| 27 | } // namespace Maxwell3D | 24 | } // namespace Tegra |