aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Graphics/Shader/Translation
diff options
context:
space:
mode:
authorgdkchan <gab.dark.100@gmail.com>2019-04-17 20:57:08 -0300
committerjduncanator <1518948+jduncanator@users.noreply.github.com>2019-04-18 09:57:08 +1000
commit6b23a2c125b9c48b5ebea92716004ef68698bb0f (patch)
tree69332df6fbbd8e2bddc522ba682fcc5c7a69e101 /Ryujinx.Graphics/Shader/Translation
parentb2e88b04a85b41cc60af3485d88c90429e84a218 (diff)
New shader translator implementation (#654)
* Start implementing a new shader translator * Fix shift instructions and a typo * Small refactoring on StructuredProgram, move RemovePhis method to a separate class * Initial geometry shader support * Implement TLD4 * Fix -- There's no negation on FMUL32I * Add constant folding and algebraic simplification optimizations, nits * Some leftovers from constant folding * Avoid cast for constant assignments * Add a branch elimination pass, and misc small fixes * Remove redundant branches, add expression propagation and other improvements on the code * Small leftovers -- add missing break and continue, remove unused properties, other improvements * Add null check to handle empty block cases on block visitor * Add HADD2 and HMUL2 half float shader instructions * Optimize pack/unpack sequences, some fixes related to half float instructions * Add TXQ, TLD, TLDS and TLD4S shader texture instructions, and some support for bindless textures, some refactoring on codegen * Fix copy paste mistake that caused RZ to be ignored on the AST instruction * Add workaround for conditional exit, and fix half float instruction with constant buffer * Add missing 0.0 source for TLDS.LZ variants * Simplify the switch for TLDS.LZ * Texture instructions related fixes * Implement the HFMA instruction, and some misc. fixes * Enable constant folding on UnpackHalf2x16 instructions * Refactor HFMA to use OpCode* for opcode decoding rather than on the helper methods * Remove the old shader translator * Remove ShaderDeclInfo and other unused things * Add dual vertex shader support * Add ShaderConfig, used to pass shader type and maximum cbuffer size * Move and rename some instruction enums * Move texture instructions into a separate file * Move operand GetExpression and locals management to OperandManager * Optimize opcode decoding using a simple list and binary search * Add missing condition for do-while on goto elimination * Misc. fixes on texture instructions * Simplify TLDS switch * Address PR feedback, and a nit
Diffstat (limited to 'Ryujinx.Graphics/Shader/Translation')
-rw-r--r--Ryujinx.Graphics/Shader/Translation/AttributeConsts.cs30
-rw-r--r--Ryujinx.Graphics/Shader/Translation/ControlFlowGraph.cs108
-rw-r--r--Ryujinx.Graphics/Shader/Translation/Dominance.cs127
-rw-r--r--Ryujinx.Graphics/Shader/Translation/EmitterContext.cs105
-rw-r--r--Ryujinx.Graphics/Shader/Translation/EmitterContextInsts.cs420
-rw-r--r--Ryujinx.Graphics/Shader/Translation/Optimizations/BranchElimination.cs64
-rw-r--r--Ryujinx.Graphics/Shader/Translation/Optimizations/ConstantFolding.cs323
-rw-r--r--Ryujinx.Graphics/Shader/Translation/Optimizations/HalfConversion.cs47
-rw-r--r--Ryujinx.Graphics/Shader/Translation/Optimizations/Optimizer.cs172
-rw-r--r--Ryujinx.Graphics/Shader/Translation/Optimizations/Simplification.cs147
-rw-r--r--Ryujinx.Graphics/Shader/Translation/Ssa.cs330
-rw-r--r--Ryujinx.Graphics/Shader/Translation/Translator.cs219
12 files changed, 2092 insertions, 0 deletions
diff --git a/Ryujinx.Graphics/Shader/Translation/AttributeConsts.cs b/Ryujinx.Graphics/Shader/Translation/AttributeConsts.cs
new file mode 100644
index 00000000..ae3e361c
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Translation/AttributeConsts.cs
@@ -0,0 +1,30 @@
+namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
+{
+ static class AttributeConsts
+ {
+ public const int Layer = 0x064;
+ public const int PointSize = 0x06c;
+ public const int PositionX = 0x070;
+ public const int PositionY = 0x074;
+ public const int PositionZ = 0x078;
+ public const int PositionW = 0x07c;
+ public const int PointCoordX = 0x2e0;
+ public const int PointCoordY = 0x2e4;
+ public const int TessCoordX = 0x2f0;
+ public const int TessCoordY = 0x2f4;
+ public const int InstanceId = 0x2f8;
+ public const int VertexId = 0x2fc;
+ public const int FrontFacing = 0x3fc;
+
+ public const int UserAttributesCount = 32;
+ public const int UserAttributeBase = 0x80;
+ public const int UserAttributeEnd = UserAttributeBase + UserAttributesCount * 16;
+
+
+ //Note: Those attributes are used internally by the translator
+ //only, they don't exist on Maxwell.
+ public const int FragmentOutputDepth = 0x1000000;
+ public const int FragmentOutputColorBase = 0x1000010;
+ public const int FragmentOutputColorEnd = FragmentOutputColorBase + 8 * 16;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Translation/ControlFlowGraph.cs b/Ryujinx.Graphics/Shader/Translation/ControlFlowGraph.cs
new file mode 100644
index 00000000..e2ca74a4
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Translation/ControlFlowGraph.cs
@@ -0,0 +1,108 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Shader.Translation
+{
+ static class ControlFlowGraph
+ {
+ public static BasicBlock[] MakeCfg(Operation[] operations)
+ {
+ Dictionary<Operand, BasicBlock> labels = new Dictionary<Operand, BasicBlock>();
+
+ List<BasicBlock> blocks = new List<BasicBlock>();
+
+ BasicBlock currentBlock = null;
+
+ void NextBlock(BasicBlock nextBlock)
+ {
+ if (currentBlock != null && !EndsWithUnconditionalInst(currentBlock.GetLastOp()))
+ {
+ currentBlock.Next = nextBlock;
+ }
+
+ currentBlock = nextBlock;
+ }
+
+ void NewNextBlock()
+ {
+ BasicBlock block = new BasicBlock(blocks.Count);
+
+ blocks.Add(block);
+
+ NextBlock(block);
+ }
+
+ bool needsNewBlock = true;
+
+ for (int index = 0; index < operations.Length; index++)
+ {
+ Operation operation = operations[index];
+
+ if (operation.Inst == Instruction.MarkLabel)
+ {
+ Operand label = operation.Dest;
+
+ if (labels.TryGetValue(label, out BasicBlock nextBlock))
+ {
+ nextBlock.Index = blocks.Count;
+
+ blocks.Add(nextBlock);
+
+ NextBlock(nextBlock);
+ }
+ else
+ {
+ NewNextBlock();
+
+ labels.Add(label, currentBlock);
+ }
+ }
+ else
+ {
+ if (needsNewBlock)
+ {
+ NewNextBlock();
+ }
+
+ currentBlock.Operations.AddLast(operation);
+ }
+
+ needsNewBlock = operation.Inst == Instruction.Branch ||
+ operation.Inst == Instruction.BranchIfTrue ||
+ operation.Inst == Instruction.BranchIfFalse;
+
+ if (needsNewBlock)
+ {
+ Operand label = operation.Dest;
+
+ if (!labels.TryGetValue(label, out BasicBlock branchBlock))
+ {
+ branchBlock = new BasicBlock();
+
+ labels.Add(label, branchBlock);
+ }
+
+ currentBlock.Branch = branchBlock;
+ }
+ }
+
+ return blocks.ToArray();
+ }
+
+ private static bool EndsWithUnconditionalInst(INode node)
+ {
+ if (node is Operation operation)
+ {
+ switch (operation.Inst)
+ {
+ case Instruction.Branch:
+ case Instruction.Discard:
+ case Instruction.Return:
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Translation/Dominance.cs b/Ryujinx.Graphics/Shader/Translation/Dominance.cs
new file mode 100644
index 00000000..b4b80e3e
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Translation/Dominance.cs
@@ -0,0 +1,127 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Shader.Translation
+{
+ static class Dominance
+ {
+ //Those methods are an implementation of the algorithms on "A Simple, Fast Dominance Algorithm".
+ //https://www.cs.rice.edu/~keith/EMBED/dom.pdf
+ public static void FindDominators(BasicBlock entry, int blocksCount)
+ {
+ HashSet<BasicBlock> visited = new HashSet<BasicBlock>();
+
+ Stack<BasicBlock> blockStack = new Stack<BasicBlock>();
+
+ List<BasicBlock> postOrderBlocks = new List<BasicBlock>(blocksCount);
+
+ int[] postOrderMap = new int[blocksCount];
+
+ visited.Add(entry);
+
+ blockStack.Push(entry);
+
+ while (blockStack.TryPop(out BasicBlock block))
+ {
+ if (block.Next != null && visited.Add(block.Next))
+ {
+ blockStack.Push(block);
+ blockStack.Push(block.Next);
+ }
+ else if (block.Branch != null && visited.Add(block.Branch))
+ {
+ blockStack.Push(block);
+ blockStack.Push(block.Branch);
+ }
+ else
+ {
+ postOrderMap[block.Index] = postOrderBlocks.Count;
+
+ postOrderBlocks.Add(block);
+ }
+ }
+
+ BasicBlock Intersect(BasicBlock block1, BasicBlock block2)
+ {
+ while (block1 != block2)
+ {
+ while (postOrderMap[block1.Index] < postOrderMap[block2.Index])
+ {
+ block1 = block1.ImmediateDominator;
+ }
+
+ while (postOrderMap[block2.Index] < postOrderMap[block1.Index])
+ {
+ block2 = block2.ImmediateDominator;
+ }
+ }
+
+ return block1;
+ }
+
+ entry.ImmediateDominator = entry;
+
+ bool modified;
+
+ do
+ {
+ modified = false;
+
+ for (int blkIndex = postOrderBlocks.Count - 2; blkIndex >= 0; blkIndex--)
+ {
+ BasicBlock block = postOrderBlocks[blkIndex];
+
+ BasicBlock newIDom = null;
+
+ foreach (BasicBlock predecessor in block.Predecessors)
+ {
+ if (predecessor.ImmediateDominator != null)
+ {
+ if (newIDom != null)
+ {
+ newIDom = Intersect(predecessor, newIDom);
+ }
+ else
+ {
+ newIDom = predecessor;
+ }
+ }
+ }
+
+ if (block.ImmediateDominator != newIDom)
+ {
+ block.ImmediateDominator = newIDom;
+
+ modified = true;
+ }
+ }
+ }
+ while (modified);
+ }
+
+ public static void FindDominanceFrontiers(BasicBlock[] blocks)
+ {
+ for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++)
+ {
+ BasicBlock block = blocks[blkIndex];
+
+ if (block.Predecessors.Count < 2)
+ {
+ continue;
+ }
+
+ for (int pBlkIndex = 0; pBlkIndex < block.Predecessors.Count; pBlkIndex++)
+ {
+ BasicBlock current = block.Predecessors[pBlkIndex];
+
+ while (current != block.ImmediateDominator)
+ {
+ current.DominanceFrontiers.Add(block);
+
+ current = current.ImmediateDominator;
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Translation/EmitterContext.cs b/Ryujinx.Graphics/Shader/Translation/EmitterContext.cs
new file mode 100644
index 00000000..6c2bf6e4
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Translation/EmitterContext.cs
@@ -0,0 +1,105 @@
+using Ryujinx.Graphics.Gal;
+using Ryujinx.Graphics.Shader.Decoders;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System.Collections.Generic;
+
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Translation
+{
+ class EmitterContext
+ {
+ public Block CurrBlock { get; set; }
+ public OpCode CurrOp { get; set; }
+
+ private GalShaderType _shaderType;
+
+ private ShaderHeader _header;
+
+ private List<Operation> _operations;
+
+ private Dictionary<ulong, Operand> _labels;
+
+ public EmitterContext(GalShaderType shaderType, ShaderHeader header)
+ {
+ _shaderType = shaderType;
+ _header = header;
+
+ _operations = new List<Operation>();
+
+ _labels = new Dictionary<ulong, Operand>();
+ }
+
+ public Operand Add(Instruction inst, Operand dest = null, params Operand[] sources)
+ {
+ Operation operation = new Operation(inst, dest, sources);
+
+ Add(operation);
+
+ return dest;
+ }
+
+ public void Add(Operation operation)
+ {
+ _operations.Add(operation);
+ }
+
+ public void MarkLabel(Operand label)
+ {
+ Add(Instruction.MarkLabel, label);
+ }
+
+ public Operand GetLabel(ulong address)
+ {
+ if (!_labels.TryGetValue(address, out Operand label))
+ {
+ label = Label();
+
+ _labels.Add(address, label);
+ }
+
+ return label;
+ }
+
+ public void PrepareForReturn()
+ {
+ if (_shaderType == GalShaderType.Fragment)
+ {
+ if (_header.OmapDepth)
+ {
+ Operand dest = Attribute(AttributeConsts.FragmentOutputDepth);
+
+ Operand src = Register(_header.DepthRegister, RegisterType.Gpr);
+
+ this.Copy(dest, src);
+ }
+
+ int regIndex = 0;
+
+ for (int attachment = 0; attachment < 8; attachment++)
+ {
+ OutputMapTarget target = _header.OmapTargets[attachment];
+
+ for (int component = 0; component < 4; component++)
+ {
+ if (target.ComponentEnabled(component))
+ {
+ Operand dest = Attribute(AttributeConsts.FragmentOutputColorBase + regIndex * 4);
+
+ Operand src = Register(regIndex, RegisterType.Gpr);
+
+ this.Copy(dest, src);
+
+ regIndex++;
+ }
+ }
+ }
+ }
+ }
+
+ public Operation[] GetOperations()
+ {
+ return _operations.ToArray();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Translation/EmitterContextInsts.cs b/Ryujinx.Graphics/Shader/Translation/EmitterContextInsts.cs
new file mode 100644
index 00000000..604aa67d
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Translation/EmitterContextInsts.cs
@@ -0,0 +1,420 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Translation
+{
+ static class EmitterContextInsts
+ {
+ public static Operand BitfieldExtractS32(this EmitterContext context, Operand a, Operand b, Operand c)
+ {
+ return context.Add(Instruction.BitfieldExtractS32, Local(), a, b, c);
+ }
+
+ public static Operand BitfieldExtractU32(this EmitterContext context, Operand a, Operand b, Operand c)
+ {
+ return context.Add(Instruction.BitfieldExtractU32, Local(), a, b, c);
+ }
+
+ public static Operand BitfieldInsert(this EmitterContext context, Operand a, Operand b, Operand c, Operand d)
+ {
+ return context.Add(Instruction.BitfieldInsert, Local(), a, b, c, d);
+ }
+
+ public static Operand BitfieldReverse(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.BitfieldReverse, Local(), a);
+ }
+
+ public static Operand BitwiseAnd(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.BitwiseAnd, Local(), a, b);
+ }
+
+ public static Operand BitwiseExclusiveOr(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.BitwiseExclusiveOr, Local(), a, b);
+ }
+
+ public static Operand BitwiseNot(this EmitterContext context, Operand a, bool invert)
+ {
+ if (invert)
+ {
+ a = context.BitwiseNot(a);
+ }
+
+ return a;
+ }
+
+ public static Operand BitwiseNot(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.BitwiseNot, Local(), a);
+ }
+
+ public static Operand BitwiseOr(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.BitwiseOr, Local(), a, b);
+ }
+
+ public static Operand Branch(this EmitterContext context, Operand d)
+ {
+ return context.Add(Instruction.Branch, d);
+ }
+
+ public static Operand BranchIfFalse(this EmitterContext context, Operand d, Operand a)
+ {
+ return context.Add(Instruction.BranchIfFalse, d, a);
+ }
+
+ public static Operand BranchIfTrue(this EmitterContext context, Operand d, Operand a)
+ {
+ return context.Add(Instruction.BranchIfTrue, d, a);
+ }
+
+ public static Operand ConditionalSelect(this EmitterContext context, Operand a, Operand b, Operand c)
+ {
+ return context.Add(Instruction.ConditionalSelect, Local(), a, b, c);
+ }
+
+ public static Operand Copy(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.Copy, Local(), a);
+ }
+
+ public static void Copy(this EmitterContext context, Operand d, Operand a)
+ {
+ if (d.Type == OperandType.Constant)
+ {
+ return;
+ }
+
+ context.Add(Instruction.Copy, d, a);
+ }
+
+ public static Operand Discard(this EmitterContext context)
+ {
+ return context.Add(Instruction.Discard);
+ }
+
+ public static Operand EmitVertex(this EmitterContext context)
+ {
+ return context.Add(Instruction.EmitVertex);
+ }
+
+ public static Operand EndPrimitive(this EmitterContext context)
+ {
+ return context.Add(Instruction.EndPrimitive);
+ }
+
+ public static Operand FPAbsNeg(this EmitterContext context, Operand a, bool abs, bool neg)
+ {
+ return context.FPNegate(context.FPAbsolute(a, abs), neg);
+ }
+
+ public static Operand FPAbsolute(this EmitterContext context, Operand a, bool abs)
+ {
+ if (abs)
+ {
+ a = context.FPAbsolute(a);
+ }
+
+ return a;
+ }
+
+ public static Operand FPAbsolute(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.FP | Instruction.Absolute, Local(), a);
+ }
+
+ public static Operand FPAdd(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.FP | Instruction.Add, Local(), a, b);
+ }
+
+ public static Operand FPCeiling(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.FP | Instruction.Ceiling, Local(), a);
+ }
+
+ public static Operand FPCompareEqual(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.FP | Instruction.CompareEqual, Local(), a, b);
+ }
+
+ public static Operand FPCompareLess(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.FP | Instruction.CompareLess, Local(), a, b);
+ }
+
+ public static Operand FPConvertToS32(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.ConvertFPToS32, Local(), a);
+ }
+
+ public static Operand FPCosine(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.FP | Instruction.Cosine, Local(), a);
+ }
+
+ public static Operand FPDivide(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.FP | Instruction.Divide, Local(), a, b);
+ }
+
+ public static Operand FPExponentB2(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.FP | Instruction.ExponentB2, Local(), a);
+ }
+
+ public static Operand FPFloor(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.FP | Instruction.Floor, Local(), a);
+ }
+
+ public static Operand FPLogarithmB2(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.FP | Instruction.LogarithmB2, Local(), a);
+ }
+
+ public static Operand FPMaximum(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.FP | Instruction.Maximum, Local(), a, b);
+ }
+
+ public static Operand FPMinimum(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.FP | Instruction.Minimum, Local(), a, b);
+ }
+
+ public static Operand FPMultiply(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.FP | Instruction.Multiply, Local(), a, b);
+ }
+
+ public static Operand FPFusedMultiplyAdd(this EmitterContext context, Operand a, Operand b, Operand c)
+ {
+ return context.Add(Instruction.FusedMultiplyAdd, Local(), a, b, c);
+ }
+
+ public static Operand FPNegate(this EmitterContext context, Operand a, bool neg)
+ {
+ if (neg)
+ {
+ a = context.FPNegate(a);
+ }
+
+ return a;
+ }
+
+ public static Operand FPNegate(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.FP | Instruction.Negate, Local(), a);
+ }
+
+ public static Operand FPReciprocal(this EmitterContext context, Operand a)
+ {
+ return context.FPDivide(ConstF(1), a);
+ }
+
+ public static Operand FPReciprocalSquareRoot(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.FP | Instruction.ReciprocalSquareRoot, Local(), a);
+ }
+
+ public static Operand FPSaturate(this EmitterContext context, Operand a, bool sat)
+ {
+ if (sat)
+ {
+ a = context.FPSaturate(a);
+ }
+
+ return a;
+ }
+
+ public static Operand FPSaturate(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.FP | Instruction.Clamp, Local(), a, ConstF(0), ConstF(1));
+ }
+
+ public static Operand FPSine(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.FP | Instruction.Sine, Local(), a);
+ }
+
+ public static Operand FPSquareRoot(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.FP | Instruction.SquareRoot, Local(), a);
+ }
+
+ public static Operand FPTruncate(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.Truncate, Local(), a);
+ }
+
+ public static Operand IAbsNeg(this EmitterContext context, Operand a, bool abs, bool neg)
+ {
+ return context.INegate(context.IAbsolute(a, abs), neg);
+ }
+
+ public static Operand IAbsolute(this EmitterContext context, Operand a, bool abs)
+ {
+ if (abs)
+ {
+ a = context.IAbsolute(a);
+ }
+
+ return a;
+ }
+
+ public static Operand IAbsolute(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.Absolute, Local(), a);
+ }
+
+ public static Operand IAdd(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.Add, Local(), a, b);
+ }
+
+ public static Operand IClampS32(this EmitterContext context, Operand a, Operand b, Operand c)
+ {
+ return context.Add(Instruction.Clamp, Local(), a, b, c);
+ }
+
+ public static Operand IClampU32(this EmitterContext context, Operand a, Operand b, Operand c)
+ {
+ return context.Add(Instruction.ClampU32, Local(), a, b, c);
+ }
+
+ public static Operand ICompareEqual(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.CompareEqual, Local(), a, b);
+ }
+
+ public static Operand ICompareLess(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.CompareLess, Local(), a, b);
+ }
+
+ public static Operand ICompareLessUnsigned(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.CompareLessU32, Local(), a, b);
+ }
+
+ public static Operand ICompareNotEqual(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.CompareNotEqual, Local(), a, b);
+ }
+
+ public static Operand IConvertS32ToFP(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.ConvertS32ToFP, Local(), a);
+ }
+
+ public static Operand IConvertU32ToFP(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.ConvertU32ToFP, Local(), a);
+ }
+
+ public static Operand IMaximumS32(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.Maximum, Local(), a, b);
+ }
+
+ public static Operand IMaximumU32(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.MaximumU32, Local(), a, b);
+ }
+
+ public static Operand IMinimumS32(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.Minimum, Local(), a, b);
+ }
+
+ public static Operand IMinimumU32(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.MinimumU32, Local(), a, b);
+ }
+
+ public static Operand IMultiply(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.Multiply, Local(), a, b);
+ }
+
+ public static Operand INegate(this EmitterContext context, Operand a, bool neg)
+ {
+ if (neg)
+ {
+ a = context.INegate(a);
+ }
+
+ return a;
+ }
+
+ public static Operand INegate(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.Negate, Local(), a);
+ }
+
+ public static Operand ISubtract(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.Subtract, Local(), a, b);
+ }
+
+ public static Operand IsNan(this EmitterContext context, Operand a)
+ {
+ return context.Add(Instruction.IsNan, Local(), a);
+ }
+
+ public static Operand LoadConstant(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.LoadConstant, Local(), a, b);
+ }
+
+ public static Operand PackHalf2x16(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.PackHalf2x16, Local(), a, b);
+ }
+
+ public static Operand Return(this EmitterContext context)
+ {
+ context.PrepareForReturn();
+
+ return context.Add(Instruction.Return);
+ }
+
+ public static Operand ShiftLeft(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.ShiftLeft, Local(), a, b);
+ }
+
+ public static Operand ShiftRightS32(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.ShiftRightS32, Local(), a, b);
+ }
+
+ public static Operand ShiftRightU32(this EmitterContext context, Operand a, Operand b)
+ {
+ return context.Add(Instruction.ShiftRightU32, Local(), a, b);
+ }
+
+ public static Operand UnpackHalf2x16High(this EmitterContext context, Operand a)
+ {
+ return UnpackHalf2x16(context, a, 1);
+ }
+
+ public static Operand UnpackHalf2x16Low(this EmitterContext context, Operand a)
+ {
+ return UnpackHalf2x16(context, a, 0);
+ }
+
+ private static Operand UnpackHalf2x16(this EmitterContext context, Operand a, int index)
+ {
+ Operand dest = Local();
+
+ context.Add(new Operation(Instruction.UnpackHalf2x16, index, dest, a));
+
+ return dest;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Translation/Optimizations/BranchElimination.cs b/Ryujinx.Graphics/Shader/Translation/Optimizations/BranchElimination.cs
new file mode 100644
index 00000000..2b0f1905
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Translation/Optimizations/BranchElimination.cs
@@ -0,0 +1,64 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System;
+
+namespace Ryujinx.Graphics.Shader.Translation.Optimizations
+{
+ static class BranchElimination
+ {
+ public static bool Eliminate(BasicBlock block)
+ {
+ if (block.HasBranch && IsRedundantBranch((Operation)block.GetLastOp(), Next(block)))
+ {
+ block.Branch = null;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private static bool IsRedundantBranch(Operation current, BasicBlock nextBlock)
+ {
+ //Here we check that:
+ //- The current block ends with a branch.
+ //- The next block only contains a branch.
+ //- The branch on the next block is unconditional.
+ //- Both branches are jumping to the same location.
+ //In this case, the branch on the current block can be removed,
+ //as the next block is going to jump to the same place anyway.
+ if (nextBlock == null)
+ {
+ return false;
+ }
+
+ if (!(nextBlock.Operations.First?.Value is Operation next))
+ {
+ return false;
+ }
+
+ if (next.Inst != Instruction.Branch)
+ {
+ return false;
+ }
+
+ return current.Dest == next.Dest;
+ }
+
+ private static BasicBlock Next(BasicBlock block)
+ {
+ block = block.Next;
+
+ while (block != null && block.Operations.Count == 0)
+ {
+ if (block.HasBranch)
+ {
+ throw new InvalidOperationException("Found a bogus empty block that \"ends with a branch\".");
+ }
+
+ block = block.Next;
+ }
+
+ return block;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Translation/Optimizations/ConstantFolding.cs b/Ryujinx.Graphics/Shader/Translation/Optimizations/ConstantFolding.cs
new file mode 100644
index 00000000..a2e05ef1
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Translation/Optimizations/ConstantFolding.cs
@@ -0,0 +1,323 @@
+using Ryujinx.Graphics.Shader.Decoders;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System;
+
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Translation.Optimizations
+{
+ static class ConstantFolding
+ {
+ public static void Fold(Operation operation)
+ {
+ if (!AreAllSourcesConstant(operation))
+ {
+ return;
+ }
+
+ switch (operation.Inst)
+ {
+ case Instruction.Add:
+ EvaluateBinary(operation, (x, y) => x + y);
+ break;
+
+ case Instruction.BitwiseAnd:
+ EvaluateBinary(operation, (x, y) => x & y);
+ break;
+
+ case Instruction.BitwiseExclusiveOr:
+ EvaluateBinary(operation, (x, y) => x ^ y);
+ break;
+
+ case Instruction.BitwiseNot:
+ EvaluateUnary(operation, (x) => ~x);
+ break;
+
+ case Instruction.BitwiseOr:
+ EvaluateBinary(operation, (x, y) => x | y);
+ break;
+
+ case Instruction.BitfieldExtractS32:
+ BitfieldExtractS32(operation);
+ break;
+
+ case Instruction.BitfieldExtractU32:
+ BitfieldExtractU32(operation);
+ break;
+
+ case Instruction.Clamp:
+ EvaluateTernary(operation, (x, y, z) => Math.Clamp(x, y, z));
+ break;
+
+ case Instruction.ClampU32:
+ EvaluateTernary(operation, (x, y, z) => (int)Math.Clamp((uint)x, (uint)y, (uint)z));
+ break;
+
+ case Instruction.CompareEqual:
+ EvaluateBinary(operation, (x, y) => x == y);
+ break;
+
+ case Instruction.CompareGreater:
+ EvaluateBinary(operation, (x, y) => x > y);
+ break;
+
+ case Instruction.CompareGreaterOrEqual:
+ EvaluateBinary(operation, (x, y) => x >= y);
+ break;
+
+ case Instruction.CompareGreaterOrEqualU32:
+ EvaluateBinary(operation, (x, y) => (uint)x >= (uint)y);
+ break;
+
+ case Instruction.CompareGreaterU32:
+ EvaluateBinary(operation, (x, y) => (uint)x > (uint)y);
+ break;
+
+ case Instruction.CompareLess:
+ EvaluateBinary(operation, (x, y) => x < y);
+ break;
+
+ case Instruction.CompareLessOrEqual:
+ EvaluateBinary(operation, (x, y) => x <= y);
+ break;
+
+ case Instruction.CompareLessOrEqualU32:
+ EvaluateBinary(operation, (x, y) => (uint)x <= (uint)y);
+ break;
+
+ case Instruction.CompareLessU32:
+ EvaluateBinary(operation, (x, y) => (uint)x < (uint)y);
+ break;
+
+ case Instruction.CompareNotEqual:
+ EvaluateBinary(operation, (x, y) => x != y);
+ break;
+
+ case Instruction.Divide:
+ EvaluateBinary(operation, (x, y) => y != 0 ? x / y : 0);
+ break;
+
+ case Instruction.FP | Instruction.Add:
+ EvaluateFPBinary(operation, (x, y) => x + y);
+ break;
+
+ case Instruction.FP | Instruction.Clamp:
+ EvaluateFPTernary(operation, (x, y, z) => Math.Clamp(x, y, z));
+ break;
+
+ case Instruction.FP | Instruction.CompareEqual:
+ EvaluateFPBinary(operation, (x, y) => x == y);
+ break;
+
+ case Instruction.FP | Instruction.CompareGreater:
+ EvaluateFPBinary(operation, (x, y) => x > y);
+ break;
+
+ case Instruction.FP | Instruction.CompareGreaterOrEqual:
+ EvaluateFPBinary(operation, (x, y) => x >= y);
+ break;
+
+ case Instruction.FP | Instruction.CompareLess:
+ EvaluateFPBinary(operation, (x, y) => x < y);
+ break;
+
+ case Instruction.FP | Instruction.CompareLessOrEqual:
+ EvaluateFPBinary(operation, (x, y) => x <= y);
+ break;
+
+ case Instruction.FP | Instruction.CompareNotEqual:
+ EvaluateFPBinary(operation, (x, y) => x != y);
+ break;
+
+ case Instruction.FP | Instruction.Divide:
+ EvaluateFPBinary(operation, (x, y) => x / y);
+ break;
+
+ case Instruction.FP | Instruction.Multiply:
+ EvaluateFPBinary(operation, (x, y) => x * y);
+ break;
+
+ case Instruction.FP | Instruction.Negate:
+ EvaluateFPUnary(operation, (x) => -x);
+ break;
+
+ case Instruction.FP | Instruction.Subtract:
+ EvaluateFPBinary(operation, (x, y) => x - y);
+ break;
+
+ case Instruction.IsNan:
+ EvaluateFPUnary(operation, (x) => float.IsNaN(x));
+ break;
+
+ case Instruction.Maximum:
+ EvaluateBinary(operation, (x, y) => Math.Max(x, y));
+ break;
+
+ case Instruction.MaximumU32:
+ EvaluateBinary(operation, (x, y) => (int)Math.Max((uint)x, (uint)y));
+ break;
+
+ case Instruction.Minimum:
+ EvaluateBinary(operation, (x, y) => Math.Min(x, y));
+ break;
+
+ case Instruction.MinimumU32:
+ EvaluateBinary(operation, (x, y) => (int)Math.Min((uint)x, (uint)y));
+ break;
+
+ case Instruction.Multiply:
+ EvaluateBinary(operation, (x, y) => x * y);
+ break;
+
+ case Instruction.Negate:
+ EvaluateUnary(operation, (x) => -x);
+ break;
+
+ case Instruction.ShiftLeft:
+ EvaluateBinary(operation, (x, y) => x << y);
+ break;
+
+ case Instruction.ShiftRightS32:
+ EvaluateBinary(operation, (x, y) => x >> y);
+ break;
+
+ case Instruction.ShiftRightU32:
+ EvaluateBinary(operation, (x, y) => (int)((uint)x >> y));
+ break;
+
+ case Instruction.Subtract:
+ EvaluateBinary(operation, (x, y) => x - y);
+ break;
+
+ case Instruction.UnpackHalf2x16:
+ UnpackHalf2x16(operation);
+ break;
+ }
+ }
+
+ private static bool AreAllSourcesConstant(Operation operation)
+ {
+ for (int index = 0; index < operation.SourcesCount; index++)
+ {
+ if (operation.GetSource(index).Type != OperandType.Constant)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static void BitfieldExtractS32(Operation operation)
+ {
+ int value = GetBitfieldExtractValue(operation);
+
+ int shift = 32 - operation.GetSource(2).Value;
+
+ value = (value << shift) >> shift;
+
+ operation.TurnIntoCopy(Const(value));
+ }
+
+ private static void BitfieldExtractU32(Operation operation)
+ {
+ operation.TurnIntoCopy(Const(GetBitfieldExtractValue(operation)));
+ }
+
+ private static int GetBitfieldExtractValue(Operation operation)
+ {
+ int value = operation.GetSource(0).Value;
+ int lsb = operation.GetSource(1).Value;
+ int length = operation.GetSource(2).Value;
+
+ return value.Extract(lsb, length);
+ }
+
+ private static void UnpackHalf2x16(Operation operation)
+ {
+ int value = operation.GetSource(0).Value;
+
+ value = (value >> operation.ComponentIndex * 16) & 0xffff;
+
+ operation.TurnIntoCopy(ConstF(HalfConversion.HalfToSingle(value)));
+ }
+
+ private static void FPNegate(Operation operation)
+ {
+ float value = operation.GetSource(0).AsFloat();
+
+ operation.TurnIntoCopy(ConstF(-value));
+ }
+
+ private static void EvaluateUnary(Operation operation, Func<int, int> op)
+ {
+ int x = operation.GetSource(0).Value;
+
+ operation.TurnIntoCopy(Const(op(x)));
+ }
+
+ private static void EvaluateFPUnary(Operation operation, Func<float, float> op)
+ {
+ float x = operation.GetSource(0).AsFloat();
+
+ operation.TurnIntoCopy(ConstF(op(x)));
+ }
+
+ private static void EvaluateFPUnary(Operation operation, Func<float, bool> op)
+ {
+ float x = operation.GetSource(0).AsFloat();
+
+ operation.TurnIntoCopy(Const(op(x) ? IrConsts.True : IrConsts.False));
+ }
+
+ private static void EvaluateBinary(Operation operation, Func<int, int, int> op)
+ {
+ int x = operation.GetSource(0).Value;
+ int y = operation.GetSource(1).Value;
+
+ operation.TurnIntoCopy(Const(op(x, y)));
+ }
+
+ private static void EvaluateBinary(Operation operation, Func<int, int, bool> op)
+ {
+ int x = operation.GetSource(0).Value;
+ int y = operation.GetSource(1).Value;
+
+ operation.TurnIntoCopy(Const(op(x, y) ? IrConsts.True : IrConsts.False));
+ }
+
+ private static void EvaluateFPBinary(Operation operation, Func<float, float, float> op)
+ {
+ float x = operation.GetSource(0).AsFloat();
+ float y = operation.GetSource(1).AsFloat();
+
+ operation.TurnIntoCopy(ConstF(op(x, y)));
+ }
+
+ private static void EvaluateFPBinary(Operation operation, Func<float, float, bool> op)
+ {
+ float x = operation.GetSource(0).AsFloat();
+ float y = operation.GetSource(1).AsFloat();
+
+ operation.TurnIntoCopy(Const(op(x, y) ? IrConsts.True : IrConsts.False));
+ }
+
+ private static void EvaluateTernary(Operation operation, Func<int, int, int, int> op)
+ {
+ int x = operation.GetSource(0).Value;
+ int y = operation.GetSource(1).Value;
+ int z = operation.GetSource(2).Value;
+
+ operation.TurnIntoCopy(Const(op(x, y, z)));
+ }
+
+ private static void EvaluateFPTernary(Operation operation, Func<float, float, float, float> op)
+ {
+ float x = operation.GetSource(0).AsFloat();
+ float y = operation.GetSource(1).AsFloat();
+ float z = operation.GetSource(2).AsFloat();
+
+ operation.TurnIntoCopy(ConstF(op(x, y, z)));
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Translation/Optimizations/HalfConversion.cs b/Ryujinx.Graphics/Shader/Translation/Optimizations/HalfConversion.cs
new file mode 100644
index 00000000..9ef35abc
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Translation/Optimizations/HalfConversion.cs
@@ -0,0 +1,47 @@
+using System;
+
+namespace Ryujinx.Graphics.Shader.Translation.Optimizations
+{
+ static class HalfConversion
+ {
+ public static float HalfToSingle(int value)
+ {
+ int mantissa = (value >> 0) & 0x3ff;
+ int exponent = (value >> 10) & 0x1f;
+ int sign = (value >> 15) & 0x1;
+
+ if (exponent == 0x1f)
+ {
+ //NaN or Infinity.
+ mantissa <<= 13;
+ exponent = 0xff;
+ }
+ else if (exponent != 0 || mantissa != 0 )
+ {
+ if (exponent == 0)
+ {
+ //Denormal.
+ int e = -1;
+ int m = mantissa;
+
+ do
+ {
+ e++;
+ m <<= 1;
+ }
+ while ((m & 0x400) == 0);
+
+ mantissa = m & 0x3ff;
+ exponent = e;
+ }
+
+ mantissa <<= 13;
+ exponent = 127 - 15 + exponent;
+ }
+
+ int output = (sign << 31) | (exponent << 23) | mantissa;
+
+ return BitConverter.Int32BitsToSingle(output);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Translation/Optimizations/Optimizer.cs b/Ryujinx.Graphics/Shader/Translation/Optimizations/Optimizer.cs
new file mode 100644
index 00000000..88118e3a
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Translation/Optimizations/Optimizer.cs
@@ -0,0 +1,172 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Ryujinx.Graphics.Shader.Translation.Optimizations
+{
+ static class Optimizer
+ {
+ public static void Optimize(BasicBlock[] blocks)
+ {
+ bool modified;
+
+ do
+ {
+ modified = false;
+
+ for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++)
+ {
+ BasicBlock block = blocks[blkIndex];
+
+ LinkedListNode<INode> node = block.Operations.First;
+
+ while (node != null)
+ {
+ LinkedListNode<INode> nextNode = node.Next;
+
+ bool isUnused = IsUnused(node.Value);
+
+ if (!(node.Value is Operation operation) || isUnused)
+ {
+ if (isUnused)
+ {
+ RemoveNode(block, node);
+
+ modified = true;
+ }
+
+ node = nextNode;
+
+ continue;
+ }
+
+ ConstantFolding.Fold(operation);
+
+ Simplification.Simplify(operation);
+
+ if (DestIsLocalVar(operation))
+ {
+ if (operation.Inst == Instruction.Copy)
+ {
+ PropagateCopy(operation);
+
+ RemoveNode(block, node);
+
+ modified = true;
+ }
+ else if (operation.Inst == Instruction.PackHalf2x16 && PropagatePack(operation))
+ {
+ if (operation.Dest.UseOps.Count == 0)
+ {
+ RemoveNode(block, node);
+ }
+
+ modified = true;
+ }
+ }
+
+ node = nextNode;
+ }
+
+ if (BranchElimination.Eliminate(block))
+ {
+ RemoveNode(block, block.Operations.Last);
+
+ modified = true;
+ }
+ }
+ }
+ while (modified);
+ }
+
+ private static void PropagateCopy(Operation copyOp)
+ {
+ //Propagate copy source operand to all uses of
+ //the destination operand.
+ Operand dest = copyOp.Dest;
+ Operand src = copyOp.GetSource(0);
+
+ INode[] uses = dest.UseOps.ToArray();
+
+ foreach (INode useNode in uses)
+ {
+ for (int index = 0; index < useNode.SourcesCount; index++)
+ {
+ if (useNode.GetSource(index) == dest)
+ {
+ useNode.SetSource(index, src);
+ }
+ }
+ }
+ }
+
+ private static bool PropagatePack(Operation packOp)
+ {
+ //Propagate pack source operands to uses by unpack
+ //instruction. The source depends on the unpack instruction.
+ bool modified = false;
+
+ Operand dest = packOp.Dest;
+ Operand src0 = packOp.GetSource(0);
+ Operand src1 = packOp.GetSource(1);
+
+ INode[] uses = dest.UseOps.ToArray();
+
+ foreach (INode useNode in uses)
+ {
+ if (!(useNode is Operation operation) || operation.Inst != Instruction.UnpackHalf2x16)
+ {
+ continue;
+ }
+
+ if (operation.GetSource(0) == dest)
+ {
+ operation.TurnIntoCopy(operation.ComponentIndex == 1 ? src1 : src0);
+
+ modified = true;
+ }
+ }
+
+ return modified;
+ }
+
+ private static void RemoveNode(BasicBlock block, LinkedListNode<INode> llNode)
+ {
+ //Remove a node from the nodes list, and also remove itself
+ //from all the use lists on the operands that this node uses.
+ block.Operations.Remove(llNode);
+
+ Queue<INode> nodes = new Queue<INode>();
+
+ nodes.Enqueue(llNode.Value);
+
+ while (nodes.TryDequeue(out INode node))
+ {
+ for (int index = 0; index < node.SourcesCount; index++)
+ {
+ Operand src = node.GetSource(index);
+
+ if (src.Type != OperandType.LocalVariable)
+ {
+ continue;
+ }
+
+ if (src.UseOps.Remove(node) && src.UseOps.Count == 0)
+ {
+ nodes.Enqueue(src.AsgOp);
+ }
+ }
+ }
+ }
+
+ private static bool IsUnused(INode node)
+ {
+ return DestIsLocalVar(node) && node.Dest.UseOps.Count == 0;
+ }
+
+ private static bool DestIsLocalVar(INode node)
+ {
+ return node.Dest != null && node.Dest.Type == OperandType.LocalVariable;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Translation/Optimizations/Simplification.cs b/Ryujinx.Graphics/Shader/Translation/Optimizations/Simplification.cs
new file mode 100644
index 00000000..56b1543f
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Translation/Optimizations/Simplification.cs
@@ -0,0 +1,147 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Translation.Optimizations
+{
+ static class Simplification
+ {
+ private const int AllOnes = ~0;
+
+ public static void Simplify(Operation operation)
+ {
+ switch (operation.Inst)
+ {
+ case Instruction.Add:
+ case Instruction.BitwiseExclusiveOr:
+ TryEliminateBinaryOpComutative(operation, 0);
+ break;
+
+ case Instruction.BitwiseAnd:
+ TryEliminateBitwiseAnd(operation);
+ break;
+
+ case Instruction.BitwiseOr:
+ TryEliminateBitwiseOr(operation);
+ break;
+
+ case Instruction.ConditionalSelect:
+ TryEliminateConditionalSelect(operation);
+ break;
+
+ case Instruction.Divide:
+ TryEliminateBinaryOpY(operation, 1);
+ break;
+
+ case Instruction.Multiply:
+ TryEliminateBinaryOpComutative(operation, 1);
+ break;
+
+ case Instruction.ShiftLeft:
+ case Instruction.ShiftRightS32:
+ case Instruction.ShiftRightU32:
+ case Instruction.Subtract:
+ TryEliminateBinaryOpY(operation, 0);
+ break;
+ }
+ }
+
+ private static void TryEliminateBitwiseAnd(Operation operation)
+ {
+ //Try to recognize and optimize those 3 patterns (in order):
+ //x & 0xFFFFFFFF == x, 0xFFFFFFFF & y == y,
+ //x & 0x00000000 == 0x00000000, 0x00000000 & y == 0x00000000
+ Operand x = operation.GetSource(0);
+ Operand y = operation.GetSource(1);
+
+ if (IsConstEqual(x, AllOnes))
+ {
+ operation.TurnIntoCopy(y);
+ }
+ else if (IsConstEqual(y, AllOnes))
+ {
+ operation.TurnIntoCopy(x);
+ }
+ else if (IsConstEqual(x, 0) || IsConstEqual(y, 0))
+ {
+ operation.TurnIntoCopy(Const(0));
+ }
+ }
+
+ private static void TryEliminateBitwiseOr(Operation operation)
+ {
+ //Try to recognize and optimize those 3 patterns (in order):
+ //x | 0x00000000 == x, 0x00000000 | y == y,
+ //x | 0xFFFFFFFF == 0xFFFFFFFF, 0xFFFFFFFF | y == 0xFFFFFFFF
+ Operand x = operation.GetSource(0);
+ Operand y = operation.GetSource(1);
+
+ if (IsConstEqual(x, 0))
+ {
+ operation.TurnIntoCopy(y);
+ }
+ else if (IsConstEqual(y, 0))
+ {
+ operation.TurnIntoCopy(x);
+ }
+ else if (IsConstEqual(x, AllOnes) || IsConstEqual(y, AllOnes))
+ {
+ operation.TurnIntoCopy(Const(AllOnes));
+ }
+ }
+
+ private static void TryEliminateBinaryOpY(Operation operation, int comparand)
+ {
+ Operand x = operation.GetSource(0);
+ Operand y = operation.GetSource(1);
+
+ if (IsConstEqual(y, comparand))
+ {
+ operation.TurnIntoCopy(x);
+ }
+ }
+
+ private static void TryEliminateBinaryOpComutative(Operation operation, int comparand)
+ {
+ Operand x = operation.GetSource(0);
+ Operand y = operation.GetSource(1);
+
+ if (IsConstEqual(x, comparand))
+ {
+ operation.TurnIntoCopy(y);
+ }
+ else if (IsConstEqual(y, comparand))
+ {
+ operation.TurnIntoCopy(x);
+ }
+ }
+
+ private static void TryEliminateConditionalSelect(Operation operation)
+ {
+ Operand cond = operation.GetSource(0);
+
+ if (cond.Type != OperandType.Constant)
+ {
+ return;
+ }
+
+ //The condition is constant, we can turn it into a copy, and select
+ //the source based on the condition value.
+ int srcIndex = cond.Value != 0 ? 1 : 2;
+
+ Operand source = operation.GetSource(srcIndex);
+
+ operation.TurnIntoCopy(source);
+ }
+
+ private static bool IsConstEqual(Operand operand, int comparand)
+ {
+ if (operand.Type != OperandType.Constant)
+ {
+ return false;
+ }
+
+ return operand.Value == comparand;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Translation/Ssa.cs b/Ryujinx.Graphics/Shader/Translation/Ssa.cs
new file mode 100644
index 00000000..b612649c
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Translation/Ssa.cs
@@ -0,0 +1,330 @@
+using Ryujinx.Graphics.Shader.Decoders;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System.Collections.Generic;
+
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Translation
+{
+ static class Ssa
+ {
+ private const int GprsAndPredsCount = RegisterConsts.GprsCount + RegisterConsts.PredsCount;
+
+ private class DefMap
+ {
+ private Dictionary<Register, Operand> _map;
+
+ private long[] _phiMasks;
+
+ public DefMap()
+ {
+ _map = new Dictionary<Register, Operand>();
+
+ _phiMasks = new long[(RegisterConsts.TotalCount + 63) / 64];
+ }
+
+ public bool TryAddOperand(Register reg, Operand operand)
+ {
+ return _map.TryAdd(reg, operand);
+ }
+
+ public bool TryGetOperand(Register reg, out Operand operand)
+ {
+ return _map.TryGetValue(reg, out operand);
+ }
+
+ public bool AddPhi(Register reg)
+ {
+ int key = GetKeyFromRegister(reg);
+
+ int index = key / 64;
+ int bit = key & 63;
+
+ long mask = 1L << bit;
+
+ if ((_phiMasks[index] & mask) != 0)
+ {
+ return false;
+ }
+
+ _phiMasks[index] |= mask;
+
+ return true;
+ }
+
+ public bool HasPhi(Register reg)
+ {
+ int key = GetKeyFromRegister(reg);
+
+ int index = key / 64;
+ int bit = key & 63;
+
+ return (_phiMasks[index] & (1L << bit)) != 0;
+ }
+ }
+
+ private struct Definition
+ {
+ public BasicBlock Block { get; }
+ public Operand Local { get; }
+
+ public Definition(BasicBlock block, Operand local)
+ {
+ Block = block;
+ Local = local;
+ }
+ }
+
+ public static void Rename(BasicBlock[] blocks)
+ {
+ DefMap[] globalDefs = new DefMap[blocks.Length];
+
+ for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++)
+ {
+ globalDefs[blkIndex] = new DefMap();
+ }
+
+ Queue<BasicBlock> dfPhiBlocks = new Queue<BasicBlock>();
+
+ //First pass, get all defs and locals uses.
+ for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++)
+ {
+ Operand[] localDefs = new Operand[RegisterConsts.TotalCount];
+
+ Operand RenameLocal(Operand operand)
+ {
+ if (operand != null && operand.Type == OperandType.Register)
+ {
+ Operand local = localDefs[GetKeyFromRegister(operand.GetRegister())];
+
+ operand = local ?? operand;
+ }
+
+ return operand;
+ }
+
+ BasicBlock block = blocks[blkIndex];
+
+ LinkedListNode<INode> node = block.Operations.First;
+
+ while (node != null)
+ {
+ if (node.Value is Operation operation)
+ {
+ for (int index = 0; index < operation.SourcesCount; index++)
+ {
+ operation.SetSource(index, RenameLocal(operation.GetSource(index)));
+ }
+
+ if (operation.Dest != null && operation.Dest.Type == OperandType.Register)
+ {
+ Operand local = Local();
+
+ localDefs[GetKeyFromRegister(operation.Dest.GetRegister())] = local;
+
+ operation.Dest = local;
+ }
+ }
+
+ node = node.Next;
+ }
+
+ for (int index = 0; index < RegisterConsts.TotalCount; index++)
+ {
+ Operand local = localDefs[index];
+
+ if (local == null)
+ {
+ continue;
+ }
+
+ Register reg = GetRegisterFromKey(index);
+
+ globalDefs[block.Index].TryAddOperand(reg, local);
+
+ dfPhiBlocks.Enqueue(block);
+
+ while (dfPhiBlocks.TryDequeue(out BasicBlock dfPhiBlock))
+ {
+ foreach (BasicBlock domFrontier in dfPhiBlock.DominanceFrontiers)
+ {
+ if (globalDefs[domFrontier.Index].AddPhi(reg))
+ {
+ dfPhiBlocks.Enqueue(domFrontier);
+ }
+ }
+ }
+ }
+ }
+
+ //Second pass, rename variables with definitions on different blocks.
+ for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++)
+ {
+ Operand[] localDefs = new Operand[RegisterConsts.TotalCount];
+
+ BasicBlock block = blocks[blkIndex];
+
+ Operand RenameGlobal(Operand operand)
+ {
+ if (operand != null && operand.Type == OperandType.Register)
+ {
+ int key = GetKeyFromRegister(operand.GetRegister());
+
+ Operand local = localDefs[key];
+
+ if (local != null)
+ {
+ return local;
+ }
+
+ operand = FindDefinitionForCurr(globalDefs, block, operand.GetRegister());
+
+ localDefs[key] = operand;
+ }
+
+ return operand;
+ }
+
+ LinkedListNode<INode> node = block.Operations.First;
+
+ while (node != null)
+ {
+ if (node.Value is Operation operation)
+ {
+ for (int index = 0; index < operation.SourcesCount; index++)
+ {
+ operation.SetSource(index, RenameGlobal(operation.GetSource(index)));
+ }
+ }
+
+ node = node.Next;
+ }
+ }
+ }
+
+ private static Operand FindDefinitionForCurr(DefMap[] globalDefs, BasicBlock current, Register reg)
+ {
+ if (globalDefs[current.Index].HasPhi(reg))
+ {
+ return InsertPhi(globalDefs, current, reg);
+ }
+
+ if (current != current.ImmediateDominator)
+ {
+ return FindDefinition(globalDefs, current.ImmediateDominator, reg).Local;
+ }
+
+ return Undef();
+ }
+
+ private static Definition FindDefinition(DefMap[] globalDefs, BasicBlock current, Register reg)
+ {
+ foreach (BasicBlock block in SelfAndImmediateDominators(current))
+ {
+ DefMap defMap = globalDefs[block.Index];
+
+ if (defMap.TryGetOperand(reg, out Operand lastDef))
+ {
+ return new Definition(block, lastDef);
+ }
+
+ if (defMap.HasPhi(reg))
+ {
+ return new Definition(block, InsertPhi(globalDefs, block, reg));
+ }
+ }
+
+ return new Definition(current, Undef());
+ }
+
+ private static IEnumerable<BasicBlock> SelfAndImmediateDominators(BasicBlock block)
+ {
+ while (block != block.ImmediateDominator)
+ {
+ yield return block;
+
+ block = block.ImmediateDominator;
+ }
+
+ yield return block;
+ }
+
+ private static Operand InsertPhi(DefMap[] globalDefs, BasicBlock block, Register reg)
+ {
+ //This block has a Phi that has not been materialized yet, but that
+ //would define a new version of the variable we're looking for. We need
+ //to materialize the Phi, add all the block/operand pairs into the Phi, and
+ //then use the definition from that Phi.
+ Operand local = Local();
+
+ PhiNode phi = new PhiNode(local);
+
+ AddPhi(block, phi);
+
+ globalDefs[block.Index].TryAddOperand(reg, local);
+
+ foreach (BasicBlock predecessor in block.Predecessors)
+ {
+ Definition def = FindDefinition(globalDefs, predecessor, reg);
+
+ phi.AddSource(def.Block, def.Local);
+ }
+
+ return local;
+ }
+
+ private static void AddPhi(BasicBlock block, PhiNode phi)
+ {
+ LinkedListNode<INode> node = block.Operations.First;
+
+ if (node != null)
+ {
+ while (node.Next?.Value is PhiNode)
+ {
+ node = node.Next;
+ }
+ }
+
+ if (node?.Value is PhiNode)
+ {
+ block.Operations.AddAfter(node, phi);
+ }
+ else
+ {
+ block.Operations.AddFirst(phi);
+ }
+ }
+
+ private static int GetKeyFromRegister(Register reg)
+ {
+ if (reg.Type == RegisterType.Gpr)
+ {
+ return reg.Index;
+ }
+ else if (reg.Type == RegisterType.Predicate)
+ {
+ return RegisterConsts.GprsCount + reg.Index;
+ }
+ else /* if (reg.Type == RegisterType.Flag) */
+ {
+ return GprsAndPredsCount + reg.Index;
+ }
+ }
+
+ private static Register GetRegisterFromKey(int key)
+ {
+ if (key < RegisterConsts.GprsCount)
+ {
+ return new Register(key, RegisterType.Gpr);
+ }
+ else if (key < GprsAndPredsCount)
+ {
+ return new Register(key - RegisterConsts.GprsCount, RegisterType.Predicate);
+ }
+ else /* if (key < RegisterConsts.TotalCount) */
+ {
+ return new Register(key - GprsAndPredsCount, RegisterType.Flag);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics/Shader/Translation/Translator.cs b/Ryujinx.Graphics/Shader/Translation/Translator.cs
new file mode 100644
index 00000000..706f3cfa
--- /dev/null
+++ b/Ryujinx.Graphics/Shader/Translation/Translator.cs
@@ -0,0 +1,219 @@
+using Ryujinx.Graphics.Gal;
+using Ryujinx.Graphics.Shader.CodeGen.Glsl;
+using Ryujinx.Graphics.Shader.Decoders;
+using Ryujinx.Graphics.Shader.Instructions;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.StructuredIr;
+using Ryujinx.Graphics.Shader.Translation.Optimizations;
+using System.Collections.Generic;
+
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Translation
+{
+ public static class Translator
+ {
+ public static ShaderProgram Translate(IGalMemory memory, ulong address, ShaderConfig config)
+ {
+ return Translate(memory, address, 0, config);
+ }
+
+ public static ShaderProgram Translate(
+ IGalMemory memory,
+ ulong address,
+ ulong addressB,
+ ShaderConfig config)
+ {
+ Operation[] shaderOps = DecodeShader(memory, address, config.Type);
+
+ if (addressB != 0)
+ {
+ //Dual vertex shader.
+ Operation[] shaderOpsB = DecodeShader(memory, addressB, config.Type);
+
+ shaderOps = Combine(shaderOps, shaderOpsB);
+ }
+
+ BasicBlock[] irBlocks = ControlFlowGraph.MakeCfg(shaderOps);
+
+ Dominance.FindDominators(irBlocks[0], irBlocks.Length);
+
+ Dominance.FindDominanceFrontiers(irBlocks);
+
+ Ssa.Rename(irBlocks);
+
+ Optimizer.Optimize(irBlocks);
+
+ StructuredProgramInfo sInfo = StructuredProgram.MakeStructuredProgram(irBlocks);
+
+ GlslProgram program = GlslGenerator.Generate(sInfo, config);
+
+ ShaderProgramInfo spInfo = new ShaderProgramInfo(
+ program.CBufferDescriptors,
+ program.TextureDescriptors);
+
+ return new ShaderProgram(spInfo, program.Code);
+ }
+
+ private static Operation[] DecodeShader(IGalMemory memory, ulong address, GalShaderType shaderType)
+ {
+ ShaderHeader header = new ShaderHeader(memory, address);
+
+ Block[] cfg = Decoder.Decode(memory, address);
+
+ EmitterContext context = new EmitterContext(shaderType, header);
+
+ for (int blkIndex = 0; blkIndex < cfg.Length; blkIndex++)
+ {
+ Block block = cfg[blkIndex];
+
+ context.CurrBlock = block;
+
+ context.MarkLabel(context.GetLabel(block.Address));
+
+ for (int opIndex = 0; opIndex < block.OpCodes.Count; opIndex++)
+ {
+ OpCode op = block.OpCodes[opIndex];
+
+ if (op.NeverExecute)
+ {
+ continue;
+ }
+
+ Operand predSkipLbl = null;
+
+ bool skipPredicateCheck = op.Emitter == InstEmit.Bra;
+
+ if (op is OpCodeSync opSync)
+ {
+ //If the instruction is a SYNC instruction with only one
+ //possible target address, then the instruction is basically
+ //just a simple branch, we can generate code similar to branch
+ //instructions, with the condition check on the branch itself.
+ skipPredicateCheck |= opSync.Targets.Count < 2;
+ }
+
+ if (!(op.Predicate.IsPT || skipPredicateCheck))
+ {
+ Operand label;
+
+ if (opIndex == block.OpCodes.Count - 1 && block.Next != null)
+ {
+ label = context.GetLabel(block.Next.Address);
+ }
+ else
+ {
+ label = Label();
+
+ predSkipLbl = label;
+ }
+
+ Operand pred = Register(op.Predicate);
+
+ if (op.InvertPredicate)
+ {
+ context.BranchIfTrue(label, pred);
+ }
+ else
+ {
+ context.BranchIfFalse(label, pred);
+ }
+ }
+
+ context.CurrOp = op;
+
+ op.Emitter(context);
+
+ if (predSkipLbl != null)
+ {
+ context.MarkLabel(predSkipLbl);
+ }
+ }
+ }
+
+ return context.GetOperations();
+ }
+
+ private static Operation[] Combine(Operation[] a, Operation[] b)
+ {
+ //Here we combine two shaders.
+ //For shader A:
+ //- All user attribute stores on shader A are turned into copies to a
+ //temporary variable. It's assumed that shader B will consume them.
+ //- All return instructions are turned into branch instructions, the
+ //branch target being the start of the shader B code.
+ //For shader B:
+ //- All user attribute loads on shader B are turned into copies from a
+ //temporary variable, as long that attribute is written by shader A.
+ List<Operation> output = new List<Operation>(a.Length + b.Length);
+
+ Operand[] temps = new Operand[AttributeConsts.UserAttributesCount * 4];
+
+ Operand lblB = Label();
+
+ for (int index = 0; index < a.Length; index++)
+ {
+ Operation operation = a[index];
+
+ if (IsUserAttribute(operation.Dest))
+ {
+ int tIndex = (operation.Dest.Value - AttributeConsts.UserAttributeBase) / 4;
+
+ Operand temp = temps[tIndex];
+
+ if (temp == null)
+ {
+ temp = Local();
+
+ temps[tIndex] = temp;
+ }
+
+ operation.Dest = temp;
+ }
+
+ if (operation.Inst == Instruction.Return)
+ {
+ output.Add(new Operation(Instruction.Branch, lblB));
+ }
+ else
+ {
+ output.Add(operation);
+ }
+ }
+
+ output.Add(new Operation(Instruction.MarkLabel, lblB));
+
+ for (int index = 0; index < b.Length; index++)
+ {
+ Operation operation = b[index];
+
+ for (int srcIndex = 0; srcIndex < operation.SourcesCount; srcIndex++)
+ {
+ Operand src = operation.GetSource(srcIndex);
+
+ if (IsUserAttribute(src))
+ {
+ Operand temp = temps[(src.Value - AttributeConsts.UserAttributeBase) / 4];
+
+ if (temp != null)
+ {
+ operation.SetSource(srcIndex, temp);
+ }
+ }
+ }
+
+ output.Add(operation);
+ }
+
+ return output.ToArray();
+ }
+
+ private static bool IsUserAttribute(Operand operand)
+ {
+ return operand != null &&
+ operand.Type == OperandType.Attribute &&
+ operand.Value >= AttributeConsts.UserAttributeBase &&
+ operand.Value < AttributeConsts.UserAttributeEnd;
+ }
+ }
+} \ No newline at end of file