diff --git a/include/PICA/shader.hpp b/include/PICA/shader.hpp index 44ca2a15..68b16de8 100644 --- a/include/PICA/shader.hpp +++ b/include/PICA/shader.hpp @@ -58,6 +58,10 @@ namespace ShaderOpcodes { }; } +namespace PICA::ShaderGen { + class ShaderDecompiler; +}; + // Note: All PICA f24 vec4 registers must have the alignas(16) specifier to make them easier to access in SSE/NEON code in the JIT class PICAShader { using f24 = Floats::f24; @@ -135,6 +139,7 @@ class PICAShader { // Add these as friend classes for the JIT so it has access to all important state friend class ShaderJIT; friend class ShaderEmitter; + friend class PICA::ShaderGen::ShaderDecompiler; vec4f getSource(u32 source); vec4f& getDest(u32 dest); diff --git a/include/PICA/shader_decompiler.hpp b/include/PICA/shader_decompiler.hpp index cbc569ae..1253226f 100644 --- a/include/PICA/shader_decompiler.hpp +++ b/include/PICA/shader_decompiler.hpp @@ -101,6 +101,16 @@ namespace PICA::ShaderGen { void writeAttributes(); + std::string getSource(u32 source, u32 index) const; + std::string getDest(u32 dest) const; + std::string getSwizzlePattern(u32 swizzle) const; + std::string getDestSwizzle(u32 destinationMask) const; + + void setDest(u32 operandDescriptor, const std::string& dest, const std::string& value); + // Returns if the instruction uses the typical register encodings most instructions use + // With some exceptions like MAD/MADI, and the control flow instructions which are completely different + bool usesCommonEncoding(u32 instruction) const; + public: ShaderDecompiler(PICAShader& shader, EmulatorConfig& config, u32 entrypoint, API api, Language language) : shader(shader), entrypoint(entrypoint), config(config), api(api), language(language), decompiledShader("") {} diff --git a/src/core/PICA/shader_decompiler.cpp b/src/core/PICA/shader_decompiler.cpp index 91b07574..bdbef8f3 100644 --- a/src/core/PICA/shader_decompiler.cpp +++ b/src/core/PICA/shader_decompiler.cpp @@ -4,6 +4,8 @@ using namespace PICA; using namespace PICA::ShaderGen; +using namespace Helpers; + using Function = ControlFlow::Function; using ExitMode = Function::ExitMode; @@ -70,11 +72,16 @@ const Function* ShaderDecompiler::findFunction(const AddressRange& range) { void ShaderDecompiler::writeAttributes() { decompiledShader += R"( + layout(location = 0) in vec4 inputs[8]; + layout(std140) uniform PICAShaderUniforms { vec4 uniform_float[96]; uvec4 uniform_int; uint uniform_bool; }; + + vec4 temp_registers[16]; + vec4 dummy_vec = vec4(0.0); )"; decompiledShader += "\n"; @@ -130,24 +137,172 @@ std::string ShaderDecompiler::decompile() { return decompiledShader; } +std::string ShaderDecompiler::getSource(u32 source, [[maybe_unused]] u32 index) const { + if (source < 0x10) { + return "inputs[" + std::to_string(source) + "]"; + } else if (source < 0x20) { + return "temp_registers[" + std::to_string(source - 0x10) + "]"; + } else { + const usize floatIndex = (source - 0x20) & 0x7f; + + if (floatIndex >= 96) [[unlikely]] { + return "dummy_vec"; + } + return "uniform_float[" + std::to_string(floatIndex) + "]"; + } +} + +std::string ShaderDecompiler::getDest(u32 dest) const { + if (dest < 0x10) { + return "output_registers[" + std::to_string(dest) + "]"; + } else if (dest < 0x20) { + return "temp_registers[" + std::to_string(dest - 0x10) + "]"; + } else { + return "dummy_vec"; + } +} + +std::string ShaderDecompiler::getSwizzlePattern(u32 swizzle) const { + static constexpr std::array names = {'x', 'y', 'z', 'w'}; + std::string ret(". "); + + for (int i = 0; i < 4; i++) { + ret[3 - i + 1] = names[swizzle & 0x3]; + swizzle >>= 2; + } + + return ret; +} + +std::string ShaderDecompiler::getDestSwizzle(u32 destinationMask) const { + std::string ret = "."; + + if (destinationMask & 0b1000) { + ret += "x"; + } + + if (destinationMask & 0b100) { + ret += "y"; + } + + if (destinationMask & 0b10) { + ret += "z"; + } + + if (destinationMask & 0b1) { + ret += "w"; + } + + return ret; +} + +void ShaderDecompiler::setDest(u32 operandDescriptor, const std::string& dest, const std::string& value) { + u32 destinationMask = operandDescriptor & 0xF; + + std::string destSwizzle = getDestSwizzle(destinationMask); + // We subtract 1 for the "." character of the swizzle descriptor + u32 writtenLaneCount = destSwizzle.size() - 1; + + // All lanes are masked out, so the operation is a nop. + if (writtenLaneCount == 0) { + return; + } + + decompiledShader += dest + destSwizzle + " = "; + if (writtenLaneCount == 1) { + decompiledShader += "float(" + value + ");\n"; + } else { + decompiledShader += "vec" + std::to_string(writtenLaneCount) + "(" + value + ");\n"; + } +} + void ShaderDecompiler::compileInstruction(u32& pc, bool& finished) { const u32 instruction = shader.loadedShader[pc]; const u32 opcode = instruction >> 26; - switch (opcode) { - case ShaderOpcodes::DP4: decompiledShader += "dp4\n"; break; - case ShaderOpcodes::MOV: decompiledShader += "mov\n"; break; - case ShaderOpcodes::END: finished = true; return; - default: Helpers::warn("GLSL recompiler: Unknown opcode: %X", opcode); break; + if (usesCommonEncoding(instruction)) { + const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; + const bool invertSources = (opcode == ShaderOpcodes::SLTI || opcode == ShaderOpcodes::SGEI || opcode == ShaderOpcodes::DPHI); + + // src1 and src2 indexes depend on whether this is one of the inverting instructions or not + const u32 src1Index = invertSources ? getBits<14, 5>(instruction) : getBits<12, 7>(instruction); + const u32 src2Index = invertSources ? getBits<7, 7>(instruction) : getBits<7, 5>(instruction); + + const u32 idx = getBits<19, 2>(instruction); + const u32 destIndex = getBits<21, 5>(instruction); + + const bool negate1 = (getBit<4>(operandDescriptor)) != 0; + const u32 swizzle1 = getBits<5, 8>(operandDescriptor); + const bool negate2 = (getBit<13>(operandDescriptor)) != 0; + const u32 swizzle2 = getBits<14, 8>(operandDescriptor); + + std::string src1 = negate1 ? "-" : ""; + src1 += getSource(src1Index, invertSources ? 0 : idx); + src1 += getSwizzlePattern(swizzle1); + + std::string src2 = negate2 ? "-" : ""; + src2 += getSource(src2Index, invertSources ? idx : 0); + src2 += getSwizzlePattern(swizzle2); + + std::string dest = getDest(destIndex); + + if (idx != 0) { + Helpers::panic("GLSL recompiler: Indexed instruction"); + } + + if (invertSources) { + Helpers::panic("GLSL recompiler: Inverted instruction"); + } + + switch (opcode) { + case ShaderOpcodes::DP4: setDest(operandDescriptor, dest, "vec4(dot(" + src1 + ", " + src2 + "))"); break; + case ShaderOpcodes::MOV: setDest(operandDescriptor, dest, src1); break; + default: Helpers::panic("GLSL recompiler: Unknown common opcode: %X", opcode); break; + } + } else { + switch (opcode) { + case ShaderOpcodes::END: finished = true; return; + default: Helpers::panic("GLSL recompiler: Unknown opcode: %X", opcode); break; + } } pc++; } + +bool ShaderDecompiler::usesCommonEncoding(u32 instruction) const { + const u32 opcode = instruction >> 26; + switch (opcode) { + case ShaderOpcodes::ADD: + case ShaderOpcodes::CMP1: + case ShaderOpcodes::CMP2: + case ShaderOpcodes::MUL: + case ShaderOpcodes::MIN: + case ShaderOpcodes::MAX: + case ShaderOpcodes::FLR: + case ShaderOpcodes::DP3: + case ShaderOpcodes::DP4: + case ShaderOpcodes::DPH: + case ShaderOpcodes::DPHI: + case ShaderOpcodes::LG2: + case ShaderOpcodes::EX2: + case ShaderOpcodes::RCP: + case ShaderOpcodes::RSQ: + case ShaderOpcodes::MOV: + case ShaderOpcodes::MOVA: + case ShaderOpcodes::SLT: + case ShaderOpcodes::SLTI: + case ShaderOpcodes::SGE: + case ShaderOpcodes::SGEI: return true; + + default: return false; + } +} + void ShaderDecompiler::callFunction(const Function& function) { decompiledShader += function.getCallStatement() + ";\n"; } std::string ShaderGen::decompileShader(PICAShader& shader, EmulatorConfig& config, u32 entrypoint, API api, Language language) { ShaderDecompiler decompiler(shader, config, entrypoint, api, language); return decompiler.decompile(); -} \ No newline at end of file +}