rsx/fp: Reimplement GLSL code generation from CFG
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user