mirror of
				https://git.zaroz.cloud/nintendo-back-up/yuzu/yuzu.git
				synced 2025-05-12 00:45:25 +00:00 
			
		
		
		
	gl_shader_decompiler: Add skeleton code from Citra for shader analysis.
This commit is contained in:
		
							parent
							
								
									4e7e0f8112
								
							
						
					
					
						commit
						ed7e597b44
					
				@ -2,57 +2,158 @@
 | 
				
			|||||||
// Licensed under GPLv2 or any later version
 | 
					// Licensed under GPLv2 or any later version
 | 
				
			||||||
// Refer to the license.txt file included.
 | 
					// Refer to the license.txt file included.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <map>
 | 
				
			||||||
 | 
					#include <set>
 | 
				
			||||||
#include <string>
 | 
					#include <string>
 | 
				
			||||||
#include <queue>
 | 
					 | 
				
			||||||
#include "common/assert.h"
 | 
					#include "common/assert.h"
 | 
				
			||||||
#include "common/common_types.h"
 | 
					#include "common/common_types.h"
 | 
				
			||||||
 | 
					#include "video_core/engines/shader_bytecode.h"
 | 
				
			||||||
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
 | 
					#include "video_core/renderer_opengl/gl_shader_decompiler.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Maxwell3D {
 | 
					namespace Tegra {
 | 
				
			||||||
namespace Shader {
 | 
					namespace Shader {
 | 
				
			||||||
namespace Decompiler {
 | 
					namespace Decompiler {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
constexpr u32 PROGRAM_END = MAX_PROGRAM_CODE_LENGTH;
 | 
					constexpr u32 PROGRAM_END = MAX_PROGRAM_CODE_LENGTH;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Impl {
 | 
					class DecompileFail : public std::runtime_error {
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
    Impl(const std::array<u32, MAX_PROGRAM_CODE_LENGTH>& program_code,
 | 
					    using std::runtime_error::runtime_error;
 | 
				
			||||||
         const std::array<u32, MAX_SWIZZLE_DATA_LENGTH>& swizzle_data, u32 main_offset,
 | 
					};
 | 
				
			||||||
         const std::function<std::string(u32)>& inputreg_getter,
 | 
					 | 
				
			||||||
         const std::function<std::string(u32)>& outputreg_getter, bool sanitize_mul,
 | 
					 | 
				
			||||||
         const std::string& emit_cb, const std::string& setemit_cb)
 | 
					 | 
				
			||||||
        : program_code(program_code), swizzle_data(swizzle_data), main_offset(main_offset),
 | 
					 | 
				
			||||||
          inputreg_getter(inputreg_getter), outputreg_getter(outputreg_getter),
 | 
					 | 
				
			||||||
          sanitize_mul(sanitize_mul), emit_cb(emit_cb), setemit_cb(setemit_cb) {}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    std::string Decompile() {
 | 
					/// Describes the behaviour of code path of a given entry point and a return point.
 | 
				
			||||||
        UNREACHABLE();
 | 
					enum class ExitMethod {
 | 
				
			||||||
        return {};
 | 
					    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<u32> labels;   ///< Addresses refereced by JMP instructions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool operator<(const Subroutine& rhs) const {
 | 
				
			||||||
 | 
					        return std::tie(begin, end) < std::tie(rhs.begin, rhs.end);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// 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);
 | 
				
			||||||
 | 
					        if (program_main.exit_method != ExitMethod::AlwaysEnd)
 | 
				
			||||||
 | 
					            throw DecompileFail("Program does not always end");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std::set<Subroutine> GetSubroutines() {
 | 
				
			||||||
 | 
					        return std::move(subroutines);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
    const std::array<u32, MAX_PROGRAM_CODE_LENGTH>& program_code;
 | 
					    const ProgramCode& program_code;
 | 
				
			||||||
    const std::array<u32, MAX_SWIZZLE_DATA_LENGTH>& swizzle_data;
 | 
					    std::set<Subroutine> subroutines;
 | 
				
			||||||
    u32 main_offset;
 | 
					    std::map<std::pair<u32, u32>, ExitMethod> exit_method_map;
 | 
				
			||||||
    const std::function<std::string(u32)>& inputreg_getter;
 | 
					
 | 
				
			||||||
    const std::function<std::string(u32)>& outputreg_getter;
 | 
					    /// Adds and analyzes a new subroutine if it is not added yet.
 | 
				
			||||||
    bool sanitize_mul;
 | 
					    const Subroutine& AddSubroutine(u32 begin, u32 end) {
 | 
				
			||||||
    const std::string& emit_cb;
 | 
					        auto iter = subroutines.find(Subroutine{begin, end});
 | 
				
			||||||
    const std::string& setemit_cb;
 | 
					        if (iter != subroutines.end())
 | 
				
			||||||
 | 
					            return *iter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        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;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Scans a range of code for labels and determines the exit method.
 | 
				
			||||||
 | 
					    ExitMethod Scan(u32 begin, u32 end, std::set<u32>& 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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (u32 offset = begin; offset != end && offset != PROGRAM_END; ++offset) {
 | 
				
			||||||
 | 
					            const Instruction instr = {program_code[offset]};
 | 
				
			||||||
 | 
					            switch (instr.opcode.Value().EffectiveOpCode()) {
 | 
				
			||||||
 | 
					            case OpCode::Id::EXIT: {
 | 
				
			||||||
 | 
					                return exit_method = ExitMethod::AlwaysEnd;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return exit_method = ExitMethod::AlwaysReturn;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
std::string DecompileProgram(const std::array<u32, MAX_PROGRAM_CODE_LENGTH>& program_code,
 | 
					class ShaderWriter {
 | 
				
			||||||
                             const std::array<u32, MAX_SWIZZLE_DATA_LENGTH>& swizzle_data,
 | 
					public:
 | 
				
			||||||
                             u32 main_offset,
 | 
					    void AddLine(const std::string& text) {
 | 
				
			||||||
                             const std::function<std::string(u32)>& inputreg_getter,
 | 
					        DEBUG_ASSERT(scope >= 0);
 | 
				
			||||||
                             const std::function<std::string(u32)>& outputreg_getter,
 | 
					        if (!text.empty()) {
 | 
				
			||||||
                             bool sanitize_mul, const std::string& emit_cb,
 | 
					            shader_source += std::string(static_cast<size_t>(scope) * 4, ' ');
 | 
				
			||||||
                             const std::string& setemit_cb) {
 | 
					        }
 | 
				
			||||||
    Impl impl(program_code, swizzle_data, main_offset, inputreg_getter, outputreg_getter,
 | 
					        shader_source += text + '\n';
 | 
				
			||||||
              sanitize_mul, emit_cb, setemit_cb);
 | 
					    }
 | 
				
			||||||
    return impl.Decompile();
 | 
					
 | 
				
			||||||
 | 
					    std::string GetResult() {
 | 
				
			||||||
 | 
					        return std::move(shader_source);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    int scope = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
					    std::string shader_source;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class GLSLGenerator {
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					    GLSLGenerator(const std::set<Subroutine>& subroutines, const ProgramCode& program_code,
 | 
				
			||||||
 | 
					                  u32 main_offset)
 | 
				
			||||||
 | 
					        : subroutines(subroutines), program_code(program_code), main_offset(main_offset) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Generate();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std::string GetShaderCode() {
 | 
				
			||||||
 | 
					        return shader.GetResult();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
					    const std::set<Subroutine>& subroutines;
 | 
				
			||||||
 | 
					    const ProgramCode& program_code;
 | 
				
			||||||
 | 
					    const u32 main_offset;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ShaderWriter shader;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void Generate() {}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					boost::optional<std::string> DecompileProgram(const ProgramCode& program_code, u32 main_offset) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        auto subroutines = ControlFlowAnalyzer(program_code, main_offset).GetSubroutines();
 | 
				
			||||||
 | 
					        GLSLGenerator generator(subroutines, program_code, main_offset);
 | 
				
			||||||
 | 
					        return generator.GetShaderCode();
 | 
				
			||||||
 | 
					    } catch (const DecompileFail& exception) {
 | 
				
			||||||
 | 
					        LOG_ERROR(HW_GPU, "Shader decompilation failed: %s", exception.what());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return boost::none;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
} // namespace Decompiler
 | 
					} // namespace Decompiler
 | 
				
			||||||
} // namespace Shader
 | 
					} // namespace Shader
 | 
				
			||||||
} // namespace Maxwell3D
 | 
					} // namespace Tegra
 | 
				
			||||||
 | 
				
			|||||||
@ -5,23 +5,20 @@
 | 
				
			|||||||
#include <array>
 | 
					#include <array>
 | 
				
			||||||
#include <functional>
 | 
					#include <functional>
 | 
				
			||||||
#include <string>
 | 
					#include <string>
 | 
				
			||||||
 | 
					#include <boost/optional.hpp>
 | 
				
			||||||
#include "common/common_types.h"
 | 
					#include "common/common_types.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Maxwell3D {
 | 
					namespace Tegra {
 | 
				
			||||||
namespace Shader {
 | 
					namespace Shader {
 | 
				
			||||||
namespace Decompiler {
 | 
					namespace Decompiler {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
constexpr size_t MAX_PROGRAM_CODE_LENGTH{0x100000};
 | 
					constexpr size_t MAX_PROGRAM_CODE_LENGTH{0x100};
 | 
				
			||||||
constexpr size_t MAX_SWIZZLE_DATA_LENGTH{0x100000};
 | 
					constexpr size_t MAX_SWIZZLE_DATA_LENGTH{0x100};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
std::string DecompileProgram(const std::array<u32, MAX_PROGRAM_CODE_LENGTH>& program_code,
 | 
					using ProgramCode = std::array<u64, MAX_PROGRAM_CODE_LENGTH>;
 | 
				
			||||||
                             const std::array<u32, MAX_SWIZZLE_DATA_LENGTH>& swizzle_data,
 | 
					
 | 
				
			||||||
                             u32 main_offset,
 | 
					boost::optional<std::string> DecompileProgram(const ProgramCode& program_code, u32 main_offset);
 | 
				
			||||||
                             const std::function<std::string(u32)>& inputreg_getter,
 | 
					 | 
				
			||||||
                             const std::function<std::string(u32)>& outputreg_getter,
 | 
					 | 
				
			||||||
                             bool sanitize_mul, const std::string& emit_cb = "",
 | 
					 | 
				
			||||||
                             const std::string& setemit_cb = "");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
} // namespace Decompiler
 | 
					} // namespace Decompiler
 | 
				
			||||||
} // namespace Shader
 | 
					} // namespace Shader
 | 
				
			||||||
} // namespace Maxwell3D
 | 
					} // namespace Tegra
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user