Initial work

This commit is contained in:
gdk 2019-10-13 03:02:07 -03:00 committed by Thog
parent f617fb542a
commit 1876b346fe
518 changed files with 15170 additions and 12486 deletions

View file

@ -0,0 +1,35 @@
using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper;
namespace Ryujinx.Graphics.Shader.StructuredIr
{
class AstAssignment : AstNode
{
public IAstNode Destination { get; }
private IAstNode _source;
public IAstNode Source
{
get
{
return _source;
}
set
{
RemoveUse(_source, this);
AddUse(value, this);
_source = value;
}
}
public AstAssignment(IAstNode destination, IAstNode source)
{
Destination = destination;
Source = source;
AddDef(destination, this);
}
}
}

View file

@ -0,0 +1,116 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System;
using System.Collections;
using System.Collections.Generic;
using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper;
namespace Ryujinx.Graphics.Shader.StructuredIr
{
class AstBlock : AstNode, IEnumerable<IAstNode>
{
public AstBlockType Type { get; private set; }
private IAstNode _condition;
public IAstNode Condition
{
get
{
return _condition;
}
set
{
RemoveUse(_condition, this);
AddUse(value, this);
_condition = value;
}
}
private LinkedList<IAstNode> _nodes;
public IAstNode First => _nodes.First?.Value;
public int Count => _nodes.Count;
public AstBlock(AstBlockType type, IAstNode condition = null)
{
Type = type;
Condition = condition;
_nodes = new LinkedList<IAstNode>();
}
public void Add(IAstNode node)
{
Add(node, _nodes.AddLast(node));
}
public void AddFirst(IAstNode node)
{
Add(node, _nodes.AddFirst(node));
}
public void AddBefore(IAstNode next, IAstNode node)
{
Add(node, _nodes.AddBefore(next.LLNode, node));
}
public void AddAfter(IAstNode prev, IAstNode node)
{
Add(node, _nodes.AddAfter(prev.LLNode, node));
}
private void Add(IAstNode node, LinkedListNode<IAstNode> 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 void TurnIntoIf(IAstNode cond)
{
Condition = cond;
Type = AstBlockType.If;
}
public void TurnIntoElseIf()
{
Type = AstBlockType.ElseIf;
}
public IEnumerator<IAstNode> GetEnumerator()
{
return _nodes.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.Graphics.Shader.StructuredIr
{
enum AstBlockType
{
DoWhile,
If,
Else,
ElseIf,
Main,
While
}
}

View file

@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper;
namespace Ryujinx.Graphics.Shader.StructuredIr
{
class AstBlockVisitor
{
public AstBlock Block { get; private set; }
public class BlockVisitationEventArgs : EventArgs
{
public AstBlock Block { get; }
public BlockVisitationEventArgs(AstBlock block)
{
Block = block;
}
}
public event EventHandler<BlockVisitationEventArgs> BlockEntered;
public event EventHandler<BlockVisitationEventArgs> BlockLeft;
public AstBlockVisitor(AstBlock mainBlock)
{
Block = mainBlock;
}
public IEnumerable<IAstNode> Visit()
{
IAstNode node = Block.First;
while (node != null)
{
// We reached a child block, visit the nodes inside.
while (node is AstBlock childBlock)
{
Block = childBlock;
node = childBlock.First;
BlockEntered?.Invoke(this, new BlockVisitationEventArgs(Block));
}
// Node may be null, if the block is empty.
if (node != null)
{
IAstNode next = Next(node);
yield return node;
node = next;
}
// We reached the end of the list, go up on tree to the parent blocks.
while (node == null && Block.Type != AstBlockType.Main)
{
BlockLeft?.Invoke(this, new BlockVisitationEventArgs(Block));
node = Next(Block);
Block = Block.Parent;
}
}
}
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.Graphics.Shader.StructuredIr
{
class AstComment : AstNode
{
public string Comment { get; }
public AstComment(string comment)
{
Comment = comment;
}
}
}

View file

@ -0,0 +1,73 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
namespace Ryujinx.Graphics.Shader.StructuredIr
{
static class AstHelper
{
public static void AddUse(IAstNode node, IAstNode parent)
{
if (node is AstOperand operand && operand.Type == OperandType.LocalVariable)
{
operand.Uses.Add(parent);
}
}
public static void AddDef(IAstNode node, IAstNode parent)
{
if (node is AstOperand operand && operand.Type == OperandType.LocalVariable)
{
operand.Defs.Add(parent);
}
}
public static void RemoveUse(IAstNode node, IAstNode parent)
{
if (node is AstOperand operand && operand.Type == OperandType.LocalVariable)
{
operand.Uses.Remove(parent);
}
}
public static void RemoveDef(IAstNode node, IAstNode parent)
{
if (node is AstOperand operand && operand.Type == OperandType.LocalVariable)
{
operand.Defs.Remove(parent);
}
}
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;
}
public static IAstNode InverseCond(IAstNode cond)
{
return new AstOperation(Instruction.LogicalNot, cond);
}
public static IAstNode Next(IAstNode node)
{
return node.LLNode.Next?.Value;
}
public static IAstNode Previous(IAstNode node)
{
return node.LLNode.Previous?.Value;
}
}
}

View file

@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.StructuredIr
{
class AstNode : IAstNode
{
public AstBlock Parent { get; set; }
public LinkedListNode<IAstNode> LLNode { get; set; }
}
}

View file

@ -0,0 +1,52 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.StructuredIr
{
class AstOperand : AstNode
{
public HashSet<IAstNode> Defs { get; }
public HashSet<IAstNode> Uses { get; }
public OperandType Type { get; }
public VariableType VarType { get; set; }
public InterpolationQualifier Interpolation { get; }
public int Value { get; }
public int CbufSlot { get; }
public int CbufOffset { get; }
private AstOperand()
{
Defs = new HashSet<IAstNode>();
Uses = new HashSet<IAstNode>();
VarType = VariableType.S32;
}
public AstOperand(Operand operand) : this()
{
Type = operand.Type;
Interpolation = operand.Interpolation;
if (Type == OperandType.ConstantBuffer)
{
CbufSlot = operand.GetCbufSlot();
CbufOffset = operand.GetCbufOffset();
}
else
{
Value = operand.Value;
}
}
public AstOperand(OperandType type, int value = 0) : this()
{
Type = type;
Value = value;
}
}
}

View file

@ -0,0 +1,49 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper;
namespace Ryujinx.Graphics.Shader.StructuredIr
{
class AstOperation : AstNode
{
public Instruction Inst { get; }
public int ComponentMask { get; }
private IAstNode[] _sources;
public int SourcesCount => _sources.Length;
public AstOperation(Instruction inst, params IAstNode[] sources)
{
Inst = inst;
_sources = sources;
foreach (IAstNode source in sources)
{
AddUse(source, this);
}
ComponentMask = 1;
}
public AstOperation(Instruction inst, int compMask, params IAstNode[] sources) : this(inst, sources)
{
ComponentMask = compMask;
}
public IAstNode GetSource(int index)
{
return _sources[index];
}
public void SetSource(int index, IAstNode source)
{
RemoveUse(_sources[index], this);
AddUse(source, this);
_sources[index] = source;
}
}
}

View file

@ -0,0 +1,155 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.Translation;
using System.Collections.Generic;
using System.Linq;
using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper;
namespace Ryujinx.Graphics.Shader.StructuredIr
{
static class AstOptimizer
{
public static void Optimize(StructuredProgramContext context)
{
AstBlock mainBlock = context.Info.MainBlock;
// When debug mode is enabled, we disable expression propagation
// (this makes comparison with the disassembly easier).
if ((context.Config.Flags & TranslationFlags.DebugMode) == 0)
{
AstBlockVisitor visitor = new AstBlockVisitor(mainBlock);
foreach (IAstNode node in visitor.Visit())
{
if (node is AstAssignment assignment && assignment.Destination is AstOperand propVar)
{
bool isWorthPropagating = propVar.Uses.Count == 1 || IsWorthPropagating(assignment.Source);
if (propVar.Defs.Count == 1 && isWorthPropagating)
{
PropagateExpression(propVar, assignment.Source);
}
if (propVar.Type == OperandType.LocalVariable && propVar.Uses.Count == 0)
{
visitor.Block.Remove(assignment);
context.Info.Locals.Remove(propVar);
}
}
}
}
RemoveEmptyBlocks(mainBlock);
}
private static bool IsWorthPropagating(IAstNode source)
{
if (!(source is AstOperation srcOp))
{
return false;
}
if (!InstructionInfo.IsUnary(srcOp.Inst))
{
return false;
}
return srcOp.GetSource(0) is AstOperand || srcOp.Inst == Instruction.Copy;
}
private static void PropagateExpression(AstOperand propVar, IAstNode source)
{
IAstNode[] uses = propVar.Uses.ToArray();
foreach (IAstNode useNode in uses)
{
if (useNode is AstBlock useBlock)
{
useBlock.Condition = source;
}
else if (useNode is AstOperation useOperation)
{
for (int srcIndex = 0; srcIndex < useOperation.SourcesCount; srcIndex++)
{
if (useOperation.GetSource(srcIndex) == propVar)
{
useOperation.SetSource(srcIndex, source);
}
}
}
else if (useNode is AstAssignment useAssignment)
{
useAssignment.Source = source;
}
}
}
private static void RemoveEmptyBlocks(AstBlock mainBlock)
{
Queue<AstBlock> pending = new Queue<AstBlock>();
pending.Enqueue(mainBlock);
while (pending.TryDequeue(out AstBlock block))
{
foreach (IAstNode node in block)
{
if (node is AstBlock childBlock)
{
pending.Enqueue(childBlock);
}
}
AstBlock parent = block.Parent;
if (parent == null)
{
continue;
}
AstBlock nextBlock = Next(block) as AstBlock;
bool hasElse = nextBlock != null && nextBlock.Type == AstBlockType.Else;
bool isIf = block.Type == AstBlockType.If;
if (block.Count == 0)
{
if (isIf)
{
if (hasElse)
{
nextBlock.TurnIntoIf(InverseCond(block.Condition));
}
parent.Remove(block);
}
else if (block.Type == AstBlockType.Else)
{
parent.Remove(block);
}
}
else if (isIf && parent.Type == AstBlockType.Else && parent.Count == (hasElse ? 2 : 1))
{
AstBlock parentOfParent = parent.Parent;
parent.Remove(block);
parentOfParent.AddAfter(parent, block);
if (hasElse)
{
parent.Remove(nextBlock);
parentOfParent.AddAfter(block, nextBlock);
}
parentOfParent.Remove(parent);
block.TurnIntoElseIf();
}
}
}
}
}

View file

@ -0,0 +1,25 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
namespace Ryujinx.Graphics.Shader.StructuredIr
{
class AstTextureOperation : AstOperation
{
public TextureTarget Target { get; }
public TextureFlags Flags { get; }
public int Handle { get; }
public AstTextureOperation(
Instruction inst,
TextureTarget target,
TextureFlags flags,
int handle,
int compMask,
params IAstNode[] sources) : base(inst, compMask, sources)
{
Target = target;
Flags = flags;
Handle = handle;
}
}
}

View file

@ -0,0 +1,459 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System;
using System.Collections.Generic;
using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper;
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.SourcesCount == 2)
{
if (operation.Inst == inst && IsSameCond(operation.GetSource(1), newCond))
{
return true;
}
node = operation.GetSource(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);
}
// Modify the do-while condition to allow it to continue.
if (!ContainsCondComb(block.Condition, Instruction.LogicalOr, stmt.Condition))
{
block.OrCondition(stmt.Condition);
}
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.GetSource(0);
rCond = rCondOp.GetSource(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<AstBlock> path = new List<AstBlock>();
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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.StructuredIr
{
interface IAstNode
{
AstBlock Parent { get; set; }
LinkedListNode<IAstNode> LLNode { get; set; }
}
}

View file

@ -0,0 +1,150 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System;
namespace Ryujinx.Graphics.Shader.StructuredIr
{
static class InstructionInfo
{
private struct InstInfo
{
public VariableType DestType { get; }
public VariableType[] SrcTypes { get; }
public InstInfo(VariableType destType, params VariableType[] srcTypes)
{
DestType = destType;
SrcTypes = srcTypes;
}
}
private static InstInfo[] _infoTbl;
static InstructionInfo()
{
_infoTbl = new InstInfo[(int)Instruction.Count];
// Inst Destination type Source 1 type Source 2 type Source 3 type Source 4 type
Add(Instruction.Absolute, VariableType.Scalar, VariableType.Scalar);
Add(Instruction.Add, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar);
Add(Instruction.BitfieldExtractS32, VariableType.S32, VariableType.S32, VariableType.S32, VariableType.S32);
Add(Instruction.BitfieldExtractU32, VariableType.U32, VariableType.U32, VariableType.S32, VariableType.S32);
Add(Instruction.BitfieldInsert, VariableType.Int, VariableType.Int, VariableType.Int, VariableType.S32, VariableType.S32);
Add(Instruction.BitfieldReverse, VariableType.Int, VariableType.Int);
Add(Instruction.BitwiseAnd, VariableType.Int, VariableType.Int, VariableType.Int);
Add(Instruction.BitwiseExclusiveOr, VariableType.Int, VariableType.Int, VariableType.Int);
Add(Instruction.BitwiseNot, VariableType.Int, VariableType.Int);
Add(Instruction.BitwiseOr, VariableType.Int, VariableType.Int, VariableType.Int);
Add(Instruction.BranchIfTrue, VariableType.None, VariableType.Bool);
Add(Instruction.BranchIfFalse, VariableType.None, VariableType.Bool);
Add(Instruction.Ceiling, VariableType.F32, VariableType.F32, VariableType.F32);
Add(Instruction.Clamp, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar);
Add(Instruction.ClampU32, VariableType.U32, VariableType.U32, VariableType.U32, VariableType.U32);
Add(Instruction.CompareEqual, VariableType.Bool, VariableType.Scalar, VariableType.Scalar);
Add(Instruction.CompareGreater, VariableType.Bool, VariableType.Scalar, VariableType.Scalar);
Add(Instruction.CompareGreaterOrEqual, VariableType.Bool, VariableType.Scalar, VariableType.Scalar);
Add(Instruction.CompareGreaterOrEqualU32, VariableType.Bool, VariableType.U32, VariableType.U32);
Add(Instruction.CompareGreaterU32, VariableType.Bool, VariableType.U32, VariableType.U32);
Add(Instruction.CompareLess, VariableType.Bool, VariableType.Scalar, VariableType.Scalar);
Add(Instruction.CompareLessOrEqual, VariableType.Bool, VariableType.Scalar, VariableType.Scalar);
Add(Instruction.CompareLessOrEqualU32, VariableType.Bool, VariableType.U32, VariableType.U32);
Add(Instruction.CompareLessU32, VariableType.Bool, VariableType.U32, VariableType.U32);
Add(Instruction.CompareNotEqual, VariableType.Bool, VariableType.Scalar, VariableType.Scalar);
Add(Instruction.ConditionalSelect, VariableType.Scalar, VariableType.Bool, VariableType.Scalar, VariableType.Scalar);
Add(Instruction.ConvertFPToS32, VariableType.S32, VariableType.F32);
Add(Instruction.ConvertS32ToFP, VariableType.F32, VariableType.S32);
Add(Instruction.ConvertU32ToFP, VariableType.F32, VariableType.U32);
Add(Instruction.Cosine, VariableType.Scalar, VariableType.Scalar);
Add(Instruction.Divide, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar);
Add(Instruction.ExponentB2, VariableType.Scalar, VariableType.Scalar);
Add(Instruction.Floor, VariableType.F32, VariableType.F32);
Add(Instruction.FusedMultiplyAdd, VariableType.F32, VariableType.F32, VariableType.F32, VariableType.F32);
Add(Instruction.IsNan, VariableType.Bool, VariableType.F32);
Add(Instruction.LoadAttribute, VariableType.F32, VariableType.S32, VariableType.S32);
Add(Instruction.LoadConstant, VariableType.F32, VariableType.S32, VariableType.S32);
Add(Instruction.LoadGlobal, VariableType.F32, VariableType.S32, VariableType.S32);
Add(Instruction.LoadLocal, VariableType.F32, VariableType.S32);
Add(Instruction.LoadStorage, 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);
Add(Instruction.Maximum, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar);
Add(Instruction.MaximumU32, VariableType.U32, VariableType.U32, VariableType.U32);
Add(Instruction.Minimum, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar);
Add(Instruction.MinimumU32, VariableType.U32, VariableType.U32, VariableType.U32);
Add(Instruction.Multiply, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar);
Add(Instruction.Negate, VariableType.Scalar, VariableType.Scalar);
Add(Instruction.PackHalf2x16, VariableType.U32, VariableType.F32, VariableType.F32);
Add(Instruction.ReciprocalSquareRoot, VariableType.Scalar, VariableType.Scalar);
Add(Instruction.Sine, VariableType.Scalar, VariableType.Scalar);
Add(Instruction.SquareRoot, VariableType.Scalar, VariableType.Scalar);
Add(Instruction.StoreGlobal, VariableType.None, VariableType.S32, VariableType.S32, VariableType.F32);
Add(Instruction.StoreLocal, VariableType.None, VariableType.S32, VariableType.F32);
Add(Instruction.StoreStorage, VariableType.None, VariableType.S32, VariableType.S32, VariableType.F32);
Add(Instruction.Subtract, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar);
Add(Instruction.TextureSample, VariableType.F32);
Add(Instruction.TextureSize, VariableType.S32, VariableType.S32, VariableType.S32);
Add(Instruction.Truncate, VariableType.F32, VariableType.F32);
Add(Instruction.UnpackHalf2x16, VariableType.F32, VariableType.U32);
}
private static void Add(Instruction inst, VariableType destType, params VariableType[] srcTypes)
{
_infoTbl[(int)inst] = new InstInfo(destType, srcTypes);
}
public static VariableType GetDestVarType(Instruction inst)
{
return GetFinalVarType(_infoTbl[(int)(inst & Instruction.Mask)].DestType, inst);
}
public static VariableType GetSrcVarType(Instruction inst, int index)
{
if (inst == Instruction.TextureSample)
{
return VariableType.F32;
}
return GetFinalVarType(_infoTbl[(int)(inst & Instruction.Mask)].SrcTypes[index], inst);
}
private static VariableType GetFinalVarType(VariableType type, Instruction inst)
{
if (type == VariableType.Scalar)
{
return (inst & Instruction.FP) != 0
? VariableType.F32
: VariableType.S32;
}
else if (type == VariableType.Int)
{
return VariableType.S32;
}
else if (type == VariableType.None)
{
throw new ArgumentException($"Invalid operand for instruction \"{inst}\".");
}
return type;
}
public static bool IsUnary(Instruction inst)
{
if (inst == Instruction.Copy)
{
return true;
}
else if (inst == Instruction.TextureSample)
{
return false;
}
return _infoTbl[(int)(inst & Instruction.Mask)].SrcTypes.Length == 1;
}
}
}

View file

@ -0,0 +1,33 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System;
namespace Ryujinx.Graphics.Shader.StructuredIr
{
static class OperandInfo
{
public static VariableType GetVarType(AstOperand operand)
{
if (operand.Type == OperandType.LocalVariable)
{
return operand.VarType;
}
else
{
return GetVarType(operand.Type);
}
}
public static VariableType GetVarType(OperandType type)
{
switch (type)
{
case OperandType.Attribute: return VariableType.F32;
case OperandType.Constant: return VariableType.S32;
case OperandType.ConstantBuffer: return VariableType.F32;
case OperandType.Undefined: return VariableType.S32;
}
throw new ArgumentException($"Invalid operand type \"{type}\".");
}
}
}

View file

@ -0,0 +1,74 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.StructuredIr
{
static class PhiFunctions
{
public static void Remove(BasicBlock[] blocks)
{
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;
if (!(node.Value is PhiNode phi))
{
node = nextNode;
continue;
}
for (int index = 0; index < phi.SourcesCount; index++)
{
Operand src = phi.GetSource(index);
BasicBlock srcBlock = phi.GetBlock(index);
Operation copyOp = new Operation(Instruction.Copy, phi.Dest, src);
AddBeforeBranch(srcBlock, copyOp);
}
block.Operations.Remove(node);
node = nextNode;
}
}
}
private static void AddBeforeBranch(BasicBlock block, INode node)
{
INode lastOp = block.GetLastOp();
if (lastOp is Operation operation && IsControlFlowInst(operation.Inst))
{
block.Operations.AddBefore(block.Operations.Last, node);
}
else
{
block.Operations.AddLast(node);
}
}
private static bool IsControlFlowInst(Instruction inst)
{
switch (inst)
{
case Instruction.Branch:
case Instruction.BranchIfFalse:
case Instruction.BranchIfTrue:
case Instruction.Discard:
case Instruction.Return:
return true;
}
return false;
}
}
}

View file

@ -0,0 +1,281 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.StructuredIr
{
static class StructuredProgram
{
public static StructuredProgramInfo MakeStructuredProgram(BasicBlock[] blocks, ShaderConfig config)
{
PhiFunctions.Remove(blocks);
StructuredProgramContext context = new StructuredProgramContext(blocks.Length, config);
for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++)
{
BasicBlock block = blocks[blkIndex];
context.EnterBlock(block);
foreach (INode node in block.Operations)
{
Operation operation = (Operation)node;
if (IsBranchInst(operation.Inst))
{
context.LeaveBlock(block, operation);
}
else
{
AddOperation(context, operation);
}
}
}
GotoElimination.Eliminate(context.GetGotos());
AstOptimizer.Optimize(context);
return context.Info;
}
private static void AddOperation(StructuredProgramContext context, Operation operation)
{
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)
{
AstOperand dest = context.GetOperandDef(operation.Dest);
if (inst == Instruction.LoadConstant)
{
Operand slot = operation.GetSource(0);
if (slot.Type != OperandType.Constant)
{
throw new InvalidOperationException("Found load with non-constant constant buffer slot.");
}
context.Info.CBuffers.Add(slot.Value);
}
else if (inst == Instruction.LoadStorage)
{
Operand slot = operation.GetSource(0);
if (slot.Type != OperandType.Constant)
{
throw new InvalidOperationException("Found load or store with non-constant storage buffer slot.");
}
context.Info.SBuffers.Add(slot.Value);
}
AstAssignment assignment;
// 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))
{
inst = GetLogicalFromBitwiseInst(inst);
}
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);
}
int componentMask = 1 << operation.ComponentIndex;
IAstNode source;
if (operation is TextureOperation texOp)
{
AstTextureOperation astTexOp = new AstTextureOperation(
inst,
texOp.Target,
texOp.Flags,
texOp.Handle,
componentMask,
sources);
context.Info.Samplers.Add(astTexOp);
source = astTexOp;
}
else if (!isCopy)
{
source = new AstOperation(inst, componentMask, sources);
}
else
{
source = sources[0];
}
assignment = new AstAssignment(dest, source);
context.AddNode(assignment);
}
else if (operation.Inst == Instruction.Comment)
{
context.AddNode(new AstComment(((CommentNode)operation).Comment));
}
else
{
if (inst == Instruction.StoreStorage)
{
Operand slot = operation.GetSource(0);
if (slot.Type != OperandType.Constant)
{
throw new InvalidOperationException("Found load or store with non-constant storage buffer slot.");
}
context.Info.SBuffers.Add(slot.Value);
}
context.AddNode(new AstOperation(inst, sources));
}
}
private static VariableType GetVarTypeFromUses(Operand dest)
{
HashSet<Operand> visited = new HashSet<Operand>();
Queue<Operand> pending = new Queue<Operand>();
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)
{
case Instruction.Branch:
case Instruction.BranchIfFalse:
case Instruction.BranchIfTrue:
return true;
}
return false;
}
private static bool IsBitwiseInst(Instruction inst)
{
switch (inst)
{
case Instruction.BitwiseAnd:
case Instruction.BitwiseExclusiveOr:
case Instruction.BitwiseNot:
case Instruction.BitwiseOr:
return true;
}
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}\".");
}
}
}

View file

@ -0,0 +1,303 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.Translation;
using System.Collections.Generic;
using System.Linq;
using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper;
namespace Ryujinx.Graphics.Shader.StructuredIr
{
class StructuredProgramContext
{
private HashSet<BasicBlock> _loopTails;
private Stack<(AstBlock Block, int EndIndex)> _blockStack;
private Dictionary<Operand, AstOperand> _localsMap;
private Dictionary<int, AstAssignment> _gotoTempAsgs;
private List<GotoStatement> _gotos;
private AstBlock _currBlock;
private int _currEndIndex;
public StructuredProgramInfo Info { get; }
public ShaderConfig Config { get; }
public StructuredProgramContext(int blocksCount, ShaderConfig config)
{
_loopTails = new HashSet<BasicBlock>();
_blockStack = new Stack<(AstBlock, int)>();
_localsMap = new Dictionary<Operand, AstOperand>();
_gotoTempAsgs = new Dictionary<int, AstAssignment>();
_gotos = new List<GotoStatement>();
_currBlock = new AstBlock(AstBlockType.Main);
_currEndIndex = blocksCount;
Info = new StructuredProgramInfo(_currBlock);
Config = config;
}
public void EnterBlock(BasicBlock block)
{
while (_currEndIndex == block.Index)
{
(_currBlock, _currEndIndex) = _blockStack.Pop();
}
if (_gotoTempAsgs.TryGetValue(block.Index, out AstAssignment gotoTempAsg))
{
AddGotoTempReset(block, gotoTempAsg);
}
LookForDoWhileStatements(block);
}
public void LeaveBlock(BasicBlock block, Operation branchOp)
{
LookForIfStatements(block, branchOp);
}
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)
{
break;
}
if (predecessor.Index < _currEndIndex && !done)
{
Operation branchOp = (Operation)predecessor.GetLastOp();
NewBlock(AstBlockType.DoWhile, branchOp, predecessor.Index + 1);
_loopTails.Add(predecessor);
done = true;
}
else
{
AddGotoTempReset(block, GetGotoTempAsg(block.Index));
break;
}
}
}
private void LookForIfStatements(BasicBlock block, Operation branchOp)
{
if (block.Branch == null)
{
return;
}
bool isLoop = block.Branch.Index <= block.Index;
if (block.Branch.Index <= _currEndIndex && !isLoop)
{
NewBlock(AstBlockType.If, branchOp, block.Branch.Index);
}
else if (!_loopTails.Contains(block))
{
AstAssignment gotoTempAsg = GetGotoTempAsg(block.Branch.Index);
IAstNode cond = GetBranchCond(AstBlockType.DoWhile, branchOp);
AddNode(Assign(gotoTempAsg.Destination, cond));
AstOperation branch = new AstOperation(branchOp.Inst);
AddNode(branch);
GotoStatement gotoStmt = new GotoStatement(branch, gotoTempAsg, isLoop);
_gotos.Add(gotoStmt);
}
}
private AstAssignment GetGotoTempAsg(int index)
{
if (_gotoTempAsgs.TryGetValue(index, out AstAssignment gotoTempAsg))
{
return gotoTempAsg;
}
AstOperand gotoTemp = NewTemp(VariableType.Bool);
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 beginning,
// 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)
{
Info.MainBlock.AddFirst(Assign(gotoTempAsg.Destination, Const(IrConsts.False)));
}
}
private void NewBlock(AstBlockType type, Operation branchOp, int endIndex)
{
NewBlock(type, GetBranchCond(type, branchOp), endIndex);
}
private void NewBlock(AstBlockType type, IAstNode cond, int endIndex)
{
AstBlock childBlock = new AstBlock(type, cond);
AddNode(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.Add(node);
}
public GotoStatement[] GetGotos()
{
return _gotos.ToArray();
}
private AstOperand NewTemp(VariableType type)
{
AstOperand newTemp = Local(type);
Info.Locals.Add(newTemp);
return newTemp;
}
public AstOperand GetOperandDef(Operand operand)
{
if (TryGetUserAttributeIndex(operand, out int attrIndex))
{
Info.OAttributes.Add(attrIndex);
}
return GetOperand(operand);
}
public AstOperand GetOperandUse(Operand operand)
{
if (TryGetUserAttributeIndex(operand, out int attrIndex))
{
Info.IAttributes.Add(attrIndex);
Info.InterpolationQualifiers[attrIndex] = operand.Interpolation;
}
else if (operand.Type == OperandType.Attribute && operand.Value == AttributeConsts.InstanceId)
{
Info.UsesInstanceId = true;
}
else if (operand.Type == OperandType.ConstantBuffer)
{
Info.CBuffers.Add(operand.GetCbufSlot());
}
return GetOperand(operand);
}
private AstOperand GetOperand(Operand operand)
{
if (operand == null)
{
return null;
}
if (operand.Type != OperandType.LocalVariable)
{
return new AstOperand(operand);
}
if (!_localsMap.TryGetValue(operand, out AstOperand astOperand))
{
astOperand = new AstOperand(operand);
_localsMap.Add(operand, astOperand);
Info.Locals.Add(astOperand);
}
return astOperand;
}
private static bool TryGetUserAttributeIndex(Operand operand, out int attrIndex)
{
if (operand.Type == OperandType.Attribute)
{
if (operand.Value >= AttributeConsts.UserAttributeBase &&
operand.Value < AttributeConsts.UserAttributeEnd)
{
attrIndex = (operand.Value - AttributeConsts.UserAttributeBase) >> 4;
return true;
}
else if (operand.Value >= AttributeConsts.FragmentOutputColorBase &&
operand.Value < AttributeConsts.FragmentOutputColorEnd)
{
attrIndex = (operand.Value - AttributeConsts.FragmentOutputColorBase) >> 4;
return true;
}
}
attrIndex = 0;
return false;
}
}
}

View file

@ -0,0 +1,40 @@
using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.StructuredIr
{
class StructuredProgramInfo
{
public AstBlock MainBlock { get; }
public HashSet<AstOperand> Locals { get; }
public HashSet<int> CBuffers { get; }
public HashSet<int> SBuffers { get; }
public HashSet<int> IAttributes { get; }
public HashSet<int> OAttributes { get; }
public InterpolationQualifier[] InterpolationQualifiers { get; }
public bool UsesInstanceId { get; set; }
public HashSet<AstTextureOperation> Samplers { get; }
public StructuredProgramInfo(AstBlock mainBlock)
{
MainBlock = mainBlock;
Locals = new HashSet<AstOperand>();
CBuffers = new HashSet<int>();
SBuffers = new HashSet<int>();
IAttributes = new HashSet<int>();
OAttributes = new HashSet<int>();
InterpolationQualifiers = new InterpolationQualifier[32];
Samplers = new HashSet<AstTextureOperation>();
}
}
}

View file

@ -0,0 +1,13 @@
namespace Ryujinx.Graphics.Shader.StructuredIr
{
enum VariableType
{
None,
Bool,
Scalar,
Int,
F32,
S32,
U32
}
}