diff --git a/src/SharpEmu.ShaderCompiler/Ir/Gen5IrBranchResolver.cs b/src/SharpEmu.ShaderCompiler/Ir/Gen5IrBranchResolver.cs new file mode 100644 index 00000000..c723ab3c --- /dev/null +++ b/src/SharpEmu.ShaderCompiler/Ir/Gen5IrBranchResolver.cs @@ -0,0 +1,67 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using System; + +namespace SharpEmu.ShaderCompiler.Ir; + +public sealed class Gen5IrBranchResolver : IIrBranchResolver +{ + public static Gen5IrBranchResolver Instance { get; } = new(); + + public bool IsBranch(Gen5ShaderInstruction instruction) => + IsUnconditionalBranch(instruction) || + IsConditional(instruction) || + IsTerminator(instruction); + + public bool IsConditional(Gen5ShaderInstruction instruction) => instruction.Opcode switch + { + "SCbranchScc0" or + "SCbranchScc1" or + "SCbranchVccz" or + "SCbranchVccnz" or + "SCbranchExecz" or + "SCbranchExecnz" or + "SCbranchCdbgsys" or + "SCbranchCdbguser" or + "SCbranchCdbgsysOrUser" or + "SCbranchCdbgsysAndUser" => true, + _ => false, + }; + + public bool TryGetBranchTarget(Gen5ShaderInstruction instruction, out uint targetPc) + { + targetPc = 0; + if (IsTerminator(instruction)) + { + return false; + } + + if (!IsUnconditionalBranch(instruction) && !IsConditional(instruction)) + { + return false; + } + + if (instruction.Encoding != Gen5ShaderEncoding.Sopp || instruction.Words.Count == 0) + { + return false; + } + + var offset = unchecked((short)(instruction.Words[0] & 0xFFFF)); + var nextPc = (long)instruction.Pc + instruction.Words.Count * sizeof(uint); + var target = nextPc + offset * sizeof(uint); + if (target < 0 || target > uint.MaxValue) + { + return false; + } + + targetPc = (uint)target; + return true; + } + + public static bool IsUnconditionalBranch(Gen5ShaderInstruction instruction) => + string.Equals(instruction.Opcode, "SBranch", StringComparison.Ordinal); + + public static bool IsTerminator(Gen5ShaderInstruction instruction) => + instruction.Opcode is "SEndpgm" or "SEndpgmSaved"; +}