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,72 +1319,83 @@ 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::ENDLOOP:
// Because of succession rules, endloop is seen twice.
// Once from the the for statement at the end of the parent
// and again at the end of the child block.
if (pred.from->is_of_type(EdgeType::LOOP))
{
m_loop_count--;
m_code_level--;
AddCode("}");
}
break;
case EdgeType::ENDIF:
{
// Same thing happens with ENDIF
// Once for the IF statement itself
// And again for the child blocks with code for the IF and ELSE paths.
const bool is_else_end = pred.from->is_of_type(EdgeType::ELSE);
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)
{
m_code_level--;
AddCode("}");
}
break;
}
case EdgeType::LOOP: case EdgeType::LOOP:
m_loop_count++; m_loop_count++;
[[ fallthrough ]]; [[ fallthrough ]];
case EdgeType::IF: case EdgeType::IF:
// Instruction will be inserted by the SIP decoder
AddCode("{"); AddCode("{");
m_code_level++; m_code_level++;
break; break;
case EdgeType::ELSE: case EdgeType::ELSE:
// This one needs more testing
m_code_level--;
AddCode("}");
AddCode("else"); AddCode("else");
AddCode("{"); AddCode("{");
m_code_level++; m_code_level++;
break; break;
case EdgeType::ENDIF:
case EdgeType::ENDLOOP:
// Pure merge block?
break;
default: default:
// Start a new block anyway fmt::throw_exception("Unhandled edge type %d", static_cast<int>(pred.type));
fmt::throw_exception("Unexpected block found"); break;
} }
} }
if (!block.prologue.empty())
{
AddCode("// Prologue");
for (auto& inst : block.prologue)
{
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");
}
} }
for (const auto& inst : block.instructions) for (const auto& inst : block.instructions)
@@ -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");
@@ -1413,11 +1422,9 @@ std::string FragmentProgramDecompiler::Decompile()
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,11 +1468,8 @@ std::string FragmentProgramDecompiler::Decompile()
if (dst.end) break; if (dst.end) break;
} }
if (block.epilogue.empty()) if (!block.epilogue.empty())
{ {
continue;
}
AddCode("// Epilogue"); AddCode("// Epilogue");
for (auto& inst : block.epilogue) for (auto& inst : block.epilogue)
@@ -1489,13 +1484,22 @@ std::string FragmentProgramDecompiler::Decompile()
} }
} }
while (m_code_level > 1) for (auto& succ : block.succ)
{ {
// Happens if the last block was hanging (no merge) switch (succ.type)
// FIXME: We must always have a merge block on exit to resolve dependencies on outputs {
m_code_level--; case EdgeType::ENDIF:
AddCode("}"); case EdgeType::ENDLOOP:
case EdgeType::ELSE:
push_block_info(succ.to);
break;
default:
break;
} }
}
}
ensure(m_code_level == 1);
// flush m_code_level // flush m_code_level
m_code_level = 1; m_code_level = 1;