d4187aaa9d
* (Re)Implement format reinterpretation, other changes * Implement writeback to guest memory, some refactoring * More refactoring, implement reinterpretation the old way again * Clean up * Some fixes on M2MF (old Dma engine), added partial support for P2MF, fix conditional ssy, add Z24S8 zeta format, other fixes * nit: Formatting * Address PR feedback
218 lines
No EOL
6.8 KiB
C#
218 lines
No EOL
6.8 KiB
C#
using System.Collections.Generic;
|
|
|
|
namespace Ryujinx.Graphics.Gal.Shader
|
|
{
|
|
static class ShaderDecoder
|
|
{
|
|
private const long HeaderSize = 0x50;
|
|
|
|
private const bool AddDbgComments = true;
|
|
|
|
public static ShaderIrBlock[] Decode(IGalMemory Memory, long Start)
|
|
{
|
|
Dictionary<int, ShaderIrBlock> Visited = new Dictionary<int, ShaderIrBlock>();
|
|
Dictionary<int, ShaderIrBlock> VisitedEnd = new Dictionary<int, ShaderIrBlock>();
|
|
|
|
Queue<ShaderIrBlock> Blocks = new Queue<ShaderIrBlock>();
|
|
|
|
long Beginning = Start + HeaderSize;
|
|
|
|
ShaderIrBlock Enqueue(int Position, ShaderIrBlock Source = null)
|
|
{
|
|
if (!Visited.TryGetValue(Position, out ShaderIrBlock Output))
|
|
{
|
|
Output = new ShaderIrBlock(Position);
|
|
|
|
Blocks.Enqueue(Output);
|
|
|
|
Visited.Add(Position, Output);
|
|
}
|
|
|
|
if (Source != null)
|
|
{
|
|
Output.Sources.Add(Source);
|
|
}
|
|
|
|
return Output;
|
|
}
|
|
|
|
ShaderIrBlock Entry = Enqueue(0);
|
|
|
|
while (Blocks.Count > 0)
|
|
{
|
|
ShaderIrBlock Current = Blocks.Dequeue();
|
|
|
|
FillBlock(Memory, Current, Beginning);
|
|
|
|
//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.Nodes.Count > 0)
|
|
{
|
|
ShaderIrNode LastNode = Current.GetLastNode();
|
|
|
|
ShaderIrOp InnerOp = GetInnermostOp(LastNode);
|
|
|
|
if (InnerOp?.Inst == ShaderIrInst.Bra)
|
|
{
|
|
int Target = ((ShaderIrOperImm)InnerOp.OperandA).Value;
|
|
|
|
Current.Branch = Enqueue(Target, Current);
|
|
}
|
|
|
|
foreach (ShaderIrNode Node in Current.Nodes)
|
|
{
|
|
InnerOp = GetInnermostOp(Node);
|
|
|
|
if (InnerOp is ShaderIrOp CurrOp && CurrOp.Inst == ShaderIrInst.Ssy)
|
|
{
|
|
int Target = ((ShaderIrOperImm)CurrOp.OperandA).Value;
|
|
|
|
Current.Branch = Enqueue(Target, Current);
|
|
}
|
|
}
|
|
|
|
if (NodeHasNext(LastNode))
|
|
{
|
|
Current.Next = Enqueue(Current.EndPosition);
|
|
}
|
|
}
|
|
|
|
//If we have on the graph two blocks with the same end position,
|
|
//then we need to split the bigger block and have two small blocks,
|
|
//the end position of the bigger "Current" block should then be == to
|
|
//the position of the "Smaller" block.
|
|
while (VisitedEnd.TryGetValue(Current.EndPosition, out ShaderIrBlock Smaller))
|
|
{
|
|
if (Current.Position > Smaller.Position)
|
|
{
|
|
ShaderIrBlock Temp = Smaller;
|
|
|
|
Smaller = Current;
|
|
Current = Temp;
|
|
}
|
|
|
|
Current.EndPosition = Smaller.Position;
|
|
Current.Next = Smaller;
|
|
Current.Branch = null;
|
|
|
|
Current.Nodes.RemoveRange(
|
|
Current.Nodes.Count - Smaller.Nodes.Count,
|
|
Smaller.Nodes.Count);
|
|
|
|
VisitedEnd[Smaller.EndPosition] = Smaller;
|
|
}
|
|
|
|
VisitedEnd.Add(Current.EndPosition, Current);
|
|
}
|
|
|
|
//Make and sort Graph blocks array by position.
|
|
ShaderIrBlock[] Graph = new ShaderIrBlock[Visited.Count];
|
|
|
|
while (Visited.Count > 0)
|
|
{
|
|
uint FirstPos = uint.MaxValue;
|
|
|
|
foreach (ShaderIrBlock Block in Visited.Values)
|
|
{
|
|
if (FirstPos > (uint)Block.Position)
|
|
FirstPos = (uint)Block.Position;
|
|
}
|
|
|
|
ShaderIrBlock Current = Visited[(int)FirstPos];
|
|
|
|
do
|
|
{
|
|
Graph[Graph.Length - Visited.Count] = Current;
|
|
|
|
Visited.Remove(Current.Position);
|
|
|
|
Current = Current.Next;
|
|
}
|
|
while (Current != null);
|
|
}
|
|
|
|
return Graph;
|
|
}
|
|
|
|
private static void FillBlock(IGalMemory Memory, ShaderIrBlock Block, long Beginning)
|
|
{
|
|
int Position = Block.Position;
|
|
|
|
do
|
|
{
|
|
//Ignore scheduling instructions, which are written every 32 bytes.
|
|
if ((Position & 0x1f) == 0)
|
|
{
|
|
Position += 8;
|
|
|
|
continue;
|
|
}
|
|
|
|
uint Word0 = (uint)Memory.ReadInt32(Position + Beginning + 0);
|
|
uint Word1 = (uint)Memory.ReadInt32(Position + Beginning + 4);
|
|
|
|
Position += 8;
|
|
|
|
long OpCode = Word0 | (long)Word1 << 32;
|
|
|
|
ShaderDecodeFunc Decode = ShaderOpCodeTable.GetDecoder(OpCode);
|
|
|
|
if (AddDbgComments)
|
|
{
|
|
string DbgOpCode = $"0x{(Position - 8):x16}: 0x{OpCode:x16} ";
|
|
|
|
DbgOpCode += (Decode?.Method.Name ?? "???");
|
|
|
|
if (Decode == ShaderDecode.Bra)
|
|
{
|
|
int Offset = ((int)(OpCode >> 20) << 8) >> 8;
|
|
|
|
long Target = Position + Offset;
|
|
|
|
DbgOpCode += " (0x" + Target.ToString("x16") + ")";
|
|
}
|
|
|
|
Block.AddNode(new ShaderIrCmnt(DbgOpCode));
|
|
}
|
|
|
|
if (Decode == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Decode(Block, OpCode, Position);
|
|
}
|
|
while (!IsFlowChange(Block.GetLastNode()));
|
|
|
|
Block.EndPosition = Position;
|
|
}
|
|
|
|
private static bool IsFlowChange(ShaderIrNode Node)
|
|
{
|
|
return !NodeHasNext(GetInnermostOp(Node));
|
|
}
|
|
|
|
private static ShaderIrOp GetInnermostOp(ShaderIrNode Node)
|
|
{
|
|
if (Node is ShaderIrCond Cond)
|
|
{
|
|
Node = Cond.Child;
|
|
}
|
|
|
|
return Node is ShaderIrOp Op ? Op : null;
|
|
}
|
|
|
|
private static bool NodeHasNext(ShaderIrNode Node)
|
|
{
|
|
if (!(Node is ShaderIrOp Op))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return Op.Inst != ShaderIrInst.Exit &&
|
|
Op.Inst != ShaderIrInst.Bra;
|
|
}
|
|
}
|
|
} |