diff --git a/Ryujinx.Graphics/Shader/Decoders/Block.cs b/Ryujinx.Graphics/Shader/Decoders/Block.cs index d4d739c6e8..b5e610d713 100644 --- a/Ryujinx.Graphics/Shader/Decoders/Block.cs +++ b/Ryujinx.Graphics/Shader/Decoders/Block.cs @@ -1,33 +1,97 @@ +using System; using System.Collections.Generic; namespace Ryujinx.Graphics.Shader.Decoders { class Block { - public int Index { get; set; } - public ulong Address { get; set; } public ulong EndAddress { get; set; } public Block Next { get; set; } public Block Branch { get; set; } - public List OpCodes { get; } - + public List OpCodes { get; } public List SsyOpCodes { get; } public Block(ulong address) { Address = address; - OpCodes = new List(); - + OpCodes = new List(); SsyOpCodes = new List(); } + public void Split(Block rightBlock) + { + int splitIndex = BinarySearch(OpCodes, rightBlock.Address); + + if (OpCodes[splitIndex].Address < rightBlock.Address) + { + splitIndex++; + } + + int splitCount = OpCodes.Count - splitIndex; + + if (splitCount <= 0) + { + throw new ArgumentException("Can't split at right block address."); + } + + rightBlock.EndAddress = EndAddress; + + rightBlock.Next = Next; + rightBlock.Branch = Branch; + + rightBlock.OpCodes.AddRange(OpCodes.GetRange(splitIndex, splitCount)); + + rightBlock.UpdateSsyOpCodes(); + + EndAddress = rightBlock.Address; + + Next = rightBlock; + Branch = null; + + OpCodes.RemoveRange(splitIndex, splitCount); + + UpdateSsyOpCodes(); + } + + private static int BinarySearch(List opCodes, ulong address) + { + int left = 0; + int middle = 0; + int right = opCodes.Count - 1; + + while (left <= right) + { + int size = right - left; + + middle = left + (size >> 1); + + OpCode opCode = opCodes[middle]; + + if (address == opCode.Address) + { + break; + } + + if (address < opCode.Address) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return middle; + } + public OpCode GetLastOp() { - if (OpCodes.Count > 0) + if (OpCodes.Count != 0) { return OpCodes[OpCodes.Count - 1]; } diff --git a/Ryujinx.Graphics/Shader/Decoders/Decoder.cs b/Ryujinx.Graphics/Shader/Decoders/Decoder.cs index 40ef8179a6..86df3b203d 100644 --- a/Ryujinx.Graphics/Shader/Decoders/Decoder.cs +++ b/Ryujinx.Graphics/Shader/Decoders/Decoder.cs @@ -25,89 +25,108 @@ namespace Ryujinx.Graphics.Shader.Decoders public static Block[] Decode(IGalMemory memory, ulong address) { - Dictionary visited = new Dictionary(); - Dictionary visitedEnd = new Dictionary(); + List blocks = new List(); - Queue blocks = new Queue(); + Queue workQueue = new Queue(); - Block Enqueue(ulong addr) + Dictionary visited = new Dictionary(); + + Block GetBlock(ulong blkAddress) { - if (!visited.TryGetValue(addr, out Block output)) + if (!visited.TryGetValue(blkAddress, out Block block)) { - output = new Block(addr); + block = new Block(blkAddress); - blocks.Enqueue(output); + workQueue.Enqueue(block); - visited.Add(addr, output); + visited.Add(blkAddress, block); } - return output; + return block; } - ulong start = address + HeaderSize; + ulong startAddress = address + HeaderSize; - Block entry = Enqueue(start); + GetBlock(startAddress); - while (blocks.TryDequeue(out Block current)) + while (workQueue.TryDequeue(out Block currBlock)) { - FillBlock(memory, current, start); - - //Set child blocks. "Branch" is the block the branch instruction - //points to (when taken), "Next" is the block at the next address, - //executed when the branch is not taken. For Unconditional Branches - //or end of shader, Next is null. - if (current.OpCodes.Count > 0) + //Check if the current block is inside another block. + if (BinarySearch(blocks, currBlock.Address, out int nBlkIndex)) { - foreach (OpCodeSsy ssyOp in current.SsyOpCodes) + Block nBlock = blocks[nBlkIndex]; + + if (nBlock.Address == currBlock.Address) { - Enqueue(ssyOp.GetAbsoluteAddress()); + throw new InvalidOperationException("Found duplicate block address on the list."); } - OpCode lastOp = current.GetLastOp(); + nBlock.Split(currBlock); + + blocks.Insert(nBlkIndex + 1, currBlock); + + continue; + } + + //If we have a block after the current one, set the limit address. + ulong limitAddress = ulong.MaxValue; + + if (nBlkIndex != blocks.Count) + { + Block nBlock = blocks[nBlkIndex]; + + int nextIndex = nBlkIndex + 1; + + if (nBlock.Address < currBlock.Address && nextIndex < blocks.Count) + { + limitAddress = blocks[nextIndex].Address; + } + else if (nBlock.Address > currBlock.Address) + { + limitAddress = blocks[nBlkIndex].Address; + } + } + + FillBlock(memory, currBlock, limitAddress, startAddress); + + if (currBlock.OpCodes.Count != 0) + { + foreach (OpCodeSsy ssyOp in currBlock.SsyOpCodes) + { + GetBlock(ssyOp.GetAbsoluteAddress()); + } + + //Set child blocks. "Branch" is the block the branch instruction + //points to (when taken), "Next" is the block at the next address, + //executed when the branch is not taken. For Unconditional Branches + //or end of program, Next is null. + OpCode lastOp = currBlock.GetLastOp(); if (lastOp is OpCodeBranch op) { - current.Branch = Enqueue(op.GetAbsoluteAddress()); + currBlock.Branch = GetBlock(op.GetAbsoluteAddress()); } if (!IsUnconditionalBranch(lastOp)) { - current.Next = Enqueue(current.EndAddress); + currBlock.Next = GetBlock(currBlock.EndAddress); } } - //If we have on the graph two blocks with the same end address, - //then we need to split the bigger block and have two small blocks, - //the end address of the bigger "Current" block should then be == to - //the address of the "Smaller" block. - while (visitedEnd.TryGetValue(current.EndAddress, out Block smaller)) + //Insert the new block on the list (sorted by address). + if (blocks.Count != 0) { - if (current.Address > smaller.Address) - { - Block temp = smaller; + Block nBlock = blocks[nBlkIndex]; - smaller = current; - current = temp; - } - - current.EndAddress = smaller.Address; - current.Next = smaller; - current.Branch = null; - - current.OpCodes.RemoveRange( - current.OpCodes.Count - smaller.OpCodes.Count, - smaller.OpCodes.Count); - - current.UpdateSsyOpCodes(); - smaller.UpdateSsyOpCodes(); - - visitedEnd[smaller.EndAddress] = smaller; + blocks.Insert(nBlkIndex + (nBlock.Address < currBlock.Address ? 1 : 0), currBlock); + } + else + { + blocks.Add(currBlock); } - - visitedEnd.Add(current.EndAddress, current); } - foreach (Block ssyBlock in visited.Values.Where(x => x.SsyOpCodes.Count != 0)) + foreach (Block ssyBlock in blocks.Where(x => x.SsyOpCodes.Count != 0)) { for (int ssyIndex = 0; ssyIndex < ssyBlock.SsyOpCodes.Count; ssyIndex++) { @@ -115,157 +134,61 @@ namespace Ryujinx.Graphics.Shader.Decoders } } - Block[] cfg = new Block[visited.Count]; - - int index = 0; - - foreach (Block block in visited.Values.OrderBy(x => x.Address - entry.Address)) - { - block.Index = index; - - cfg[index++] = block; - } - - return cfg; + return blocks.ToArray(); } - private struct PathBlockState + private static bool BinarySearch(List blocks, ulong address, out int index) { - public Block Block { get; } + index = 0; - private enum RestoreType + int left = 0; + int right = blocks.Count - 1; + + while (left <= right) { - None, - PopSsy, - PushSync - } + int size = right - left; - private RestoreType _restoreType; + int middle = left + (size >> 1); - private ulong _restoreValue; + Block block = blocks[middle]; - public bool ReturningFromVisit => _restoreType != RestoreType.None; + index = middle; - public PathBlockState(Block block) - { - Block = block; - _restoreType = RestoreType.None; - _restoreValue = 0; - } - - public PathBlockState(int oldSsyStackSize) - { - Block = null; - _restoreType = RestoreType.PopSsy; - _restoreValue = (ulong)oldSsyStackSize; - } - - public PathBlockState(ulong syncAddress) - { - Block = null; - _restoreType = RestoreType.PushSync; - _restoreValue = syncAddress; - } - - public void RestoreStackState(Stack ssyStack) - { - if (_restoreType == RestoreType.PushSync) + if (address >= block.Address && address < block.EndAddress) { - ssyStack.Push(_restoreValue); + return true; } - else if (_restoreType == RestoreType.PopSsy) + + if (address < block.Address) { - while (ssyStack.Count > (uint)_restoreValue) - { - ssyStack.Pop(); - } + right = middle - 1; + } + else + { + left = middle + 1; } } + + return false; } - private static void PropagateSsy(Dictionary blocks, Block ssyBlock, int ssyIndex) - { - OpCodeSsy ssyOp = ssyBlock.SsyOpCodes[ssyIndex]; - - Stack pending = new Stack(); - - HashSet visited = new HashSet(); - - Stack ssyStack = new Stack(); - - void Push(PathBlockState pbs) - { - if (pbs.Block == null || visited.Add(pbs.Block)) - { - pending.Push(pbs); - } - } - - Push(new PathBlockState(ssyBlock)); - - while (pending.TryPop(out PathBlockState pbs)) - { - if (pbs.ReturningFromVisit) - { - pbs.RestoreStackState(ssyStack); - - continue; - } - - Block current = pbs.Block; - - int ssyOpCodesCount = current.SsyOpCodes.Count; - - if (ssyOpCodesCount != 0) - { - Push(new PathBlockState(ssyStack.Count)); - - for (int index = ssyIndex; index < ssyOpCodesCount; index++) - { - ssyStack.Push(current.SsyOpCodes[index].GetAbsoluteAddress()); - } - } - - ssyIndex = 0; - - if (current.Next != null) - { - Push(new PathBlockState(current.Next)); - } - - if (current.Branch != null) - { - Push(new PathBlockState(current.Branch)); - } - else if (current.GetLastOp() is OpCodeSync op) - { - ulong syncAddress = ssyStack.Pop(); - - if (ssyStack.Count == 0) - { - ssyStack.Push(syncAddress); - - op.Targets.Add(ssyOp, op.Targets.Count); - - ssyOp.Syncs.TryAdd(op, Local()); - } - else - { - Push(new PathBlockState(syncAddress)); - Push(new PathBlockState(blocks[syncAddress])); - } - } - } - } - - private static void FillBlock(IGalMemory memory, Block block, ulong start) + private static void FillBlock( + IGalMemory memory, + Block block, + ulong limitAddress, + ulong startAddress) { ulong address = block.Address; do { + if (address >= limitAddress) + { + break; + } + //Ignore scheduling instructions, which are written every 32 bytes. - if (((address - start) & 0x1f) == 0) + if (((address - startAddress) & 0x1f) == 0) { address += 8; @@ -350,5 +273,134 @@ namespace Ryujinx.Graphics.Shader.Decoders return (OpActivator)mthd.CreateDelegate(typeof(OpActivator)); } + + private struct PathBlockState + { + public Block Block { get; } + + private enum RestoreType + { + None, + PopSsy, + PushSync + } + + private RestoreType _restoreType; + + private ulong _restoreValue; + + public bool ReturningFromVisit => _restoreType != RestoreType.None; + + public PathBlockState(Block block) + { + Block = block; + _restoreType = RestoreType.None; + _restoreValue = 0; + } + + public PathBlockState(int oldSsyStackSize) + { + Block = null; + _restoreType = RestoreType.PopSsy; + _restoreValue = (ulong)oldSsyStackSize; + } + + public PathBlockState(ulong syncAddress) + { + Block = null; + _restoreType = RestoreType.PushSync; + _restoreValue = syncAddress; + } + + public void RestoreStackState(Stack ssyStack) + { + if (_restoreType == RestoreType.PushSync) + { + ssyStack.Push(_restoreValue); + } + else if (_restoreType == RestoreType.PopSsy) + { + while (ssyStack.Count > (uint)_restoreValue) + { + ssyStack.Pop(); + } + } + } + } + + private static void PropagateSsy(Dictionary blocks, Block ssyBlock, int ssyIndex) + { + OpCodeSsy ssyOp = ssyBlock.SsyOpCodes[ssyIndex]; + + Stack workQueue = new Stack(); + + HashSet visited = new HashSet(); + + Stack ssyStack = new Stack(); + + void Push(PathBlockState pbs) + { + if (pbs.Block == null || visited.Add(pbs.Block)) + { + workQueue.Push(pbs); + } + } + + Push(new PathBlockState(ssyBlock)); + + while (workQueue.TryPop(out PathBlockState pbs)) + { + if (pbs.ReturningFromVisit) + { + pbs.RestoreStackState(ssyStack); + + continue; + } + + Block current = pbs.Block; + + int ssyOpCodesCount = current.SsyOpCodes.Count; + + if (ssyOpCodesCount != 0) + { + Push(new PathBlockState(ssyStack.Count)); + + for (int index = ssyIndex; index < ssyOpCodesCount; index++) + { + ssyStack.Push(current.SsyOpCodes[index].GetAbsoluteAddress()); + } + } + + ssyIndex = 0; + + if (current.Next != null) + { + Push(new PathBlockState(current.Next)); + } + + if (current.Branch != null) + { + Push(new PathBlockState(current.Branch)); + } + else if (current.GetLastOp() is OpCodeSync op) + { + ulong syncAddress = ssyStack.Pop(); + + if (ssyStack.Count == 0) + { + ssyStack.Push(syncAddress); + + op.Targets.Add(ssyOp, op.Targets.Count); + + ssyOp.Syncs.TryAdd(op, Local()); + } + else + { + Push(new PathBlockState(syncAddress)); + Push(new PathBlockState(blocks[syncAddress])); + } + } + } + } } } \ No newline at end of file