Add partial support for the BRX shader instruction

This commit is contained in:
gdk 2019-11-14 14:20:30 -03:00 committed by Thog
parent d274328c31
commit f0a59f345c
15 changed files with 242 additions and 126 deletions

View file

@ -11,15 +11,17 @@ namespace Ryujinx.Graphics.Shader.Decoders
public Block Next { get; set; } public Block Next { get; set; }
public Block Branch { get; set; } public Block Branch { get; set; }
public List<OpCode> OpCodes { get; } public OpCodeBranchIndir BrIndir { get; set; }
public List<OpCodeSsy> SsyOpCodes { get; }
public List<OpCode> OpCodes { get; }
public List<OpCodePush> PushOpCodes { get; }
public Block(ulong address) public Block(ulong address)
{ {
Address = address; Address = address;
OpCodes = new List<OpCode>(); OpCodes = new List<OpCode>();
SsyOpCodes = new List<OpCodeSsy>(); PushOpCodes = new List<OpCodePush>();
} }
public void Split(Block rightBlock) public void Split(Block rightBlock)
@ -45,7 +47,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
rightBlock.OpCodes.AddRange(OpCodes.GetRange(splitIndex, splitCount)); rightBlock.OpCodes.AddRange(OpCodes.GetRange(splitIndex, splitCount));
rightBlock.UpdateSsyOpCodes(); rightBlock.UpdatePushOps();
EndAddress = rightBlock.Address; EndAddress = rightBlock.Address;
@ -54,7 +56,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
OpCodes.RemoveRange(splitIndex, splitCount); OpCodes.RemoveRange(splitIndex, splitCount);
UpdateSsyOpCodes(); UpdatePushOps();
} }
private static int BinarySearch(List<OpCode> opCodes, ulong address) private static int BinarySearch(List<OpCode> opCodes, ulong address)
@ -99,18 +101,18 @@ namespace Ryujinx.Graphics.Shader.Decoders
return null; return null;
} }
public void UpdateSsyOpCodes() public void UpdatePushOps()
{ {
SsyOpCodes.Clear(); PushOpCodes.Clear();
for (int index = 0; index < OpCodes.Count; index++) for (int index = 0; index < OpCodes.Count; index++)
{ {
if (!(OpCodes[index] is OpCodeSsy op)) if (!(OpCodes[index] is OpCodePush op))
{ {
continue; continue;
} }
SsyOpCodes.Add(op); PushOpCodes.Add(op);
} }
} }
} }

View file

@ -43,9 +43,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
return block; return block;
} }
ulong startAddress = headerSize; GetBlock(0);
GetBlock(startAddress);
while (workQueue.TryDequeue(out Block currBlock)) while (workQueue.TryDequeue(out Block currBlock))
{ {
@ -67,7 +65,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
} }
// If we have a block after the current one, set the limit address. // If we have a block after the current one, set the limit address.
ulong limitAddress = (ulong)code.Length; ulong limitAddress = (ulong)code.Length - headerSize;
if (nBlkIndex != blocks.Count) if (nBlkIndex != blocks.Count)
{ {
@ -85,13 +83,15 @@ namespace Ryujinx.Graphics.Shader.Decoders
} }
} }
FillBlock(code, currBlock, limitAddress, startAddress); FillBlock(code, currBlock, limitAddress, headerSize);
if (currBlock.OpCodes.Count != 0) if (currBlock.OpCodes.Count != 0)
{ {
foreach (OpCodeSsy ssyOp in currBlock.SsyOpCodes) // We should have blocks for all possible branch targets,
// including those from SSY/PBK instructions.
foreach (OpCodePush pushOp in currBlock.PushOpCodes)
{ {
GetBlock(ssyOp.GetAbsoluteAddress()); GetBlock(pushOp.GetAbsoluteAddress());
} }
// Set child blocks. "Branch" is the block the branch instruction // Set child blocks. "Branch" is the block the branch instruction
@ -100,9 +100,25 @@ namespace Ryujinx.Graphics.Shader.Decoders
// or end of program, Next is null. // or end of program, Next is null.
OpCode lastOp = currBlock.GetLastOp(); OpCode lastOp = currBlock.GetLastOp();
if (lastOp is OpCodeBranch op) if (lastOp is OpCodeBranch opBr)
{ {
currBlock.Branch = GetBlock(op.GetAbsoluteAddress()); currBlock.Branch = GetBlock(opBr.GetAbsoluteAddress());
}
else if (lastOp is OpCodeBranchIndir opBrIndir)
{
// An indirect branch could go anywhere, we don't know the target.
// Those instructions are usually used on a switch to jump table
// compiler optimization, and in those cases the possible targets
// seems to be always right after the BRX itself. We can assume
// that the possible targets are all the blocks in-between the
// instruction right after the BRX, and the common target that
// all the "cases" should eventually jump to, acting as the
// switch break.
Block firstTarget = GetBlock(currBlock.EndAddress);
firstTarget.BrIndir = opBrIndir;
opBrIndir.PossibleTargets.Add(firstTarget);
} }
if (!IsUnconditionalBranch(lastOp)) if (!IsUnconditionalBranch(lastOp))
@ -122,13 +138,28 @@ namespace Ryujinx.Graphics.Shader.Decoders
{ {
blocks.Add(currBlock); blocks.Add(currBlock);
} }
// Do we have a block after the current one?
if (!IsExit(currBlock.GetLastOp()) && currBlock.BrIndir != null)
{
bool targetVisited = visited.ContainsKey(currBlock.EndAddress);
Block possibleTarget = GetBlock(currBlock.EndAddress);
currBlock.BrIndir.PossibleTargets.Add(possibleTarget);
if (!targetVisited)
{
possibleTarget.BrIndir = currBlock.BrIndir;
}
}
} }
foreach (Block ssyBlock in blocks.Where(x => x.SsyOpCodes.Count != 0)) foreach (Block block in blocks.Where(x => x.PushOpCodes.Count != 0))
{ {
for (int ssyIndex = 0; ssyIndex < ssyBlock.SsyOpCodes.Count; ssyIndex++) for (int pushOpIndex = 0; pushOpIndex < block.PushOpCodes.Count; pushOpIndex++)
{ {
PropagateSsy(visited, ssyBlock, ssyIndex); PropagatePushOp(visited, block, pushOpIndex);
} }
} }
@ -180,21 +211,21 @@ namespace Ryujinx.Graphics.Shader.Decoders
do do
{ {
if (address >= limitAddress) if (address + 7 >= limitAddress)
{ {
break; break;
} }
// Ignore scheduling instructions, which are written every 32 bytes. // Ignore scheduling instructions, which are written every 32 bytes.
if (((address - startAddress) & 0x1f) == 0) if ((address & 0x1f) == 0)
{ {
address += 8; address += 8;
continue; continue;
} }
uint word0 = BinaryPrimitives.ReadUInt32LittleEndian(code.Slice((int)address)); uint word0 = BinaryPrimitives.ReadUInt32LittleEndian(code.Slice((int)(startAddress + address)));
uint word1 = BinaryPrimitives.ReadUInt32LittleEndian(code.Slice((int)address + 4)); uint word1 = BinaryPrimitives.ReadUInt32LittleEndian(code.Slice((int)(startAddress + address + 4)));
ulong opAddress = address; ulong opAddress = address;
@ -221,7 +252,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
block.EndAddress = address; block.EndAddress = address;
block.UpdateSsyOpCodes(); block.UpdatePushOps();
} }
private static bool IsUnconditionalBranch(OpCode opCode) private static bool IsUnconditionalBranch(OpCode opCode)
@ -242,10 +273,16 @@ namespace Ryujinx.Graphics.Shader.Decoders
private static bool IsBranch(OpCode opCode) private static bool IsBranch(OpCode opCode)
{ {
return (opCode is OpCodeBranch opBranch && !opBranch.PushTarget) || return (opCode is OpCodeBranch opBranch && !opBranch.PushTarget) ||
opCode is OpCodeSync || opCode is OpCodeBranchIndir ||
opCode is OpCodeBranchPop ||
opCode is OpCodeExit; opCode is OpCodeExit;
} }
private static bool IsExit(OpCode opCode)
{
return opCode is OpCodeExit;
}
private static OpCode MakeOpCode(Type type, InstEmitter emitter, ulong address, long opCode) private static OpCode MakeOpCode(Type type, InstEmitter emitter, ulong address, long opCode)
{ {
if (type == null) if (type == null)
@ -282,8 +319,8 @@ namespace Ryujinx.Graphics.Shader.Decoders
private enum RestoreType private enum RestoreType
{ {
None, None,
PopSsy, PopPushOp,
PushSync PushBranchOp
} }
private RestoreType _restoreType; private RestoreType _restoreType;
@ -299,45 +336,45 @@ namespace Ryujinx.Graphics.Shader.Decoders
_restoreValue = 0; _restoreValue = 0;
} }
public PathBlockState(int oldSsyStackSize) public PathBlockState(int oldStackSize)
{ {
Block = null; Block = null;
_restoreType = RestoreType.PopSsy; _restoreType = RestoreType.PopPushOp;
_restoreValue = (ulong)oldSsyStackSize; _restoreValue = (ulong)oldStackSize;
} }
public PathBlockState(ulong syncAddress) public PathBlockState(ulong syncAddress)
{ {
Block = null; Block = null;
_restoreType = RestoreType.PushSync; _restoreType = RestoreType.PushBranchOp;
_restoreValue = syncAddress; _restoreValue = syncAddress;
} }
public void RestoreStackState(Stack<ulong> ssyStack) public void RestoreStackState(Stack<ulong> branchStack)
{ {
if (_restoreType == RestoreType.PushSync) if (_restoreType == RestoreType.PushBranchOp)
{ {
ssyStack.Push(_restoreValue); branchStack.Push(_restoreValue);
} }
else if (_restoreType == RestoreType.PopSsy) else if (_restoreType == RestoreType.PopPushOp)
{ {
while (ssyStack.Count > (uint)_restoreValue) while (branchStack.Count > (uint)_restoreValue)
{ {
ssyStack.Pop(); branchStack.Pop();
} }
} }
} }
} }
private static void PropagateSsy(Dictionary<ulong, Block> blocks, Block ssyBlock, int ssyIndex) private static void PropagatePushOp(Dictionary<ulong, Block> blocks, Block currBlock, int pushOpIndex)
{ {
OpCodeSsy ssyOp = ssyBlock.SsyOpCodes[ssyIndex]; OpCodePush pushOp = currBlock.PushOpCodes[pushOpIndex];
Stack<PathBlockState> workQueue = new Stack<PathBlockState>(); Stack<PathBlockState> workQueue = new Stack<PathBlockState>();
HashSet<Block> visited = new HashSet<Block>(); HashSet<Block> visited = new HashSet<Block>();
Stack<ulong> ssyStack = new Stack<ulong>(); Stack<ulong> branchStack = new Stack<ulong>();
void Push(PathBlockState pbs) void Push(PathBlockState pbs)
{ {
@ -347,32 +384,32 @@ namespace Ryujinx.Graphics.Shader.Decoders
} }
} }
Push(new PathBlockState(ssyBlock)); Push(new PathBlockState(currBlock));
while (workQueue.TryPop(out PathBlockState pbs)) while (workQueue.TryPop(out PathBlockState pbs))
{ {
if (pbs.ReturningFromVisit) if (pbs.ReturningFromVisit)
{ {
pbs.RestoreStackState(ssyStack); pbs.RestoreStackState(branchStack);
continue; continue;
} }
Block current = pbs.Block; Block current = pbs.Block;
int ssyOpCodesCount = current.SsyOpCodes.Count; int pushOpsCount = current.PushOpCodes.Count;
if (ssyOpCodesCount != 0) if (pushOpsCount != 0)
{ {
Push(new PathBlockState(ssyStack.Count)); Push(new PathBlockState(branchStack.Count));
for (int index = ssyIndex; index < ssyOpCodesCount; index++) for (int index = pushOpIndex; index < pushOpsCount; index++)
{ {
ssyStack.Push(current.SsyOpCodes[index].GetAbsoluteAddress()); branchStack.Push(current.PushOpCodes[index].GetAbsoluteAddress());
} }
} }
ssyIndex = 0; pushOpIndex = 0;
if (current.Next != null) if (current.Next != null)
{ {
@ -383,17 +420,24 @@ namespace Ryujinx.Graphics.Shader.Decoders
{ {
Push(new PathBlockState(current.Branch)); Push(new PathBlockState(current.Branch));
} }
else if (current.GetLastOp() is OpCodeSync op) else if (current.GetLastOp() is OpCodeBranchIndir brIndir)
{ {
ulong syncAddress = ssyStack.Pop(); foreach (Block possibleTarget in brIndir.PossibleTargets)
if (ssyStack.Count == 0)
{ {
ssyStack.Push(syncAddress); Push(new PathBlockState(possibleTarget));
}
}
else if (current.GetLastOp() is OpCodeBranchPop op)
{
ulong syncAddress = branchStack.Pop();
op.Targets.Add(ssyOp, op.Targets.Count); if (branchStack.Count == 0)
{
branchStack.Push(syncAddress);
ssyOp.Syncs.TryAdd(op, Local()); op.Targets.Add(pushOp, op.Targets.Count);
pushOp.PopOps.TryAdd(op, Local());
} }
else else
{ {

View file

@ -1,6 +1,6 @@
namespace Ryujinx.Graphics.Shader.Decoders namespace Ryujinx.Graphics.Shader.Decoders
{ {
enum FmulScale enum FPMultiplyScale
{ {
None = 0, None = 0,
Divide2 = 1, Divide2 = 1,

View file

@ -4,7 +4,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
{ {
RoundingMode RoundingMode { get; } RoundingMode RoundingMode { get; }
FmulScale Scale { get; } FPMultiplyScale Scale { get; }
bool FlushToZero { get; } bool FlushToZero { get; }
bool AbsoluteA { get; } bool AbsoluteA { get; }

View file

@ -0,0 +1,23 @@
using Ryujinx.Graphics.Shader.Instructions;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.Decoders
{
class OpCodeBranchIndir : OpCode
{
public HashSet<Block> PossibleTargets { get; }
public Register Ra { get; }
public int Offset { get; }
public OpCodeBranchIndir(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode)
{
PossibleTargets = new HashSet<Block>();
Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr);
Offset = ((int)(opCode >> 20) << 8) >> 8;
}
}
}

View file

@ -0,0 +1,15 @@
using Ryujinx.Graphics.Shader.Instructions;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.Decoders
{
class OpCodeBranchPop : OpCode
{
public Dictionary<OpCodePush, int> Targets { get; }
public OpCodeBranchPop(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode)
{
Targets = new Dictionary<OpCodePush, int>();
}
}
}

View file

@ -6,7 +6,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
{ {
public RoundingMode RoundingMode { get; } public RoundingMode RoundingMode { get; }
public FmulScale Scale { get; } public FPMultiplyScale Scale { get; }
public bool FlushToZero { get; } public bool FlushToZero { get; }
public bool AbsoluteA { get; } public bool AbsoluteA { get; }
@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
{ {
RoundingMode = (RoundingMode)opCode.Extract(39, 2); RoundingMode = (RoundingMode)opCode.Extract(39, 2);
Scale = (FmulScale)opCode.Extract(41, 3); Scale = (FPMultiplyScale)opCode.Extract(41, 3);
FlushToZero = opCode.Extract(44); FlushToZero = opCode.Extract(44);
AbsoluteA = opCode.Extract(46); AbsoluteA = opCode.Extract(46);

View file

@ -7,7 +7,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
{ {
public RoundingMode RoundingMode => RoundingMode.ToNearest; public RoundingMode RoundingMode => RoundingMode.ToNearest;
public FmulScale Scale => FmulScale.None; public FPMultiplyScale Scale => FPMultiplyScale.None;
public bool FlushToZero { get; } public bool FlushToZero { get; }
public bool AbsoluteA { get; } public bool AbsoluteA { get; }

View file

@ -4,13 +4,13 @@ using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.Decoders namespace Ryujinx.Graphics.Shader.Decoders
{ {
class OpCodeSsy : OpCodeBranch class OpCodePush : OpCodeBranch
{ {
public Dictionary<OpCodeSync, Operand> Syncs { get; } public Dictionary<OpCodeBranchPop, Operand> PopOps { get; }
public OpCodeSsy(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) public OpCodePush(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode)
{ {
Syncs = new Dictionary<OpCodeSync, Operand>(); PopOps = new Dictionary<OpCodeBranchPop, Operand>();
Predicate = new Register(RegisterConsts.PredicateTrueIndex, RegisterType.Predicate); Predicate = new Register(RegisterConsts.PredicateTrueIndex, RegisterType.Predicate);

View file

@ -1,15 +0,0 @@
using Ryujinx.Graphics.Shader.Instructions;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.Decoders
{
class OpCodeSync : OpCode
{
public Dictionary<OpCodeSsy, int> Targets { get; }
public OpCodeSync(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode)
{
Targets = new Dictionary<OpCodeSsy, int>();
}
}
}

View file

@ -41,7 +41,8 @@ namespace Ryujinx.Graphics.Shader.Decoders
Set("0101001111110x", InstEmit.Bfi, typeof(OpCodeAluRegCbuf)); Set("0101001111110x", InstEmit.Bfi, typeof(OpCodeAluRegCbuf));
Set("0101101111110x", InstEmit.Bfi, typeof(OpCodeAluReg)); Set("0101101111110x", InstEmit.Bfi, typeof(OpCodeAluReg));
Set("111000100100xx", InstEmit.Bra, typeof(OpCodeBranch)); Set("111000100100xx", InstEmit.Bra, typeof(OpCodeBranch));
Set("111000110100xx", InstEmit.Brk, typeof(OpCodeSync)); Set("111000110100xx", InstEmit.Brk, typeof(OpCodeBranchPop));
Set("111000100101xx", InstEmit.Brx, typeof(OpCodeBranchIndir));
Set("0101000010100x", InstEmit.Csetp, typeof(OpCodePsetp)); Set("0101000010100x", InstEmit.Csetp, typeof(OpCodePsetp));
Set("111000110000xx", InstEmit.Exit, typeof(OpCodeExit)); Set("111000110000xx", InstEmit.Exit, typeof(OpCodeExit));
Set("0100110010101x", InstEmit.F2F, typeof(OpCodeFArithCbuf)); Set("0100110010101x", InstEmit.F2F, typeof(OpCodeFArithCbuf));
@ -137,7 +138,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
Set("0101110010011x", InstEmit.Mov, typeof(OpCodeAluReg)); Set("0101110010011x", InstEmit.Mov, typeof(OpCodeAluReg));
Set("0101000010000x", InstEmit.Mufu, typeof(OpCodeFArith)); Set("0101000010000x", InstEmit.Mufu, typeof(OpCodeFArith));
Set("1111101111100x", InstEmit.Out, typeof(OpCode)); Set("1111101111100x", InstEmit.Out, typeof(OpCode));
Set("111000101010xx", InstEmit.Pbk, typeof(OpCodeSsy)); Set("111000101010xx", InstEmit.Pbk, typeof(OpCodePush));
Set("0100110000001x", InstEmit.Popc, typeof(OpCodeAluCbuf)); Set("0100110000001x", InstEmit.Popc, typeof(OpCodeAluCbuf));
Set("0011100x00001x", InstEmit.Popc, typeof(OpCodeAluImm)); Set("0011100x00001x", InstEmit.Popc, typeof(OpCodeAluImm));
Set("0101110000001x", InstEmit.Popc, typeof(OpCodeAluReg)); Set("0101110000001x", InstEmit.Popc, typeof(OpCodeAluReg));
@ -157,12 +158,12 @@ namespace Ryujinx.Graphics.Shader.Decoders
Set("0100110000101x", InstEmit.Shr, typeof(OpCodeAluCbuf)); Set("0100110000101x", InstEmit.Shr, typeof(OpCodeAluCbuf));
Set("0011100x00101x", InstEmit.Shr, typeof(OpCodeAluImm)); Set("0011100x00101x", InstEmit.Shr, typeof(OpCodeAluImm));
Set("0101110000101x", InstEmit.Shr, typeof(OpCodeAluReg)); Set("0101110000101x", InstEmit.Shr, typeof(OpCodeAluReg));
Set("111000101001xx", InstEmit.Ssy, typeof(OpCodeSsy)); Set("111000101001xx", InstEmit.Ssy, typeof(OpCodePush));
Set("1110111101010x", InstEmit.St, typeof(OpCodeMemory)); Set("1110111101010x", InstEmit.St, typeof(OpCodeMemory));
Set("1110111011011x", InstEmit.Stg, typeof(OpCodeMemory)); Set("1110111011011x", InstEmit.Stg, typeof(OpCodeMemory));
Set("1110111101011x", InstEmit.Sts, typeof(OpCodeMemory)); Set("1110111101011x", InstEmit.Sts, typeof(OpCodeMemory));
Set("11101011001xxx", InstEmit.Sust, typeof(OpCodeImage)); Set("11101011001xxx", InstEmit.Sust, typeof(OpCodeImage));
Set("1111000011111x", InstEmit.Sync, typeof(OpCodeSync)); Set("1111000011111x", InstEmit.Sync, typeof(OpCodeBranchPop));
Set("110000xxxx111x", InstEmit.Tex, typeof(OpCodeTex)); Set("110000xxxx111x", InstEmit.Tex, typeof(OpCodeTex));
Set("1101111010111x", InstEmit.TexB, typeof(OpCodeTexB)); Set("1101111010111x", InstEmit.TexB, typeof(OpCodeTexB));
Set("1101x00xxxxxxx", InstEmit.Texs, typeof(OpCodeTexs)); Set("1101x00xxxxxxx", InstEmit.Texs, typeof(OpCodeTexs));

View file

@ -97,14 +97,14 @@ namespace Ryujinx.Graphics.Shader.Instructions
switch (op.Scale) switch (op.Scale)
{ {
case FmulScale.None: break; case FPMultiplyScale.None: break;
case FmulScale.Divide2: srcA = context.FPDivide (srcA, ConstF(2)); break; case FPMultiplyScale.Divide2: srcA = context.FPDivide (srcA, ConstF(2)); break;
case FmulScale.Divide4: srcA = context.FPDivide (srcA, ConstF(4)); break; case FPMultiplyScale.Divide4: srcA = context.FPDivide (srcA, ConstF(4)); break;
case FmulScale.Divide8: srcA = context.FPDivide (srcA, ConstF(8)); break; case FPMultiplyScale.Divide8: srcA = context.FPDivide (srcA, ConstF(8)); break;
case FmulScale.Multiply2: srcA = context.FPMultiply(srcA, ConstF(2)); break; case FPMultiplyScale.Multiply2: srcA = context.FPMultiply(srcA, ConstF(2)); break;
case FmulScale.Multiply4: srcA = context.FPMultiply(srcA, ConstF(4)); break; case FPMultiplyScale.Multiply4: srcA = context.FPMultiply(srcA, ConstF(4)); break;
case FmulScale.Multiply8: srcA = context.FPMultiply(srcA, ConstF(8)); break; case FPMultiplyScale.Multiply8: srcA = context.FPMultiply(srcA, ConstF(8)); break;
default: break; //TODO: Warning. default: break; //TODO: Warning.
} }

View file

@ -20,6 +20,36 @@ namespace Ryujinx.Graphics.Shader.Instructions
EmitBrkOrSync(context); EmitBrkOrSync(context);
} }
public static void Brx(EmitterContext context)
{
OpCodeBranchIndir op = (OpCodeBranchIndir)context.CurrOp;
int offset = (int)op.Address + 8 + op.Offset;
Operand address = context.IAdd(Register(op.Ra), Const(offset));
// Sorting the target addresses in descending order improves the code,
// since it will always check the most distant targets first, then the
// near ones. This can be easily transformed into if/else statements.
IOrderedEnumerable<Block> sortedTargets = op.PossibleTargets.OrderByDescending(x => x.Address);
Block lastTarget = sortedTargets.LastOrDefault();
foreach (Block possibleTarget in sortedTargets)
{
Operand label = context.GetLabel(possibleTarget.Address);
if (possibleTarget != lastTarget)
{
context.BranchIfTrue(label, context.ICompareEqual(address, Const((int)possibleTarget.Address)));
}
else
{
context.Branch(label);
}
}
}
public static void Exit(EmitterContext context) public static void Exit(EmitterContext context)
{ {
OpCodeExit op = (OpCodeExit)context.CurrOp; OpCodeExit op = (OpCodeExit)context.CurrOp;
@ -54,45 +84,45 @@ namespace Ryujinx.Graphics.Shader.Instructions
private static void EmitPbkOrSsy(EmitterContext context) private static void EmitPbkOrSsy(EmitterContext context)
{ {
OpCodeSsy op = (OpCodeSsy)context.CurrOp; OpCodePush op = (OpCodePush)context.CurrOp;
foreach (KeyValuePair<OpCodeSync, Operand> kv in op.Syncs) foreach (KeyValuePair<OpCodeBranchPop, Operand> kv in op.PopOps)
{ {
OpCodeSync opSync = kv.Key; OpCodeBranchPop opSync = kv.Key;
Operand local = kv.Value; Operand local = kv.Value;
int ssyIndex = opSync.Targets[op]; int pushOpIndex = opSync.Targets[op];
context.Copy(local, Const(ssyIndex)); context.Copy(local, Const(pushOpIndex));
} }
} }
private static void EmitBrkOrSync(EmitterContext context) private static void EmitBrkOrSync(EmitterContext context)
{ {
OpCodeSync op = (OpCodeSync)context.CurrOp; OpCodeBranchPop op = (OpCodeBranchPop)context.CurrOp;
if (op.Targets.Count == 1) if (op.Targets.Count == 1)
{ {
// If we have only one target, then the SSY is basically // If we have only one target, then the SSY/PBK is basically
// a branch, we can produce better codegen for this case. // a branch, we can produce better codegen for this case.
OpCodeSsy opSsy = op.Targets.Keys.First(); OpCodePush pushOp = op.Targets.Keys.First();
EmitBranch(context, opSsy.GetAbsoluteAddress()); EmitBranch(context, pushOp.GetAbsoluteAddress());
} }
else else
{ {
foreach (KeyValuePair<OpCodeSsy, int> kv in op.Targets) foreach (KeyValuePair<OpCodePush, int> kv in op.Targets)
{ {
OpCodeSsy opSsy = kv.Key; OpCodePush pushOp = kv.Key;
Operand label = context.GetLabel(opSsy.GetAbsoluteAddress()); Operand label = context.GetLabel(pushOp.GetAbsoluteAddress());
Operand local = opSsy.Syncs[op]; Operand local = pushOp.PopOps[op];
int ssyIndex = kv.Value; int pushOpIndex = kv.Value;
context.BranchIfTrue(label, context.ICompareEqual(local, Const(ssyIndex))); context.BranchIfTrue(label, context.ICompareEqual(local, Const(pushOpIndex)));
} }
} }
} }

View file

@ -10,11 +10,14 @@ namespace Ryujinx.Graphics.Shader
public string Code { get; private set; } public string Code { get; private set; }
internal ShaderProgram(ShaderProgramInfo info, ShaderStage stage, string code) public int Size { get; }
internal ShaderProgram(ShaderProgramInfo info, ShaderStage stage, string code, int size)
{ {
Info = info; Info = info;
Stage = stage; Stage = stage;
Code = code; Code = code;
Size = size;
} }
public void Prepend(string line) public void Prepend(string line)

View file

@ -46,7 +46,12 @@ namespace Ryujinx.Graphics.Shader.Translation
bool compute = (translationConfig.Flags & TranslationFlags.Compute) != 0; bool compute = (translationConfig.Flags & TranslationFlags.Compute) != 0;
bool debugMode = (translationConfig.Flags & TranslationFlags.DebugMode) != 0; bool debugMode = (translationConfig.Flags & TranslationFlags.DebugMode) != 0;
Operation[] ops = DecodeShader(code, compute, debugMode, out ShaderHeader header); Operation[] ops = DecodeShader(
code,
compute,
debugMode,
out ShaderHeader header,
out int size);
ShaderStage stage; ShaderStage stage;
@ -76,15 +81,15 @@ namespace Ryujinx.Graphics.Shader.Translation
maxOutputVertexCount, maxOutputVertexCount,
outputTopology); outputTopology);
return Translate(ops, config); return Translate(ops, config, size);
} }
public static ShaderProgram Translate(Span<byte> vpACode, Span<byte> vpBCode, TranslationConfig translationConfig) public static ShaderProgram Translate(Span<byte> vpACode, Span<byte> vpBCode, TranslationConfig translationConfig)
{ {
bool debugMode = (translationConfig.Flags & TranslationFlags.DebugMode) != 0; bool debugMode = (translationConfig.Flags & TranslationFlags.DebugMode) != 0;
Operation[] vpAOps = DecodeShader(vpACode, compute: false, debugMode, out _); Operation[] vpAOps = DecodeShader(vpACode, compute: false, debugMode, out _, out _);
Operation[] vpBOps = DecodeShader(vpBCode, compute: false, debugMode, out ShaderHeader header); Operation[] vpBOps = DecodeShader(vpBCode, compute: false, debugMode, out ShaderHeader header, out int sizeB);
ShaderConfig config = new ShaderConfig( ShaderConfig config = new ShaderConfig(
header.Stage, header.Stage,
@ -93,10 +98,10 @@ namespace Ryujinx.Graphics.Shader.Translation
header.MaxOutputVertexCount, header.MaxOutputVertexCount,
header.OutputTopology); header.OutputTopology);
return Translate(Combine(vpAOps, vpBOps), config); return Translate(Combine(vpAOps, vpBOps), config, sizeB);
} }
private static ShaderProgram Translate(Operation[] ops, ShaderConfig config) private static ShaderProgram Translate(Operation[] ops, ShaderConfig config, int size)
{ {
BasicBlock[] irBlocks = ControlFlowGraph.MakeCfg(ops); BasicBlock[] irBlocks = ControlFlowGraph.MakeCfg(ops);
@ -122,17 +127,20 @@ namespace Ryujinx.Graphics.Shader.Translation
string glslCode = program.Code; string glslCode = program.Code;
return new ShaderProgram(spInfo, config.Stage, glslCode); return new ShaderProgram(spInfo, config.Stage, glslCode, size);
} }
private static Operation[] DecodeShader(Span<byte> code, bool compute, bool debugMode, out ShaderHeader header) private static Operation[] DecodeShader(
Span<byte> code,
bool compute,
bool debugMode,
out ShaderHeader header,
out int size)
{ {
Block[] cfg; Block[] cfg;
EmitterContext context; EmitterContext context;
ulong headerSize;
if (compute) if (compute)
{ {
header = null; header = null;
@ -140,8 +148,6 @@ namespace Ryujinx.Graphics.Shader.Translation
cfg = Decoder.Decode(code, 0); cfg = Decoder.Decode(code, 0);
context = new EmitterContext(ShaderStage.Compute, header); context = new EmitterContext(ShaderStage.Compute, header);
headerSize = 0;
} }
else else
{ {
@ -150,14 +156,19 @@ namespace Ryujinx.Graphics.Shader.Translation
cfg = Decoder.Decode(code, HeaderSize); cfg = Decoder.Decode(code, HeaderSize);
context = new EmitterContext(header.Stage, header); context = new EmitterContext(header.Stage, header);
headerSize = HeaderSize;
} }
ulong maxEndAddress = 0;
for (int blkIndex = 0; blkIndex < cfg.Length; blkIndex++) for (int blkIndex = 0; blkIndex < cfg.Length; blkIndex++)
{ {
Block block = cfg[blkIndex]; Block block = cfg[blkIndex];
if (maxEndAddress < block.EndAddress)
{
maxEndAddress = block.EndAddress;
}
context.CurrBlock = block; context.CurrBlock = block;
context.MarkLabel(context.GetLabel(block.Address)); context.MarkLabel(context.GetLabel(block.Address));
@ -179,7 +190,7 @@ namespace Ryujinx.Graphics.Shader.Translation
instName = "???"; instName = "???";
} }
string dbgComment = $"0x{(op.Address - headerSize):X6}: 0x{op.RawOpCode:X16} {instName}"; string dbgComment = $"0x{op.Address:X6}: 0x{op.RawOpCode:X16} {instName}";
context.Add(new CommentNode(dbgComment)); context.Add(new CommentNode(dbgComment));
} }
@ -193,13 +204,13 @@ namespace Ryujinx.Graphics.Shader.Translation
bool skipPredicateCheck = op.Emitter == InstEmit.Bra; bool skipPredicateCheck = op.Emitter == InstEmit.Bra;
if (op is OpCodeSync opSync) if (op is OpCodeBranchPop opBranchPop)
{ {
// If the instruction is a SYNC instruction with only one // If the instruction is a SYNC instruction with only one
// possible target address, then the instruction is basically // possible target address, then the instruction is basically
// just a simple branch, we can generate code similar to branch // just a simple branch, we can generate code similar to branch
// instructions, with the condition check on the branch itself. // instructions, with the condition check on the branch itself.
skipPredicateCheck |= opSync.Targets.Count < 2; skipPredicateCheck |= opBranchPop.Targets.Count < 2;
} }
if (!(op.Predicate.IsPT || skipPredicateCheck)) if (!(op.Predicate.IsPT || skipPredicateCheck))
@ -243,6 +254,8 @@ namespace Ryujinx.Graphics.Shader.Translation
} }
} }
size = (int)maxEndAddress + (compute ? 0 : HeaderSize);
return context.GetOperations(); return context.GetOperations();
} }