rsx/fp: Reimplement GLSL code generation from CFG

This commit is contained in:
kd-11
2025-12-07 18:16:27 +03:00
committed by kd-11
parent d23ea4760b
commit f2913e4692
@@ -1319,71 +1319,82 @@ std::string FragmentProgramDecompiler::Decompile()
m_is_valid_ucode = true; m_is_valid_ucode = true;
m_constant_offsets.clear(); m_constant_offsets.clear();
enum // For GLSL scope wind/unwind. We store the min scope depth and loop count for each block and "unwind" to it.
{ // This should recover information lost when multiple nodes converge on a single merge node or even skip a merge node as is the case with "ELSE" nodes.
FORCE_NONE, std::unordered_map<const BasicBlock*, std::pair<int, u32>> block_data;
FORCE_SCT,
FORCE_SCB,
};
int forced_unit = FORCE_NONE; auto push_block_info = [&](const BasicBlock* block)
{
u32 loop = m_loop_count;
int level = m_code_level;
auto found = block_data.find(block);
if (found != block_data.end())
{
level = std::min(level, found->second.first);
loop = std::min(loop, found->second.second);
}
block_data[block] = { level, loop };
};
for (const auto &block : graph.blocks) for (const auto &block : graph.blocks)
{ {
// TODO: Handle block prologue if any auto found = block_data.find(&block);
if (found != block_data.end())
{
const auto [level, loop] = found->second;
for (int i = m_code_level; i > level; i--)
{
m_code_level--;
AddCode("}");
}
m_loop_count = loop;
}
if (!block.pred.empty()) if (!block.pred.empty())
{ {
// CFG guarantees predecessors are sorted, closest one first // Predecessors are always sorted closest last.
for (const auto& pred : block.pred) // This gives some adjacency info and tells us how the previous block connects to this one.
const auto& pred = block.pred.back();
switch (pred.type)
{ {
switch (pred.type) case EdgeType::LOOP:
{ m_loop_count++;
case EdgeType::ENDLOOP: [[ fallthrough ]];
// Because of succession rules, endloop is seen twice. case EdgeType::IF:
// Once from the the for statement at the end of the parent AddCode("{");
// and again at the end of the child block. m_code_level++;
if (pred.from->is_of_type(EdgeType::LOOP)) break;
{ case EdgeType::ELSE:
m_loop_count--; AddCode("else");
m_code_level--; AddCode("{");
AddCode("}"); m_code_level++;
} break;
break; case EdgeType::ENDIF:
case EdgeType::ENDIF: case EdgeType::ENDLOOP:
{ // Pure merge block?
// Same thing happens with ENDIF break;
// Once for the IF statement itself default:
// And again for the child blocks with code for the IF and ELSE paths. fmt::throw_exception("Unhandled edge type %d", static_cast<int>(pred.type));
const bool is_else_end = pred.from->is_of_type(EdgeType::ELSE); break;
const bool is_if_end = pred.from->is_of_type(EdgeType::IF) && }
!pred.from->has_sibling_of_type(EdgeType::ELSE); // Avoid double-counting if the IF has an ELSE sibling }
if (is_else_end || is_if_end)
{ if (!block.prologue.empty())
m_code_level--; {
AddCode("}"); AddCode("// Prologue");
}
break; for (auto& inst : block.prologue)
} {
case EdgeType::LOOP: m_instruction = &inst;
m_loop_count++; dst.HEX = inst.bytecode[0];
[[ fallthrough ]]; src0.HEX = inst.bytecode[1];
case EdgeType::IF: src1.HEX = inst.bytecode[2];
// Instruction will be inserted by the SIP decoder src2.HEX = inst.bytecode[3];
AddCode("{");
m_code_level++; ensure(handle_tex_srb(inst.opcode) || handle_sct_scb(inst.opcode), "Unsupported operation");
break;
case EdgeType::ELSE:
// This one needs more testing
m_code_level--;
AddCode("}");
AddCode("else");
AddCode("{");
m_code_level++;
break;
default:
// Start a new block anyway
fmt::throw_exception("Unexpected block found");
}
} }
} }
@@ -1398,11 +1409,9 @@ std::string FragmentProgramDecompiler::Decompile()
opflags = 0; opflags = 0;
const u32 opcode = dst.opcode | (src1.opcode_is_branch << 6);
auto SIP = [&]() auto SIP = [&]()
{ {
switch (opcode) switch (m_instruction->opcode)
{ {
case RSX_FP_OPCODE_BRK: case RSX_FP_OPCODE_BRK:
if (m_loop_count) AddFlowOp("break"); if (m_loop_count) AddFlowOp("break");
@@ -1412,12 +1421,10 @@ std::string FragmentProgramDecompiler::Decompile()
rsx_log.error("Unimplemented SIP instruction: CAL"); rsx_log.error("Unimplemented SIP instruction: CAL");
break; break;
case RSX_FP_OPCODE_FENCT: case RSX_FP_OPCODE_FENCT:
AddCode("//FENCT"); AddCode("// FENCT");
forced_unit = FORCE_SCT;
break; break;
case RSX_FP_OPCODE_FENCB: case RSX_FP_OPCODE_FENCB:
AddCode("//FENCB"); AddCode("// FENCB");
forced_unit = FORCE_SCB;
break; break;
case RSX_FP_OPCODE_IFE: case RSX_FP_OPCODE_IFE:
AddCode("if($cond)"); AddCode("if($cond)");
@@ -1441,7 +1448,7 @@ std::string FragmentProgramDecompiler::Decompile()
return true; return true;
}; };
switch (opcode) switch (m_instruction->opcode)
{ {
case RSX_FP_OPCODE_NOP: case RSX_FP_OPCODE_NOP:
break; break;
@@ -1450,19 +1457,10 @@ std::string FragmentProgramDecompiler::Decompile()
AddFlowOp("_kill()"); AddFlowOp("_kill()");
break; break;
default: default:
int prev_force_unit = forced_unit;
// Some instructions do not respect forced unit
// Tested with Tales of Vesperia
if (SIP()) break; if (SIP()) break;
if (handle_tex_srb(opcode)) break; if (handle_tex_srb(m_instruction->opcode)) break;
if (handle_sct_scb(m_instruction->opcode)) break;
// FENCT/FENCB do not actually reject instructions if they dont match the forced unit rsx_log.error("Unknown/illegal instruction: 0x%x", m_instruction->opcode);
// Looks like they are optimization hints and not hard-coded forced paths
if (handle_sct_scb(opcode)) break;
forced_unit = FORCE_NONE;
rsx_log.error("Unknown/illegal instruction: 0x%x (forced unit %d)", opcode, prev_force_unit);
break; break;
} }
@@ -1470,32 +1468,38 @@ std::string FragmentProgramDecompiler::Decompile()
if (dst.end) break; if (dst.end) break;
} }
if (block.epilogue.empty()) if (!block.epilogue.empty())
{ {
continue; AddCode("// Epilogue");
for (auto& inst : block.epilogue)
{
m_instruction = &inst;
dst.HEX = inst.bytecode[0];
src0.HEX = inst.bytecode[1];
src1.HEX = inst.bytecode[2];
src2.HEX = inst.bytecode[3];
ensure(handle_tex_srb(inst.opcode) || handle_sct_scb(inst.opcode), "Unsupported operation");
}
} }
AddCode("// Epilogue"); for (auto& succ : block.succ)
for (auto& inst : block.epilogue)
{ {
m_instruction = &inst; switch (succ.type)
dst.HEX = inst.bytecode[0]; {
src0.HEX = inst.bytecode[1]; case EdgeType::ENDIF:
src1.HEX = inst.bytecode[2]; case EdgeType::ENDLOOP:
src2.HEX = inst.bytecode[3]; case EdgeType::ELSE:
push_block_info(succ.to);
ensure(handle_tex_srb(inst.opcode) || handle_sct_scb(inst.opcode), "Unsupported operation"); break;
default:
break;
}
} }
} }
while (m_code_level > 1) ensure(m_code_level == 1);
{
// Happens if the last block was hanging (no merge)
// FIXME: We must always have a merge block on exit to resolve dependencies on outputs
m_code_level--;
AddCode("}");
}
// flush m_code_level // flush m_code_level
m_code_level = 1; m_code_level = 1;