diff --git a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp index 68874272a1..b03f534fa9 100644 --- a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp +++ b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp @@ -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> 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(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;