mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-12-16 04:09:07 +00:00
rsx/fp: Reimplement GLSL code generation from CFG
This commit is contained in:
parent
d23ea4760b
commit
f2913e4692
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user