Merge pull request #557 from wheremyfoodat/shader-decomp

More shader->GLSL recompiler work
This commit is contained in:
wheremyfoodat 2024-07-23 23:38:16 +00:00 committed by GitHub
commit 2dc5c706b5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 176 additions and 6 deletions

View file

@ -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 // 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 { class PICAShader {
using f24 = Floats::f24; 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 // Add these as friend classes for the JIT so it has access to all important state
friend class ShaderJIT; friend class ShaderJIT;
friend class ShaderEmitter; friend class ShaderEmitter;
friend class PICA::ShaderGen::ShaderDecompiler;
vec4f getSource(u32 source); vec4f getSource(u32 source);
vec4f& getDest(u32 dest); vec4f& getDest(u32 dest);

View file

@ -101,6 +101,16 @@ namespace PICA::ShaderGen {
void writeAttributes(); 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: public:
ShaderDecompiler(PICAShader& shader, EmulatorConfig& config, u32 entrypoint, API api, Language language) ShaderDecompiler(PICAShader& shader, EmulatorConfig& config, u32 entrypoint, API api, Language language)
: shader(shader), entrypoint(entrypoint), config(config), api(api), language(language), decompiledShader("") {} : shader(shader), entrypoint(entrypoint), config(config), api(api), language(language), decompiledShader("") {}

View file

@ -4,6 +4,8 @@
using namespace PICA; using namespace PICA;
using namespace PICA::ShaderGen; using namespace PICA::ShaderGen;
using namespace Helpers;
using Function = ControlFlow::Function; using Function = ControlFlow::Function;
using ExitMode = Function::ExitMode; using ExitMode = Function::ExitMode;
@ -70,11 +72,16 @@ const Function* ShaderDecompiler::findFunction(const AddressRange& range) {
void ShaderDecompiler::writeAttributes() { void ShaderDecompiler::writeAttributes() {
decompiledShader += R"( decompiledShader += R"(
layout(location = 0) in vec4 inputs[8];
layout(std140) uniform PICAShaderUniforms { layout(std140) uniform PICAShaderUniforms {
vec4 uniform_float[96]; vec4 uniform_float[96];
uvec4 uniform_int; uvec4 uniform_int;
uint uniform_bool; uint uniform_bool;
}; };
vec4 temp_registers[16];
vec4 dummy_vec = vec4(0.0);
)"; )";
decompiledShader += "\n"; decompiledShader += "\n";
@ -130,20 +137,168 @@ std::string ShaderDecompiler::decompile() {
return decompiledShader; 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<char, 4> 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) { void ShaderDecompiler::compileInstruction(u32& pc, bool& finished) {
const u32 instruction = shader.loadedShader[pc]; const u32 instruction = shader.loadedShader[pc];
const u32 opcode = instruction >> 26; const u32 opcode = instruction >> 26;
switch (opcode) { if (usesCommonEncoding(instruction)) {
case ShaderOpcodes::DP4: decompiledShader += "dp4\n"; break; const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f];
case ShaderOpcodes::MOV: decompiledShader += "mov\n"; break; const bool invertSources = (opcode == ShaderOpcodes::SLTI || opcode == ShaderOpcodes::SGEI || opcode == ShaderOpcodes::DPHI);
case ShaderOpcodes::END: finished = true; return;
default: Helpers::warn("GLSL recompiler: Unknown opcode: %X", opcode); break; // 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++; 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"; } void ShaderDecompiler::callFunction(const Function& function) { decompiledShader += function.getCallStatement() + ";\n"; }
std::string ShaderGen::decompileShader(PICAShader& shader, EmulatorConfig& config, u32 entrypoint, API api, Language language) { std::string ShaderGen::decompileShader(PICAShader& shader, EmulatorConfig& config, u32 entrypoint, API api, Language language) {