diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Declarations.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Declarations.cs index e8c3555873..21d4a285fa 100644 --- a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Declarations.cs +++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Declarations.cs @@ -23,9 +23,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl { context.AppendLine("layout (points) in;"); context.AppendLine("layout (triangle_strip, max_vertices = 4) out;"); - } - context.AppendLine(); + context.AppendLine(); + } context.AppendLine("layout (std140) uniform Extra"); diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslGenerator.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslGenerator.cs index 7060d21c52..607d6786e9 100644 --- a/Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslGenerator.cs +++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslGenerator.cs @@ -43,7 +43,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl context.EnterScope(); - foreach (IAstNode node in block.Nodes) + foreach (IAstNode node in block) { if (node is AstBlock subBlock) { diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions.cs index 537b8436e1..896904bbfd 100644 --- a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions.cs +++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions.cs @@ -145,6 +145,24 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl case Instruction.LogarithmB2: return GetUnaryCallExpr(context, operation, "log2"); + case Instruction.LogicalAnd: + return GetBinaryExpr(context, operation, "&&"); + + case Instruction.LogicalExclusiveOr: + return GetBinaryExpr(context, operation, "^^"); + + case Instruction.LogicalNot: + return GetUnaryExpr(context, operation, "!"); + + case Instruction.LogicalOr: + return GetBinaryExpr(context, operation, "||"); + + case Instruction.LoopBreak: + return "break"; + + case Instruction.LoopContinue: + return "continue"; + case Instruction.Maximum: case Instruction.MaximumU32: return GetBinaryCallExpr(context, operation, "max"); @@ -188,7 +206,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl return GetUnaryCallExpr(context, operation, "trunc"); } - return "// " + operation.Inst; + throw new ArgumentException($"Operation has invalid instruction \"{operation.Inst}\"."); } private static string GetUnaryCallExpr(CodeGenContext context, AstOperation operation, string funcName) diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/NumberFormatter.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/NumberFormatter.cs index 05a3ecca96..40827768d8 100644 --- a/Ryujinx.Graphics/Shader/CodeGen/Glsl/NumberFormatter.cs +++ b/Ryujinx.Graphics/Shader/CodeGen/Glsl/NumberFormatter.cs @@ -45,6 +45,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl formatted = value.ToString("G9", CultureInfo.InvariantCulture); + if (!(formatted.Contains('.') || + formatted.Contains('e') || + formatted.Contains('E'))) + { + formatted += ".0"; + } + return true; } diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/Instruction.cs b/Ryujinx.Graphics/Shader/IntermediateRepresentation/Instruction.cs index 9e0b4675fc..6965f4516f 100644 --- a/Ryujinx.Graphics/Shader/IntermediateRepresentation/Instruction.cs +++ b/Ryujinx.Graphics/Shader/IntermediateRepresentation/Instruction.cs @@ -49,6 +49,12 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation LoadGlobal, LoadLocal, LogarithmB2, + LogicalAnd, + LogicalExclusiveOr, + LogicalNot, + LogicalOr, + LoopBreak, + LoopContinue, MarkLabel, Maximum, MaximumU32, diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstAssignment.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstAssignment.cs index 20847b3028..5a4acb88a3 100644 --- a/Ryujinx.Graphics/Shader/StructuredIr/AstAssignment.cs +++ b/Ryujinx.Graphics/Shader/StructuredIr/AstAssignment.cs @@ -1,6 +1,6 @@ namespace Ryujinx.Graphics.Shader.StructuredIr { - class AstAssignment : IAstNode + class AstAssignment : AstNode { public IAstNode Destination { get; } public IAstNode Source { get; } diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstBlock.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstBlock.cs index 62a998a028..888ff38e8f 100644 --- a/Ryujinx.Graphics/Shader/StructuredIr/AstBlock.cs +++ b/Ryujinx.Graphics/Shader/StructuredIr/AstBlock.cs @@ -1,22 +1,86 @@ +using System; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections; using System.Collections.Generic; namespace Ryujinx.Graphics.Shader.StructuredIr { - class AstBlock : IAstNode + class AstBlock : AstNode, IEnumerable { public AstBlockType Type { get; } - public IAstNode Condition { get; } + public IAstNode Condition { get; private set; } - public LinkedList Nodes { get; } + private LinkedList _nodes; + + public IAstNode First => _nodes.First?.Value; + public IAstNode Last => _nodes.Last?.Value; public AstBlock(AstBlockType type, IAstNode condition = null) { - Type = type; - + Type = type; Condition = condition; - Nodes = new LinkedList(); + _nodes = new LinkedList(); + } + + public void Add(IAstNode node) + { + Add(node, _nodes.AddLast(node)); + } + + public void AddFirst(IAstNode node) + { + Add(node, _nodes.AddFirst(node)); + } + + public void AddBefore(IAstNode oldNode, IAstNode node) + { + Add(node, _nodes.AddBefore(oldNode.LLNode, node)); + } + + public void AddAfter(IAstNode oldNode, IAstNode node) + { + Add(node, _nodes.AddAfter(oldNode.LLNode, node)); + } + + private void Add(IAstNode node, LinkedListNode newNode) + { + if (node.Parent != null) + { + throw new ArgumentException("Node already belongs to a block."); + } + + node.Parent = this; + node.LLNode = newNode; + } + + public void Remove(IAstNode node) + { + _nodes.Remove(node.LLNode); + + node.Parent = null; + node.LLNode = null; + } + + public void AndCondition(IAstNode cond) + { + Condition = new AstOperation(Instruction.LogicalAnd, Condition, cond); + } + + public void OrCondition(IAstNode cond) + { + Condition = new AstOperation(Instruction.LogicalOr, Condition, cond); + } + + public IEnumerator GetEnumerator() + { + return _nodes.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstDeclaration.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstDeclaration.cs index 0c75c67ca1..4d5e65c01f 100644 --- a/Ryujinx.Graphics/Shader/StructuredIr/AstDeclaration.cs +++ b/Ryujinx.Graphics/Shader/StructuredIr/AstDeclaration.cs @@ -1,6 +1,6 @@ namespace Ryujinx.Graphics.Shader.StructuredIr { - class AstDeclaration : IAstNode + class AstDeclaration : AstNode { public AstOperand Operand { get; } diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstHelper.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstHelper.cs new file mode 100644 index 0000000000..2dcb188e21 --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/AstHelper.cs @@ -0,0 +1,26 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class AstHelper + { + public static AstAssignment Assign(IAstNode destination, IAstNode source) + { + return new AstAssignment(destination, source); + } + + public static AstOperand Const(int value) + { + return new AstOperand(OperandType.Constant, value); + } + + public static AstOperand Local(VariableType type) + { + AstOperand local = new AstOperand(OperandType.LocalVariable); + + local.VarType = type; + + return local; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstNode.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstNode.cs new file mode 100644 index 0000000000..c667aac988 --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/AstNode.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstNode : IAstNode + { + public AstBlock Parent { get; set; } + + public LinkedListNode LLNode { get; set; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstOperand.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstOperand.cs index 90332a8041..5a5f3b7ee5 100644 --- a/Ryujinx.Graphics/Shader/StructuredIr/AstOperand.cs +++ b/Ryujinx.Graphics/Shader/StructuredIr/AstOperand.cs @@ -2,7 +2,7 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation; namespace Ryujinx.Graphics.Shader.StructuredIr { - class AstOperand : IAstNode + class AstOperand : AstNode { public OperandType Type { get; } @@ -33,7 +33,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr } } - public AstOperand(OperandType type, int value) : this() + public AstOperand(OperandType type, int value = 0) : this() { Type = type; Value = value; diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstOperation.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstOperation.cs index 8bdf4b8645..e8b699d4f9 100644 --- a/Ryujinx.Graphics/Shader/StructuredIr/AstOperation.cs +++ b/Ryujinx.Graphics/Shader/StructuredIr/AstOperation.cs @@ -2,7 +2,7 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation; namespace Ryujinx.Graphics.Shader.StructuredIr { - class AstOperation : IAstNode + class AstOperation : AstNode { public Instruction Inst { get; } diff --git a/Ryujinx.Graphics/Shader/StructuredIr/GotoElimination.cs b/Ryujinx.Graphics/Shader/StructuredIr/GotoElimination.cs new file mode 100644 index 0000000000..b9c9c8ff88 --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/GotoElimination.cs @@ -0,0 +1,466 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class GotoElimination + { + //This is a modified version of the algorithm presented on the paper + //"Taming Control Flow: A Structured Approach to Eliminating Goto Statements". + public static void Eliminate(GotoStatement[] gotos) + { + for (int index = gotos.Length - 1; index >= 0; index--) + { + GotoStatement stmt = gotos[index]; + + AstBlock gBlock = ParentBlock(stmt.Goto); + AstBlock lBlock = ParentBlock(stmt.Label); + + int gLevel = Level(gBlock); + int lLevel = Level(lBlock); + + if (IndirectlyRelated(gBlock, lBlock, gLevel, lLevel)) + { + AstBlock drBlock = gBlock; + + int drLevel = gLevel; + + do + { + drBlock = drBlock.Parent; + + drLevel--; + } + while (!DirectlyRelated(drBlock, lBlock, drLevel, lLevel)); + + MoveOutward(stmt, gLevel, drLevel); + + gBlock = drBlock; + gLevel = drLevel; + + if (Previous(stmt.Goto) is AstBlock elseBlock && elseBlock.Type == AstBlockType.Else) + { + //It's possible that the label was enclosed inside an else block, + //in this case we need to update the block and level. + //We also need to set the IsLoop for the case when the label is + //now before the goto, due to the newly introduced else block. + lBlock = ParentBlock(stmt.Label); + + lLevel = Level(lBlock); + + if (!IndirectlyRelated(elseBlock, lBlock, gLevel + 1, lLevel)) + { + stmt.IsLoop = true; + } + } + } + + if (DirectlyRelated(gBlock, lBlock, gLevel, lLevel)) + { + if (gLevel > lLevel) + { + MoveOutward(stmt, gLevel, lLevel); + } + else + { + if (stmt.IsLoop) + { + Lift(stmt); + } + + MoveInward(stmt); + } + } + + gBlock = ParentBlock(stmt.Goto); + + if (stmt.IsLoop) + { + EncloseDoWhile(stmt, gBlock, stmt.Label); + } + else + { + Enclose(gBlock, AstBlockType.If, stmt.Condition, Next(stmt.Goto), stmt.Label); + } + + gBlock.Remove(stmt.Goto); + } + } + + private static bool IndirectlyRelated(AstBlock lBlock, AstBlock rBlock, int lLevel, int rlevel) + { + return !(lBlock == rBlock || DirectlyRelated(lBlock, rBlock, lLevel, rlevel)); + } + + private static bool DirectlyRelated(AstBlock lBlock, AstBlock rBlock, int lLevel, int rLevel) + { + //If the levels are equal, they can be either siblings or indirectly related. + if (lLevel == rLevel) + { + return false; + } + + IAstNode block; + IAstNode other; + + int blockLvl, otherLvl; + + if (lLevel > rLevel) + { + block = lBlock; + blockLvl = lLevel; + other = rBlock; + otherLvl = rLevel; + } + else /* if (rLevel > lLevel) */ + { + block = rBlock; + blockLvl = rLevel; + other = lBlock; + otherLvl = lLevel; + } + + while (blockLvl >= otherLvl) + { + if (block == other) + { + return true; + } + + block = block.Parent; + + blockLvl--; + } + + return false; + } + + private static void Lift(GotoStatement stmt) + { + AstBlock block = ParentBlock(stmt.Goto); + + AstBlock[] path = BackwardsPath(block, ParentBlock(stmt.Label)); + + AstBlock loopFirstStmt = path[path.Length - 1]; + + if (loopFirstStmt.Type == AstBlockType.Else) + { + loopFirstStmt = Previous(loopFirstStmt) as AstBlock; + + if (loopFirstStmt == null || loopFirstStmt.Type != AstBlockType.If) + { + throw new InvalidOperationException("Found an else without a matching if."); + } + } + + AstBlock newBlock = EncloseDoWhile(stmt, block, loopFirstStmt); + + block.Remove(stmt.Goto); + + newBlock.AddFirst(stmt.Goto); + + stmt.IsLoop = false; + } + + private static void MoveOutward(GotoStatement stmt, int gLevel, int lLevel) + { + AstBlock origin = ParentBlock(stmt.Goto); + + AstBlock block = origin; + + //Check if a loop is enclosing the goto, and the block that is + //directly related to the label is above the loop block. + //In that case, we need to introduce a break to get out of the loop. + AstBlock loopBlock = origin; + + int loopLevel = gLevel; + + while (loopLevel > lLevel) + { + AstBlock child = loopBlock; + + loopBlock = loopBlock.Parent; + + loopLevel--; + + if (child.Type == AstBlockType.DoWhile) + { + EncloseSingleInst(stmt, Instruction.LoopBreak); + + block.Remove(stmt.Goto); + + loopBlock.AddAfter(child, stmt.Goto); + + block = loopBlock; + gLevel = loopLevel; + } + } + + //Insert ifs to skip the parts that shouldn't be executed due to the goto. + bool tryInsertElse = stmt.IsUnconditional && origin.Type == AstBlockType.If; + + while (gLevel > lLevel) + { + Enclose(block, AstBlockType.If, stmt.Condition, Next(stmt.Goto)); + + block.Remove(stmt.Goto); + + AstBlock child = block; + + //We can't move the goto in the middle of a if and a else block, in + //this case we need to move it after the else. + //IsLoop may need to be updated if the label is inside the else, as + //introducing a loop is the only way to ensure the else will be executed. + if (Next(child) is AstBlock elseBlock && elseBlock.Type == AstBlockType.Else) + { + child = elseBlock; + } + + block = block.Parent; + + block.AddAfter(child, stmt.Goto); + + gLevel--; + + if (tryInsertElse && child == origin) + { + AstBlock lBlock = ParentBlock(stmt.Label); + + IAstNode last = block == lBlock && !stmt.IsLoop ? stmt.Label : null; + + AstBlock newBlock = Enclose(block, AstBlockType.Else, null, Next(stmt.Goto), last); + + if (newBlock != null) + { + block.Remove(stmt.Goto); + + block.AddAfter(newBlock, stmt.Goto); + } + } + } + } + + private static void MoveInward(GotoStatement stmt) + { + AstBlock block = ParentBlock(stmt.Goto); + + AstBlock[] path = BackwardsPath(block, ParentBlock(stmt.Label)); + + for (int index = path.Length - 1; index >= 0; index--) + { + AstBlock child = path[index]; + AstBlock last = child; + + if (child.Type == AstBlockType.If) + { + //Modify the if condition to allow it to be entered by the goto. + if (!ContainsCondComb(child.Condition, Instruction.LogicalOr, stmt.Condition)) + { + child.OrCondition(stmt.Condition); + } + } + else if (child.Type == AstBlockType.Else) + { + //Modify the matching if condition to force the else to be entered by the goto. + if (!(Previous(child) is AstBlock ifBlock) || ifBlock.Type != AstBlockType.If) + { + throw new InvalidOperationException("Found an else without a matching if."); + } + + IAstNode cond = InverseCond(stmt.Condition); + + if (!ContainsCondComb(ifBlock.Condition, Instruction.LogicalAnd, cond)) + { + ifBlock.AndCondition(cond); + } + + last = ifBlock; + } + + Enclose(block, AstBlockType.If, stmt.Condition, Next(stmt.Goto), last); + + block.Remove(stmt.Goto); + + child.AddFirst(stmt.Goto); + + block = child; + } + } + + private static bool ContainsCondComb(IAstNode node, Instruction inst, IAstNode newCond) + { + while (node is AstOperation operation && operation.Sources.Length == 2) + { + if (operation.Inst == inst && IsSameCond(operation.Sources[1], newCond)) + { + return true; + } + + node = operation.Sources[0]; + } + + return false; + } + + private static AstBlock EncloseDoWhile(GotoStatement stmt, AstBlock block, IAstNode first) + { + if (block.Type == AstBlockType.DoWhile && first == block.First) + { + //We only need to insert the continue if we're not at the end of the loop, + //or if our condition is different from the loop condition. + if (Next(stmt.Goto) != null || block.Condition != stmt.Condition) + { + EncloseSingleInst(stmt, Instruction.LoopContinue); + } + + return block; + } + + return Enclose(block, AstBlockType.DoWhile, stmt.Condition, first, stmt.Goto); + } + + private static void EncloseSingleInst(GotoStatement stmt, Instruction inst) + { + AstBlock block = ParentBlock(stmt.Goto); + + AstBlock newBlock = new AstBlock(AstBlockType.If, stmt.Condition); + + block.AddAfter(stmt.Goto, newBlock); + + newBlock.AddFirst(new AstOperation(inst)); + } + + private static AstBlock Enclose( + AstBlock block, + AstBlockType type, + IAstNode cond, + IAstNode first, + IAstNode last = null) + { + if (first == last) + { + return null; + } + + if (type == AstBlockType.If) + { + cond = InverseCond(cond); + } + + //Do a quick check, if we are enclosing a single block, + //and the block type/condition matches the one we're going + //to create, then we don't need a new block, we can just + //return the old one. + bool hasSingleNode = Next(first) == last; + + if (hasSingleNode && BlockMatches(first, type, cond)) + { + return first as AstBlock; + } + + AstBlock newBlock = new AstBlock(type, cond); + + block.AddBefore(first, newBlock); + + while (first != last) + { + IAstNode next = Next(first); + + block.Remove(first); + + newBlock.Add(first); + + first = next; + } + + return newBlock; + } + + private static bool BlockMatches(IAstNode node, AstBlockType type, IAstNode cond) + { + if (!(node is AstBlock block)) + { + return false; + } + + return block.Type == type && IsSameCond(block.Condition, cond); + } + + private static bool IsSameCond(IAstNode lCond, IAstNode rCond) + { + if (lCond is AstOperation lCondOp && lCondOp.Inst == Instruction.LogicalNot) + { + if (!(rCond is AstOperation rCondOp) || rCondOp.Inst != lCondOp.Inst) + { + return false; + } + + lCond = lCondOp.Sources[0]; + rCond = rCondOp.Sources[0]; + } + + return lCond == rCond; + } + + private static AstBlock ParentBlock(IAstNode node) + { + if (node is AstBlock block) + { + return block.Parent; + } + + while (!(node is AstBlock)) + { + node = node.Parent; + } + + return node as AstBlock; + } + + private static AstBlock[] BackwardsPath(AstBlock top, AstBlock bottom) + { + AstBlock block = bottom; + + List path = new List(); + + while (block != top) + { + path.Add(block); + + block = block.Parent; + } + + return path.ToArray(); + } + + private static int Level(IAstNode node) + { + int level = 0; + + while (node != null) + { + level++; + + node = node.Parent; + } + + return level; + } + + private static IAstNode InverseCond(IAstNode cond) + { + return new AstOperation(Instruction.LogicalNot, cond); + } + + private static IAstNode Next(IAstNode node) + { + return node.LLNode.Next?.Value; + } + + private static IAstNode Previous(IAstNode node) + { + return node.LLNode.Previous?.Value; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/GotoStatement.cs b/Ryujinx.Graphics/Shader/StructuredIr/GotoStatement.cs new file mode 100644 index 0000000000..25216e55fb --- /dev/null +++ b/Ryujinx.Graphics/Shader/StructuredIr/GotoStatement.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class GotoStatement + { + public AstOperation Goto { get; } + public AstAssignment Label { get; } + + public IAstNode Condition => Label.Destination; + + public bool IsLoop { get; set; } + + public bool IsUnconditional => Goto.Inst == Instruction.Branch; + + public GotoStatement(AstOperation branch, AstAssignment label, bool isLoop) + { + Goto = branch; + Label = label; + IsLoop = isLoop; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/IAstNode.cs b/Ryujinx.Graphics/Shader/StructuredIr/IAstNode.cs index fcb8723df5..5ececbb5e4 100644 --- a/Ryujinx.Graphics/Shader/StructuredIr/IAstNode.cs +++ b/Ryujinx.Graphics/Shader/StructuredIr/IAstNode.cs @@ -1,7 +1,11 @@ +using System.Collections.Generic; + namespace Ryujinx.Graphics.Shader.StructuredIr { interface IAstNode { + AstBlock Parent { get; set; } + LinkedListNode LLNode { get; set; } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/InstructionInfo.cs b/Ryujinx.Graphics/Shader/StructuredIr/InstructionInfo.cs index 04f5c6397b..2d7fa1f281 100644 --- a/Ryujinx.Graphics/Shader/StructuredIr/InstructionInfo.cs +++ b/Ryujinx.Graphics/Shader/StructuredIr/InstructionInfo.cs @@ -61,6 +61,10 @@ namespace Ryujinx.Graphics.Shader.StructuredIr Add(Instruction.IsNan, VariableType.Bool, VariableType.F32); Add(Instruction.LoadConstant, VariableType.F32, VariableType.S32, VariableType.S32); Add(Instruction.LogarithmB2, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.LogicalAnd, VariableType.Bool, VariableType.Bool, VariableType.Bool); + Add(Instruction.LogicalExclusiveOr, VariableType.Bool, VariableType.Bool, VariableType.Bool); + Add(Instruction.LogicalNot, VariableType.Bool, VariableType.Bool); + Add(Instruction.LogicalOr, VariableType.Bool, VariableType.Bool, VariableType.Bool); Add(Instruction.ShiftLeft, VariableType.Int, VariableType.Int, VariableType.Int); Add(Instruction.ShiftRightS32, VariableType.S32, VariableType.S32, VariableType.Int); Add(Instruction.ShiftRightU32, VariableType.U32, VariableType.U32, VariableType.Int); diff --git a/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgram.cs b/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgram.cs index 97cfafeeaa..6684768819 100644 --- a/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgram.cs +++ b/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgram.cs @@ -1,4 +1,6 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; +using System.Collections.Generic; namespace Ryujinx.Graphics.Shader.StructuredIr { @@ -8,7 +10,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr { PhiFunctions.Remove(blocks); - StructuredProgramContext context = new StructuredProgramContext(blocks); + StructuredProgramContext context = new StructuredProgramContext(blocks.Length); for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) { @@ -24,6 +26,8 @@ namespace Ryujinx.Graphics.Shader.StructuredIr context.LeaveBlock(block); } + GotoElimination.Eliminate(context.GetGotos()); + context.PrependLocalDeclarations(); return context.Info; @@ -33,77 +37,167 @@ namespace Ryujinx.Graphics.Shader.StructuredIr { Instruction inst = operation.Inst; + IAstNode[] sources = new IAstNode[operation.SourcesCount]; + + for (int index = 0; index < sources.Length; index++) + { + sources[index] = context.GetOperandUse(operation.GetSource(index)); + } + if (operation.Dest != null && !IsBranchInst(inst)) { AstOperand dest = context.GetOperandDef(operation.Dest); - IAstNode[] sources = new IAstNode[operation.SourcesCount]; - - for (int index = 0; index < sources.Length; index++) - { - sources[index] = context.GetOperandUse(operation.GetSource(index)); - } - if (inst == Instruction.LoadConstant) { context.Info.ConstantBuffers.Add((sources[0] as AstOperand).Value); } - AstAssignment astAsg; + AstAssignment assignment; - if (inst == Instruction.Copy) + //If all the sources are bool, it's better to use short-circuiting + //logical operations, rather than forcing a cast to int and doing + //a bitwise operation with the value, as it is likely to be used as + //a bool in the end. + if (IsBitwiseInst(inst) && AreAllSourceTypesEqual(sources, VariableType.Bool)) { - //Copies are pretty much a typeless operation, - //so it's better to get the type from the source - //operand used on the copy, to avoid unnecessary - //reinterpret casts on the generated code. - dest.VarType = GetVarTypeFromUses(operation.Dest); + inst = GetLogicalFromBitwiseInst(inst); + } - astAsg = new AstAssignment(dest, sources[0]); + bool isCondSel = inst == Instruction.ConditionalSelect; + bool isCopy = inst == Instruction.Copy; + + if (isCondSel || isCopy) + { + VariableType type = GetVarTypeFromUses(operation.Dest); + + if (isCondSel && type == VariableType.F32) + { + inst |= Instruction.FP; + } + + dest.VarType = type; } else { dest.VarType = InstructionInfo.GetDestVarType(inst); - - AstOperation astOperation; - - if (operation is TextureOperation texOp) - { - if (!context.Info.Samplers.TryAdd(texOp.TextureHandle, texOp.Type)) - { - //TODO: Warning. - } - - int[] components = new int[] { texOp.ComponentIndex }; - - astOperation = new AstTextureOperation( - inst, - texOp.Type, - texOp.Flags, - texOp.TextureHandle, - components, - sources); - } - else - { - astOperation = new AstOperation(inst, sources); - } - - astAsg = new AstAssignment(dest, astOperation); } - context.AddNode(astAsg); + IAstNode source; + + if (operation is TextureOperation texOp) + { + if (!context.Info.Samplers.TryAdd(texOp.TextureHandle, texOp.Type)) + { + //TODO: Warning. + } + + int[] components = new int[] { texOp.ComponentIndex }; + + source = new AstTextureOperation( + inst, + texOp.Type, + texOp.Flags, + texOp.TextureHandle, + components, + sources); + } + else if (!isCopy) + { + source = new AstOperation(inst, sources); + } + else + { + source = sources[0]; + } + + assignment = new AstAssignment(dest, source); + + context.AddNode(assignment); } else { - //If dest is null, it's assumed that all the source - //operands are also null. - AstOperation astOperation = new AstOperation(inst); - - context.AddNode(astOperation); + context.AddNode(new AstOperation(inst, sources)); } } + private static VariableType GetVarTypeFromUses(Operand dest) + { + HashSet visited = new HashSet(); + + Queue pending = new Queue(); + + bool Enqueue(Operand operand) + { + if (visited.Add(operand)) + { + pending.Enqueue(operand); + + return true; + } + + return false; + } + + Enqueue(dest); + + while (pending.TryDequeue(out Operand operand)) + { + foreach (INode useNode in operand.UseOps) + { + if (!(useNode is Operation operation)) + { + continue; + } + + if (operation.Inst == Instruction.Copy) + { + if (operation.Dest.Type == OperandType.LocalVariable) + { + if (Enqueue(operation.Dest)) + { + break; + } + } + else + { + return OperandInfo.GetVarType(operation.Dest.Type); + } + } + else + { + for (int index = 0; index < operation.SourcesCount; index++) + { + if (operation.GetSource(index) == operand) + { + return InstructionInfo.GetSrcVarType(operation.Inst, index); + } + } + } + } + } + + return VariableType.S32; + } + + private static bool AreAllSourceTypesEqual(IAstNode[] sources, VariableType type) + { + foreach (IAstNode node in sources) + { + if (!(node is AstOperand operand)) + { + return false; + } + + if (operand.VarType != type) + { + return false; + } + } + + return true; + } + private static bool IsBranchInst(Instruction inst) { switch (inst) @@ -117,34 +211,31 @@ namespace Ryujinx.Graphics.Shader.StructuredIr return false; } - private static VariableType GetVarTypeFromUses(Operand dest) + private static bool IsBitwiseInst(Instruction inst) { - foreach (INode useNode in dest.UseOps) + switch (inst) { - if (useNode is Operation operation) - { - if (operation.Inst == Instruction.Copy) - { - if (operation.Dest.Type == OperandType.LocalVariable) - { - //TODO: Search through copies. - return VariableType.S32; - } - - return OperandInfo.GetVarType(operation.Dest.Type); - } - - for (int index = 0; index < operation.SourcesCount; index++) - { - if (operation.GetSource(index) == dest) - { - return InstructionInfo.GetSrcVarType(operation.Inst, index); - } - } - } + case Instruction.BitwiseAnd: + case Instruction.BitwiseExclusiveOr: + case Instruction.BitwiseNot: + case Instruction.BitwiseOr: + return true; } - return VariableType.S32; + return false; + } + + private static Instruction GetLogicalFromBitwiseInst(Instruction inst) + { + switch (inst) + { + case Instruction.BitwiseAnd: return Instruction.LogicalAnd; + case Instruction.BitwiseExclusiveOr: return Instruction.LogicalExclusiveOr; + case Instruction.BitwiseNot: return Instruction.LogicalNot; + case Instruction.BitwiseOr: return Instruction.LogicalOr; + } + + throw new ArgumentException($"Unexpected instruction \"{inst}\"."); } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgramContext.cs b/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgramContext.cs index 5741899e38..ebfffd6101 100644 --- a/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgramContext.cs +++ b/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgramContext.cs @@ -1,17 +1,24 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation; -using System; using System.Collections.Generic; using System.Linq; +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + namespace Ryujinx.Graphics.Shader.StructuredIr { class StructuredProgramContext { - private BasicBlock[] _blocks; + private HashSet _loopTails; - private List<(AstBlock Block, int EndIndex)> _blockStack; + private Stack<(AstBlock Block, int EndIndex)> _blockStack; - private Dictionary _locals; + private Dictionary _localsMap; + + private List _locals; + + private Dictionary _gotoTempAsgs; + + private List _gotos; private AstBlock _currBlock; @@ -19,17 +26,23 @@ namespace Ryujinx.Graphics.Shader.StructuredIr public StructuredProgramInfo Info { get; } - public StructuredProgramContext(BasicBlock[] blocks) + public StructuredProgramContext(int blocksCount) { - _blocks = blocks; + _loopTails = new HashSet(); - _blockStack = new List<(AstBlock, int)>(); + _blockStack = new Stack<(AstBlock, int)>(); - _locals = new Dictionary(); + _localsMap = new Dictionary(); + + _locals = new List(); + + _gotoTempAsgs = new Dictionary(); + + _gotos = new List(); _currBlock = new AstBlock(AstBlockType.Main); - _currEndIndex = blocks.Length; + _currEndIndex = blocksCount; Info = new StructuredProgramInfo(_currBlock); } @@ -38,7 +51,12 @@ namespace Ryujinx.Graphics.Shader.StructuredIr { while (_currEndIndex == block.Index) { - PopBlock(); + (_currBlock, _currEndIndex) = _blockStack.Pop(); + } + + if (_gotoTempAsgs.TryGetValue(block.Index, out AstAssignment gotoTempAsg)) + { + AddGotoTempReset(block, gotoTempAsg); } LookForDoWhileStatements(block); @@ -46,13 +64,15 @@ namespace Ryujinx.Graphics.Shader.StructuredIr public void LeaveBlock(BasicBlock block) { - LookForIfElseStatements(block); + LookForIfStatements(block); } private void LookForDoWhileStatements(BasicBlock block) { //Check if we have any predecessor whose index is greater than the //current block, this indicates a loop. + bool done = false; + foreach (BasicBlock predecessor in block.Predecessors.OrderByDescending(x => x.Index)) { if (predecessor.Index < block.Index) @@ -60,183 +80,177 @@ namespace Ryujinx.Graphics.Shader.StructuredIr break; } - if (predecessor.Index < _currEndIndex) + if (predecessor.Index < _currEndIndex && !done) { Operation branchOp = (Operation)predecessor.GetLastOp(); NewBlock(AstBlockType.DoWhile, branchOp, predecessor.Index + 1); + + _loopTails.Add(predecessor); + + done = true; } - else /* if (predecessor.Index >= _currEndIndex) */ + else { - //TODO: Handle unstructured case. + AddGotoTempReset(block, GetGotoTempAsg(block.Index)); + + break; } } } - private void LookForIfElseStatements(BasicBlock block) + private void LookForIfStatements(BasicBlock block) { - if (block.Branch == null || block.Branch.Index <= block.Index) + if (block.Branch == null) { return; } Operation branchOp = (Operation)block.GetLastOp(); - if (block.Branch.Index <= _currEndIndex) + bool isLoop = block.Branch.Index <= block.Index; + + AstOperation branch = _currBlock.Last as AstOperation; + + if (block.Branch.Index <= _currEndIndex && !isLoop) { - //If (conditional branch forward). + _currBlock.Remove(branch); + NewBlock(AstBlockType.If, branchOp, block.Branch.Index); } - else if (IsElseSkipBlock(block)) + else if (_loopTails.Contains(block)) { - //Else (unconditional branch forward). - int topBlockIndex = TopBlockIndexOnStack(); - - //We need to pop enough elements so that the one at - //"topBlockIndex" is the last one poped from the stack. - while (_blockStack.Count > topBlockIndex) - { - PopBlock(); - } - - NewBlock(AstBlockType.Else, branchOp, block.Branch.Index); + //Loop handled by "LookForDoWhileStatements". + //We can safely remove the branch as it was already taken care of. + _currBlock.Remove(branch); } - else if (IsElseSkipBlock(_blocks[_currEndIndex - 1]) && block.Branch == _blocks[_currEndIndex - 1].Branch) + else { - //If (conditional branch forward). - NewBlock(AstBlockType.If, branchOp, _currEndIndex); - } - else if (block.Branch.Index > _currEndIndex) - { - //TODO: Handle unstructured case. + AstAssignment gotoTempAsg = GetGotoTempAsg(block.Branch.Index); + + IAstNode cond = GetBranchCond(AstBlockType.DoWhile, branchOp); + + _currBlock.AddBefore(branch, Assign(gotoTempAsg.Destination, cond)); + + GotoStatement gotoStmt = new GotoStatement(branch, gotoTempAsg, isLoop); + + _gotos.Add(gotoStmt); } } - private bool IsElseSkipBlock(BasicBlock block) + private AstAssignment GetGotoTempAsg(int index) { - //Checks performed (in order): - //- The block should end with a branch. - //- The branch should be unconditional. - //- This should be the last block on the current (if) statement. - //- The statement before the else must be an if statement. - //- The branch target must be before or at (but not after) the end of the enclosing block. - if (block.Branch == null || block.Next != null || block.Index + 1 != _currEndIndex) + if (_gotoTempAsgs.TryGetValue(index, out AstAssignment gotoTempAsg)) { - return false; + return gotoTempAsg; } - (AstBlock parentBlock, int parentEndIndex) = _blockStack[TopBlockIndexOnStack()]; + AstOperand gotoTemp = NewTemp(VariableType.Bool); - if ((parentBlock.Nodes.Last.Value as AstBlock).Type != AstBlockType.If) + gotoTempAsg = Assign(gotoTemp, Const(IrConsts.False)); + + _gotoTempAsgs.Add(index, gotoTempAsg); + + return gotoTempAsg; + } + + private void AddGotoTempReset(BasicBlock block, AstAssignment gotoTempAsg) + { + AddNode(gotoTempAsg); + + //For block 0, we don't need to add the extra "reset" at the beggining, + //because it is already the first node to be executed on the shader, + //so it is reset to false by the "local" assignment anyway. + if (block.Index != 0) { - return false; + Info.MainBlock.AddFirst(Assign(gotoTempAsg.Destination, Const(IrConsts.False))); } - - return block.Branch.Index <= parentEndIndex; } private void NewBlock(AstBlockType type, Operation branchOp, int endIndex) { - Instruction inst = branchOp.Inst; - - if (type == AstBlockType.If) - { - //For ifs, the if block is only executed executed if the - //branch is not taken, that is, if the condition is false. - //So, we invert the condition to take that into account. - if (inst == Instruction.BranchIfFalse) - { - inst = Instruction.BranchIfTrue; - } - else if (inst == Instruction.BranchIfTrue) - { - inst = Instruction.BranchIfFalse; - } - } - - IAstNode cond; - - switch (inst) - { - case Instruction.Branch: - cond = new AstOperand(OperandType.Constant, IrConsts.True); - break; - - case Instruction.BranchIfFalse: - cond = new AstOperation(Instruction.BitwiseNot, GetOperandUse(branchOp.GetSource(0))); - break; - - case Instruction.BranchIfTrue: - cond = GetOperandUse(branchOp.GetSource(0)); - break; - - default: throw new InvalidOperationException($"Invalid branch instruction \"{branchOp.Inst}\"."); - } + NewBlock(type, GetBranchCond(type, branchOp), endIndex); + } + private void NewBlock(AstBlockType type, IAstNode cond, int endIndex) + { AstBlock childBlock = new AstBlock(type, cond); AddNode(childBlock); - PushBlock(); - - _currBlock = childBlock; + _blockStack.Push((_currBlock, _currEndIndex)); + _currBlock = childBlock; _currEndIndex = endIndex; } + private IAstNode GetBranchCond(AstBlockType type, Operation branchOp) + { + IAstNode cond; + + if (branchOp.Inst == Instruction.Branch) + { + cond = Const(type == AstBlockType.If ? IrConsts.False : IrConsts.True); + } + else + { + cond = GetOperandUse(branchOp.GetSource(0)); + + Instruction invInst = type == AstBlockType.If + ? Instruction.BranchIfTrue + : Instruction.BranchIfFalse; + + if (branchOp.Inst == invInst) + { + cond = new AstOperation(Instruction.LogicalNot, cond); + } + } + + return cond; + } + public void AddNode(IAstNode node) { - _currBlock.Nodes.AddLast(node); - } - - private void PushBlock() - { - _blockStack.Add((_currBlock, _currEndIndex)); - } - - private void PopBlock() - { - int lastIndex = _blockStack.Count - 1; - - (_currBlock, _currEndIndex) = _blockStack[lastIndex]; - - _blockStack.RemoveAt(lastIndex); - } - - private int TopBlockIndexOnStack() - { - for (int index = _blockStack.Count - 1; index >= 0; index--) - { - if (_blockStack[index].EndIndex > _currEndIndex) - { - return index; - } - } - - return 0; + _currBlock.Add(node); } public void PrependLocalDeclarations() { AstBlock mainBlock = Info.MainBlock; - LinkedListNode declNode = null; + AstDeclaration decl = null; - foreach (AstOperand operand in _locals.Values) + foreach (AstOperand operand in _locals) { - AstDeclaration astDecl = new AstDeclaration(operand); + AstDeclaration oldDecl = decl; - if (declNode == null) + decl = new AstDeclaration(operand); + + if (oldDecl == null) { - declNode = mainBlock.Nodes.AddFirst(astDecl); + mainBlock.AddFirst(decl); } else { - declNode = mainBlock.Nodes.AddAfter(declNode, astDecl); + mainBlock.AddAfter(oldDecl, decl); } } } + public GotoStatement[] GetGotos() + { + return _gotos.ToArray(); + } + + private AstOperand NewTemp(VariableType type) + { + AstOperand newTemp = Local(type); + + _locals.Add(newTemp); + + return newTemp; + } + public AstOperand GetOperandDef(Operand operand) { if (TryGetUserAttributeIndex(operand, out int attrIndex)) @@ -273,11 +287,13 @@ namespace Ryujinx.Graphics.Shader.StructuredIr return new AstOperand(operand); } - if (!_locals.TryGetValue(operand, out AstOperand astOperand)) + if (!_localsMap.TryGetValue(operand, out AstOperand astOperand)) { astOperand = new AstOperand(operand); - _locals.Add(operand, astOperand); + _localsMap.Add(operand, astOperand); + + _locals.Add(astOperand); } return astOperand;