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

View File

@ -1319,71 +1319,82 @@ std::string FragmentProgramDecompiler::Decompile()
m_is_valid_ucode = true;
m_constant_offsets.clear();
enum
{
FORCE_NONE,
FORCE_SCT,
FORCE_SCB,
};
// 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.
std::unordered_map<const BasicBlock*, std::pair<int, u32>> block_data;
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)
{
// 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())
{
// CFG guarantees predecessors are sorted, closest one first
for (const auto& pred : block.pred)
// Predecessors are always sorted closest last.
// 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:
m_loop_count++;
[[ fallthrough ]];
case EdgeType::IF:
// Instruction will be inserted by the SIP decoder
AddCode("{");
m_code_level++;
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");
}
case EdgeType::LOOP:
m_loop_count++;
[[ fallthrough ]];
case EdgeType::IF:
AddCode("{");
m_code_level++;
break;
case EdgeType::ELSE:
AddCode("else");
AddCode("{");
m_code_level++;
break;
case EdgeType::ENDIF:
case EdgeType::ENDLOOP:
// Pure merge block?
break;
default:
fmt::throw_exception("Unhandled edge type %d", static_cast<int>(pred.type));
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");
}
}
@ -1398,11 +1409,9 @@ std::string FragmentProgramDecompiler::Decompile()
opflags = 0;
const u32 opcode = dst.opcode | (src1.opcode_is_branch << 6);
auto SIP = [&]()
{
switch (opcode)
switch (m_instruction->opcode)
{
case RSX_FP_OPCODE_BRK:
if (m_loop_count) AddFlowOp("break");
@ -1412,12 +1421,10 @@ std::string FragmentProgramDecompiler::Decompile()
rsx_log.error("Unimplemented SIP instruction: CAL");
break;
case RSX_FP_OPCODE_FENCT:
AddCode("//FENCT");
forced_unit = FORCE_SCT;
AddCode("// FENCT");
break;
case RSX_FP_OPCODE_FENCB:
AddCode("//FENCB");
forced_unit = FORCE_SCB;
AddCode("// FENCB");
break;
case RSX_FP_OPCODE_IFE:
AddCode("if($cond)");
@ -1441,7 +1448,7 @@ std::string FragmentProgramDecompiler::Decompile()
return true;
};
switch (opcode)
switch (m_instruction->opcode)
{
case RSX_FP_OPCODE_NOP:
break;
@ -1450,19 +1457,10 @@ std::string FragmentProgramDecompiler::Decompile()
AddFlowOp("_kill()");
break;
default:
int prev_force_unit = forced_unit;
// Some instructions do not respect forced unit
// Tested with Tales of Vesperia
if (SIP()) break;
if (handle_tex_srb(opcode)) break;
// FENCT/FENCB do not actually reject instructions if they dont match the forced unit
// 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);
if (handle_tex_srb(m_instruction->opcode)) break;
if (handle_sct_scb(m_instruction->opcode)) break;
rsx_log.error("Unknown/illegal instruction: 0x%x", m_instruction->opcode);
break;
}
@ -1470,32 +1468,38 @@ std::string FragmentProgramDecompiler::Decompile()
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& inst : block.epilogue)
for (auto& succ : block.succ)
{
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");
switch (succ.type)
{
case EdgeType::ENDIF:
case EdgeType::ENDLOOP:
case EdgeType::ELSE:
push_block_info(succ.to);
break;
default:
break;
}
}
}
while (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("}");
}
ensure(m_code_level == 1);
// flush m_code_level
m_code_level = 1;