aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Graphics.Shader/IntermediateRepresentation
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.Graphics.Shader/IntermediateRepresentation')
-rw-r--r--src/Ryujinx.Graphics.Shader/IntermediateRepresentation/BasicBlock.cs91
-rw-r--r--src/Ryujinx.Graphics.Shader/IntermediateRepresentation/CommentNode.cs12
-rw-r--r--src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Function.cs23
-rw-r--r--src/Ryujinx.Graphics.Shader/IntermediateRepresentation/INode.cs15
-rw-r--r--src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs178
-rw-r--r--src/Ryujinx.Graphics.Shader/IntermediateRepresentation/IoVariable.cs51
-rw-r--r--src/Ryujinx.Graphics.Shader/IntermediateRepresentation/IrConsts.cs8
-rw-r--r--src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operand.cs79
-rw-r--r--src/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandHelper.cs62
-rw-r--r--src/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandType.cs13
-rw-r--r--src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs257
-rw-r--r--src/Ryujinx.Graphics.Shader/IntermediateRepresentation/PhiNode.cs107
-rw-r--r--src/Ryujinx.Graphics.Shader/IntermediateRepresentation/StorageKind.cs39
-rw-r--r--src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureFlags.cs32
-rw-r--r--src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs69
15 files changed, 1036 insertions, 0 deletions
diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/BasicBlock.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/BasicBlock.cs
new file mode 100644
index 00000000..2aca118b
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/BasicBlock.cs
@@ -0,0 +1,91 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
+{
+ class BasicBlock
+ {
+ public int Index { get; set; }
+
+ public LinkedList<INode> Operations { get; }
+
+ private BasicBlock _next;
+ private BasicBlock _branch;
+
+ public BasicBlock Next
+ {
+ get => _next;
+ set => _next = AddSuccessor(_next, value);
+ }
+
+ public BasicBlock Branch
+ {
+ get => _branch;
+ set => _branch = AddSuccessor(_branch, value);
+ }
+
+ public bool HasBranch => _branch != null;
+ public bool Reachable => Index == 0 || Predecessors.Count != 0;
+
+ public List<BasicBlock> Predecessors { get; }
+
+ public HashSet<BasicBlock> DominanceFrontiers { get; }
+
+ public BasicBlock ImmediateDominator { get; set; }
+
+ public BasicBlock()
+ {
+ Operations = new LinkedList<INode>();
+
+ Predecessors = new List<BasicBlock>();
+
+ DominanceFrontiers = new HashSet<BasicBlock>();
+ }
+
+ public BasicBlock(int index) : this()
+ {
+ Index = index;
+ }
+
+ private BasicBlock AddSuccessor(BasicBlock oldBlock, BasicBlock newBlock)
+ {
+ oldBlock?.Predecessors.Remove(this);
+ newBlock?.Predecessors.Add(this);
+
+ return newBlock;
+ }
+
+ public INode GetLastOp()
+ {
+ return Operations.Last?.Value;
+ }
+
+ public void Append(INode node)
+ {
+ INode lastOp = GetLastOp();
+
+ if (lastOp is Operation operation && IsControlFlowInst(operation.Inst))
+ {
+ Operations.AddBefore(Operations.Last, node);
+ }
+ else
+ {
+ Operations.AddLast(node);
+ }
+ }
+
+ private static bool IsControlFlowInst(Instruction inst)
+ {
+ switch (inst)
+ {
+ case Instruction.Branch:
+ case Instruction.BranchIfFalse:
+ case Instruction.BranchIfTrue:
+ case Instruction.Discard:
+ case Instruction.Return:
+ return true;
+ }
+
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/CommentNode.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/CommentNode.cs
new file mode 100644
index 00000000..d4d87b06
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/CommentNode.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
+{
+ class CommentNode : Operation
+ {
+ public string Comment { get; }
+
+ public CommentNode(string comment) : base(Instruction.Comment, null)
+ {
+ Comment = comment;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Function.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Function.cs
new file mode 100644
index 00000000..e535c3fc
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Function.cs
@@ -0,0 +1,23 @@
+namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
+{
+ class Function
+ {
+ public BasicBlock[] Blocks { get; }
+
+ public string Name { get; }
+
+ public bool ReturnsValue { get; }
+
+ public int InArgumentsCount { get; }
+ public int OutArgumentsCount { get; }
+
+ public Function(BasicBlock[] blocks, string name, bool returnsValue, int inArgumentsCount, int outArgumentsCount)
+ {
+ Blocks = blocks;
+ Name = name;
+ ReturnsValue = returnsValue;
+ InArgumentsCount = inArgumentsCount;
+ OutArgumentsCount = outArgumentsCount;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/INode.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/INode.cs
new file mode 100644
index 00000000..0f545e56
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/INode.cs
@@ -0,0 +1,15 @@
+namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
+{
+ interface INode
+ {
+ Operand Dest { get; set; }
+
+ int DestsCount { get; }
+ int SourcesCount { get; }
+
+ Operand GetDest(int index);
+ Operand GetSource(int index);
+
+ void SetSource(int index, Operand operand);
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs
new file mode 100644
index 00000000..d7c4a961
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs
@@ -0,0 +1,178 @@
+using System;
+
+namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
+{
+ [Flags]
+ enum Instruction
+ {
+ Absolute = 1,
+ Add,
+ AtomicAdd,
+ AtomicAnd,
+ AtomicCompareAndSwap,
+ AtomicMinS32,
+ AtomicMinU32,
+ AtomicMaxS32,
+ AtomicMaxU32,
+ AtomicOr,
+ AtomicSwap,
+ AtomicXor,
+ Ballot,
+ Barrier,
+ BitCount,
+ BitfieldExtractS32,
+ BitfieldExtractU32,
+ BitfieldInsert,
+ BitfieldReverse,
+ BitwiseAnd,
+ BitwiseExclusiveOr,
+ BitwiseNot,
+ BitwiseOr,
+ Branch,
+ BranchIfFalse,
+ BranchIfTrue,
+ Call,
+ Ceiling,
+ Clamp,
+ ClampU32,
+ Comment,
+ CompareEqual,
+ CompareGreater,
+ CompareGreaterOrEqual,
+ CompareGreaterOrEqualU32,
+ CompareGreaterU32,
+ CompareLess,
+ CompareLessOrEqual,
+ CompareLessOrEqualU32,
+ CompareLessU32,
+ CompareNotEqual,
+ ConditionalSelect,
+ ConvertFP32ToFP64,
+ ConvertFP64ToFP32,
+ ConvertFP32ToS32,
+ ConvertFP32ToU32,
+ ConvertFP64ToS32,
+ ConvertFP64ToU32,
+ ConvertS32ToFP32,
+ ConvertS32ToFP64,
+ ConvertU32ToFP32,
+ ConvertU32ToFP64,
+ Copy,
+ Cosine,
+ Ddx,
+ Ddy,
+ Discard,
+ Divide,
+ EmitVertex,
+ EndPrimitive,
+ ExponentB2,
+ FSIBegin,
+ FSIEnd,
+ FindLSB,
+ FindMSBS32,
+ FindMSBU32,
+ Floor,
+ FusedMultiplyAdd,
+ GroupMemoryBarrier,
+ ImageLoad,
+ ImageStore,
+ ImageAtomic,
+ IsNan,
+ Load,
+ LoadConstant,
+ LoadGlobal,
+ LoadLocal,
+ LoadShared,
+ LoadStorage,
+ Lod,
+ LogarithmB2,
+ LogicalAnd,
+ LogicalExclusiveOr,
+ LogicalNot,
+ LogicalOr,
+ LoopBreak,
+ LoopContinue,
+ MarkLabel,
+ Maximum,
+ MaximumU32,
+ MemoryBarrier,
+ Minimum,
+ MinimumU32,
+ Multiply,
+ MultiplyHighS32,
+ MultiplyHighU32,
+ Negate,
+ PackDouble2x32,
+ PackHalf2x16,
+ ReciprocalSquareRoot,
+ Return,
+ Round,
+ ShiftLeft,
+ ShiftRightS32,
+ ShiftRightU32,
+ Shuffle,
+ ShuffleDown,
+ ShuffleUp,
+ ShuffleXor,
+ Sine,
+ SquareRoot,
+ Store,
+ StoreGlobal,
+ StoreGlobal16,
+ StoreGlobal8,
+ StoreLocal,
+ StoreShared,
+ StoreShared16,
+ StoreShared8,
+ StoreStorage,
+ StoreStorage16,
+ StoreStorage8,
+ Subtract,
+ SwizzleAdd,
+ TextureSample,
+ TextureSize,
+ Truncate,
+ UnpackDouble2x32,
+ UnpackHalf2x16,
+ VectorExtract,
+ VoteAll,
+ VoteAllEqual,
+ VoteAny,
+
+ Count,
+
+ FP32 = 1 << 16,
+ FP64 = 1 << 17,
+
+ Mask = 0xffff
+ }
+
+ static class InstructionExtensions
+ {
+ public static bool IsAtomic(this Instruction inst)
+ {
+ switch (inst & Instruction.Mask)
+ {
+ case Instruction.AtomicAdd:
+ case Instruction.AtomicAnd:
+ case Instruction.AtomicCompareAndSwap:
+ case Instruction.AtomicMaxS32:
+ case Instruction.AtomicMaxU32:
+ case Instruction.AtomicMinS32:
+ case Instruction.AtomicMinU32:
+ case Instruction.AtomicOr:
+ case Instruction.AtomicSwap:
+ case Instruction.AtomicXor:
+ return true;
+ }
+
+ return false;
+ }
+
+ public static bool IsTextureQuery(this Instruction inst)
+ {
+ inst &= Instruction.Mask;
+ return inst == Instruction.Lod || inst == Instruction.TextureSize;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/IoVariable.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/IoVariable.cs
new file mode 100644
index 00000000..a2163d14
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/IoVariable.cs
@@ -0,0 +1,51 @@
+namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
+{
+ enum IoVariable
+ {
+ Invalid,
+
+ BackColorDiffuse,
+ BackColorSpecular,
+ BaseInstance,
+ BaseVertex,
+ ClipDistance,
+ CtaId,
+ DrawIndex,
+ FogCoord,
+ FragmentCoord,
+ FragmentOutputColor,
+ FragmentOutputDepth,
+ FragmentOutputIsBgra, // TODO: Remove and use constant buffer access.
+ FrontColorDiffuse,
+ FrontColorSpecular,
+ FrontFacing,
+ InstanceId,
+ InstanceIndex,
+ InvocationId,
+ Layer,
+ PatchVertices,
+ PointCoord,
+ PointSize,
+ Position,
+ PrimitiveId,
+ SubgroupEqMask,
+ SubgroupGeMask,
+ SubgroupGtMask,
+ SubgroupLaneId,
+ SubgroupLeMask,
+ SubgroupLtMask,
+ SupportBlockViewInverse, // TODO: Remove and use constant buffer access.
+ SupportBlockRenderScale, // TODO: Remove and use constant buffer access.
+ TessellationCoord,
+ TessellationLevelInner,
+ TessellationLevelOuter,
+ TextureCoord,
+ ThreadId,
+ ThreadKill,
+ UserDefined,
+ VertexId,
+ VertexIndex,
+ ViewportIndex,
+ ViewportMask
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/IrConsts.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/IrConsts.cs
new file mode 100644
index 00000000..c264e47d
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/IrConsts.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
+{
+ static class IrConsts
+ {
+ public const int False = 0;
+ public const int True = -1;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operand.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operand.cs
new file mode 100644
index 00000000..1df88a3d
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operand.cs
@@ -0,0 +1,79 @@
+using Ryujinx.Graphics.Shader.Decoders;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
+{
+ class Operand
+ {
+ private const int CbufSlotBits = 5;
+ private const int CbufSlotLsb = 32 - CbufSlotBits;
+ private const int CbufSlotMask = (1 << CbufSlotBits) - 1;
+
+ public OperandType Type { get; }
+
+ public int Value { get; }
+
+ public INode AsgOp { get; set; }
+
+ public HashSet<INode> UseOps { get; }
+
+ private Operand()
+ {
+ UseOps = new HashSet<INode>();
+ }
+
+ public Operand(OperandType type) : this()
+ {
+ Type = type;
+ }
+
+ public Operand(OperandType type, int value) : this()
+ {
+ Type = type;
+ Value = value;
+ }
+
+ public Operand(Register reg) : this()
+ {
+ Type = OperandType.Register;
+ Value = PackRegInfo(reg.Index, reg.Type);
+ }
+
+ public Operand(int slot, int offset) : this()
+ {
+ Type = OperandType.ConstantBuffer;
+ Value = PackCbufInfo(slot, offset);
+ }
+
+ private static int PackCbufInfo(int slot, int offset)
+ {
+ return (slot << CbufSlotLsb) | offset;
+ }
+
+ private static int PackRegInfo(int index, RegisterType type)
+ {
+ return ((int)type << 24) | index;
+ }
+
+ public int GetCbufSlot()
+ {
+ return (Value >> CbufSlotLsb) & CbufSlotMask;
+ }
+
+ public int GetCbufOffset()
+ {
+ return Value & ~(CbufSlotMask << CbufSlotLsb);
+ }
+
+ public Register GetRegister()
+ {
+ return new Register(Value & 0xffffff, (RegisterType)(Value >> 24));
+ }
+
+ public float AsFloat()
+ {
+ return BitConverter.Int32BitsToSingle(Value);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandHelper.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandHelper.cs
new file mode 100644
index 00000000..37c349e8
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandHelper.cs
@@ -0,0 +1,62 @@
+using Ryujinx.Graphics.Shader.Decoders;
+using System;
+
+namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
+{
+ static class OperandHelper
+ {
+ public static Operand Argument(int value)
+ {
+ return new Operand(OperandType.Argument, value);
+ }
+
+ public static Operand Cbuf(int slot, int offset)
+ {
+ return new Operand(slot, offset);
+ }
+
+ public static Operand Const(int value)
+ {
+ return new Operand(OperandType.Constant, value);
+ }
+
+ public static Operand ConstF(float value)
+ {
+ return new Operand(OperandType.Constant, BitConverter.SingleToInt32Bits(value));
+ }
+
+ public static Operand Label()
+ {
+ return new Operand(OperandType.Label);
+ }
+
+ public static Operand Local()
+ {
+ return new Operand(OperandType.LocalVariable);
+ }
+
+ public static Operand Register(int index, RegisterType type)
+ {
+ return Register(new Register(index, type));
+ }
+
+ public static Operand Register(Register reg)
+ {
+ if (reg.IsRZ)
+ {
+ return Const(0);
+ }
+ else if (reg.IsPT)
+ {
+ return Const(IrConsts.True);
+ }
+
+ return new Operand(reg);
+ }
+
+ public static Operand Undef()
+ {
+ return new Operand(OperandType.Undefined);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandType.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandType.cs
new file mode 100644
index 00000000..4d2da734
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandType.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
+{
+ enum OperandType
+ {
+ Argument,
+ Constant,
+ ConstantBuffer,
+ Label,
+ LocalVariable,
+ Register,
+ Undefined
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs
new file mode 100644
index 00000000..99179f15
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs
@@ -0,0 +1,257 @@
+using System;
+using System.Diagnostics;
+
+namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
+{
+ class Operation : INode
+ {
+ public Instruction Inst { get; private set; }
+ public StorageKind StorageKind { get; }
+
+ private Operand[] _dests;
+
+ public Operand Dest
+ {
+ get
+ {
+ return _dests.Length != 0 ? _dests[0] : null;
+ }
+ set
+ {
+ if (value != null && value.Type == OperandType.LocalVariable)
+ {
+ value.AsgOp = this;
+ }
+
+ if (value != null)
+ {
+ _dests = new[] { value };
+ }
+ else
+ {
+ _dests = Array.Empty<Operand>();
+ }
+ }
+ }
+
+ public int DestsCount => _dests.Length;
+
+ private Operand[] _sources;
+
+ public int SourcesCount => _sources.Length;
+
+ public int Index { get; }
+
+ private Operation(Operand[] sources)
+ {
+ // The array may be modified externally, so we store a copy.
+ _sources = (Operand[])sources.Clone();
+
+ for (int index = 0; index < _sources.Length; index++)
+ {
+ Operand source = _sources[index];
+
+ if (source.Type == OperandType.LocalVariable)
+ {
+ source.UseOps.Add(this);
+ }
+ }
+ }
+
+ public Operation(Instruction inst, int index, Operand[] dests, Operand[] sources) : this(sources)
+ {
+ Inst = inst;
+ Index = index;
+
+ if (dests != null)
+ {
+ // The array may be modified externally, so we store a copy.
+ _dests = (Operand[])dests.Clone();
+
+ for (int dstIndex = 0; dstIndex < dests.Length; dstIndex++)
+ {
+ Operand dest = dests[dstIndex];
+
+ if (dest != null && dest.Type == OperandType.LocalVariable)
+ {
+ dest.AsgOp = this;
+ }
+ }
+ }
+ else
+ {
+ _dests = Array.Empty<Operand>();
+ }
+ }
+
+ public Operation(Instruction inst, Operand dest, params Operand[] sources) : this(sources)
+ {
+ Inst = inst;
+
+ if (dest != null)
+ {
+ dest.AsgOp = this;
+
+ _dests = new[] { dest };
+ }
+ else
+ {
+ _dests = Array.Empty<Operand>();
+ }
+ }
+
+ public Operation(Instruction inst, StorageKind storageKind, Operand dest, params Operand[] sources) : this(sources)
+ {
+ Inst = inst;
+ StorageKind = storageKind;
+
+ if (dest != null)
+ {
+ dest.AsgOp = this;
+
+ _dests = new[] { dest };
+ }
+ else
+ {
+ _dests = Array.Empty<Operand>();
+ }
+ }
+
+ public Operation(Instruction inst, int index, Operand dest, params Operand[] sources) : this(inst, dest, sources)
+ {
+ Index = index;
+ }
+
+ public void AppendDests(Operand[] operands)
+ {
+ int startIndex = _dests.Length;
+
+ Array.Resize(ref _dests, startIndex + operands.Length);
+
+ for (int index = 0; index < operands.Length; index++)
+ {
+ Operand dest = operands[index];
+
+ if (dest != null && dest.Type == OperandType.LocalVariable)
+ {
+ Debug.Assert(dest.AsgOp == null);
+ dest.AsgOp = this;
+ }
+
+ _dests[startIndex + index] = dest;
+ }
+ }
+
+ public void AppendSources(Operand[] operands)
+ {
+ int startIndex = _sources.Length;
+
+ Array.Resize(ref _sources, startIndex + operands.Length);
+
+ for (int index = 0; index < operands.Length; index++)
+ {
+ Operand source = operands[index];
+
+ if (source.Type == OperandType.LocalVariable)
+ {
+ source.UseOps.Add(this);
+ }
+
+ _sources[startIndex + index] = source;
+ }
+ }
+
+ public Operand GetDest(int index)
+ {
+ return _dests[index];
+ }
+
+ public Operand GetSource(int index)
+ {
+ return _sources[index];
+ }
+
+ public void SetDest(int index, Operand dest)
+ {
+ Operand oldDest = _dests[index];
+
+ if (oldDest != null && oldDest.Type == OperandType.LocalVariable)
+ {
+ oldDest.AsgOp = null;
+ }
+
+ if (dest != null && dest.Type == OperandType.LocalVariable)
+ {
+ dest.AsgOp = this;
+ }
+
+ _dests[index] = dest;
+ }
+
+ public void SetSource(int index, Operand source)
+ {
+ Operand oldSrc = _sources[index];
+
+ if (oldSrc != null && oldSrc.Type == OperandType.LocalVariable)
+ {
+ oldSrc.UseOps.Remove(this);
+ }
+
+ if (source != null && source.Type == OperandType.LocalVariable)
+ {
+ source.UseOps.Add(this);
+ }
+
+ _sources[index] = source;
+ }
+
+ public void InsertSource(int index, Operand source)
+ {
+ Operand[] newSources = new Operand[_sources.Length + 1];
+
+ Array.Copy(_sources, 0, newSources, 0, index);
+ Array.Copy(_sources, index, newSources, index + 1, _sources.Length - index);
+
+ newSources[index] = source;
+
+ _sources = newSources;
+ }
+
+ protected void RemoveSource(int index)
+ {
+ SetSource(index, null);
+
+ Operand[] newSources = new Operand[_sources.Length - 1];
+
+ Array.Copy(_sources, 0, newSources, 0, index);
+ Array.Copy(_sources, index + 1, newSources, index, _sources.Length - (index + 1));
+
+ _sources = newSources;
+ }
+
+ public void TurnIntoCopy(Operand source)
+ {
+ TurnInto(Instruction.Copy, source);
+ }
+
+ public void TurnInto(Instruction newInst, Operand source)
+ {
+ Inst = newInst;
+
+ foreach (Operand oldSrc in _sources)
+ {
+ if (oldSrc != null && oldSrc.Type == OperandType.LocalVariable)
+ {
+ oldSrc.UseOps.Remove(this);
+ }
+ }
+
+ if (source.Type == OperandType.LocalVariable)
+ {
+ source.UseOps.Add(this);
+ }
+
+ _sources = new Operand[] { source };
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/PhiNode.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/PhiNode.cs
new file mode 100644
index 00000000..8fa25ae9
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/PhiNode.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
+{
+ class PhiNode : INode
+ {
+ private Operand _dest;
+
+ public Operand Dest
+ {
+ get => _dest;
+ set => _dest = AssignDest(value);
+ }
+
+ public int DestsCount => _dest != null ? 1 : 0;
+
+ private HashSet<BasicBlock> _blocks;
+
+ private class PhiSource
+ {
+ public BasicBlock Block { get; }
+ public Operand Operand { get; set; }
+
+ public PhiSource(BasicBlock block, Operand operand)
+ {
+ Block = block;
+ Operand = operand;
+ }
+ }
+
+ private List<PhiSource> _sources;
+
+ public int SourcesCount => _sources.Count;
+
+ public PhiNode(Operand dest)
+ {
+ _blocks = new HashSet<BasicBlock>();
+
+ _sources = new List<PhiSource>();
+
+ dest.AsgOp = this;
+
+ Dest = dest;
+ }
+
+ private Operand AssignDest(Operand dest)
+ {
+ if (dest != null && dest.Type == OperandType.LocalVariable)
+ {
+ dest.AsgOp = this;
+ }
+
+ return dest;
+ }
+
+ public void AddSource(BasicBlock block, Operand operand)
+ {
+ if (_blocks.Add(block))
+ {
+ if (operand.Type == OperandType.LocalVariable)
+ {
+ operand.UseOps.Add(this);
+ }
+
+ _sources.Add(new PhiSource(block, operand));
+ }
+ }
+
+ public Operand GetDest(int index)
+ {
+ if (index != 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+
+ return _dest;
+ }
+
+ public Operand GetSource(int index)
+ {
+ return _sources[index].Operand;
+ }
+
+ public BasicBlock GetBlock(int index)
+ {
+ return _sources[index].Block;
+ }
+
+ public void SetSource(int index, Operand source)
+ {
+ Operand oldSrc = _sources[index].Operand;
+
+ if (oldSrc != null && oldSrc.Type == OperandType.LocalVariable)
+ {
+ oldSrc.UseOps.Remove(this);
+ }
+
+ if (source.Type == OperandType.LocalVariable)
+ {
+ source.UseOps.Add(this);
+ }
+
+ _sources[index].Operand = source;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/StorageKind.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/StorageKind.cs
new file mode 100644
index 00000000..59357443
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/StorageKind.cs
@@ -0,0 +1,39 @@
+namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
+{
+ enum StorageKind
+ {
+ None,
+ Input,
+ InputPerPatch,
+ Output,
+ OutputPerPatch,
+ ConstantBuffer,
+ StorageBuffer,
+ LocalMemory,
+ SharedMemory,
+ GlobalMemory
+ }
+
+ static class StorageKindExtensions
+ {
+ public static bool IsInputOrOutput(this StorageKind storageKind)
+ {
+ return storageKind == StorageKind.Input ||
+ storageKind == StorageKind.InputPerPatch ||
+ storageKind == StorageKind.Output ||
+ storageKind == StorageKind.OutputPerPatch;
+ }
+
+ public static bool IsOutput(this StorageKind storageKind)
+ {
+ return storageKind == StorageKind.Output ||
+ storageKind == StorageKind.OutputPerPatch;
+ }
+
+ public static bool IsPerPatch(this StorageKind storageKind)
+ {
+ return storageKind == StorageKind.InputPerPatch ||
+ storageKind == StorageKind.OutputPerPatch;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureFlags.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureFlags.cs
new file mode 100644
index 00000000..6c20e856
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureFlags.cs
@@ -0,0 +1,32 @@
+using System;
+
+namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
+{
+ [Flags]
+ enum TextureFlags
+ {
+ None = 0,
+ Bindless = 1 << 0,
+ Gather = 1 << 1,
+ Derivatives = 1 << 2,
+ IntCoords = 1 << 3,
+ LodBias = 1 << 4,
+ LodLevel = 1 << 5,
+ Offset = 1 << 6,
+ Offsets = 1 << 7,
+ Coherent = 1 << 8,
+
+ AtomicMask = 15 << 16,
+
+ Add = 0 << 16,
+ Minimum = 1 << 16,
+ Maximum = 2 << 16,
+ Increment = 3 << 16,
+ Decrement = 4 << 16,
+ BitwiseAnd = 5 << 16,
+ BitwiseOr = 6 << 16,
+ BitwiseXor = 7 << 16,
+ Swap = 8 << 16,
+ CAS = 9 << 16
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs
new file mode 100644
index 00000000..6ab868cd
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs
@@ -0,0 +1,69 @@
+namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
+{
+ class TextureOperation : Operation
+ {
+ public const int DefaultCbufSlot = -1;
+
+ public SamplerType Type { get; set; }
+ public TextureFormat Format { get; set; }
+ public TextureFlags Flags { get; private set; }
+
+ public int CbufSlot { get; private set; }
+ public int Handle { get; private set; }
+
+ public TextureOperation(
+ Instruction inst,
+ SamplerType type,
+ TextureFormat format,
+ TextureFlags flags,
+ int cbufSlot,
+ int handle,
+ int compIndex,
+ Operand[] dests,
+ Operand[] sources) : base(inst, compIndex, dests, sources)
+ {
+ Type = type;
+ Format = format;
+ Flags = flags;
+ CbufSlot = cbufSlot;
+ Handle = handle;
+ }
+
+ public TextureOperation(
+ Instruction inst,
+ SamplerType type,
+ TextureFormat format,
+ TextureFlags flags,
+ int handle,
+ int compIndex,
+ Operand[] dests,
+ Operand[] sources) : this(inst, type, format, flags, DefaultCbufSlot, handle, compIndex, dests, sources)
+ {
+ }
+
+ public void TurnIntoIndexed(int handle)
+ {
+ Type |= SamplerType.Indexed;
+ Flags &= ~TextureFlags.Bindless;
+ Handle = handle;
+ }
+
+ public void SetHandle(int handle, int cbufSlot = DefaultCbufSlot)
+ {
+ if ((Flags & TextureFlags.Bindless) != 0)
+ {
+ Flags &= ~TextureFlags.Bindless;
+
+ RemoveSource(0);
+ }
+
+ CbufSlot = cbufSlot;
+ Handle = handle;
+ }
+
+ public void SetLodLevelFlag()
+ {
+ Flags |= TextureFlags.LodLevel;
+ }
+ }
+} \ No newline at end of file