diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
index 8e8389a3..cd20a5a2 100644
--- a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
@@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
///
/// Version of the codegen (to be changed when codegen or guest format change).
///
- private const ulong ShaderCodeGenVersion = 2163;
+ private const ulong ShaderCodeGenVersion = 2200;
// Progress reporting helpers
private volatile int _shaderCount;
diff --git a/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs b/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs
index 7352b18b..f462cedb 100644
--- a/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs
+++ b/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs
@@ -5,66 +5,6 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
class BindlessElimination
{
- private static Operation FindBranchSource(BasicBlock block)
- {
- foreach (BasicBlock sourceBlock in block.Predecessors)
- {
- if (sourceBlock.Operations.Count > 0)
- {
- Operation lastOp = sourceBlock.Operations.Last.Value as Operation;
-
- if (lastOp != null &&
- ((sourceBlock.Next == block && lastOp.Inst == Instruction.BranchIfFalse) ||
- (sourceBlock.Branch == block && lastOp.Inst == Instruction.BranchIfTrue)))
- {
- return lastOp;
- }
- }
- }
-
- return null;
- }
-
- private static bool BlockConditionsMatch(BasicBlock currentBlock, BasicBlock queryBlock)
- {
- // Check if all the conditions for the query block are satisfied by the current block.
- // Just checks the top-most conditional for now.
-
- Operation currentBranch = FindBranchSource(currentBlock);
- Operation queryBranch = FindBranchSource(queryBlock);
-
- Operand currentCondition = currentBranch?.GetSource(0);
- Operand queryCondition = queryBranch?.GetSource(0);
-
- // The condition should be the same operand instance.
-
- return currentBranch != null && queryBranch != null &&
- currentBranch.Inst == queryBranch.Inst &&
- currentCondition == queryCondition;
- }
-
- private static Operand FindLastOperation(Operand source, BasicBlock block)
- {
- if (source.AsgOp is PhiNode phiNode)
- {
- // This source can have a different value depending on a previous branch.
- // Ensure that conditions met for that branch are also met for the current one.
- // Prefer the latest sources for the phi node.
-
- for (int i = phiNode.SourcesCount - 1; i >= 0; i--)
- {
- BasicBlock phiBlock = phiNode.GetBlock(i);
-
- if (BlockConditionsMatch(block, phiBlock))
- {
- return phiNode.GetSource(i);
- }
- }
- }
-
- return source;
- }
-
public static void RunPass(BasicBlock block, ShaderConfig config)
{
// We can turn a bindless into regular access by recognizing the pattern
@@ -89,7 +29,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
texOp.Inst == Instruction.TextureSample ||
texOp.Inst == Instruction.TextureSize)
{
- Operand bindlessHandle = FindLastOperation(texOp.GetSource(0), block);
+ Operand bindlessHandle = Utils.FindLastOperation(texOp.GetSource(0), block);
if (bindlessHandle.Type == OperandType.ConstantBuffer)
{
@@ -107,8 +47,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
continue;
}
- Operand src0 = FindLastOperation(handleCombineOp.GetSource(0), block);
- Operand src1 = FindLastOperation(handleCombineOp.GetSource(1), block);
+ Operand src0 = Utils.FindLastOperation(handleCombineOp.GetSource(0), block);
+ Operand src1 = Utils.FindLastOperation(handleCombineOp.GetSource(1), block);
if (src0.Type != OperandType.ConstantBuffer ||
src1.Type != OperandType.ConstantBuffer || src0.GetCbufSlot() != src1.GetCbufSlot())
@@ -120,7 +60,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
}
else if (texOp.Inst == Instruction.ImageLoad || texOp.Inst == Instruction.ImageStore)
{
- Operand src0 = FindLastOperation(texOp.GetSource(0), block);
+ Operand src0 = Utils.FindLastOperation(texOp.GetSource(0), block);
if (src0.Type == OperandType.ConstantBuffer)
{
diff --git a/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs b/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs
index 254bd0b3..a3417544 100644
--- a/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs
+++ b/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs
@@ -25,32 +25,29 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
Operand source = operation.GetSource(0);
- if (source.AsgOp is Operation asgOperation)
+ int storageIndex = SearchForStorageBase(block, source, sbStart, sbEnd);
+
+ if (storageIndex >= 0)
{
- int storageIndex = SearchForStorageBase(asgOperation, sbStart, sbEnd);
+ // Storage buffers are implemented using global memory access.
+ // If we know from where the base address of the access is loaded,
+ // we can guess which storage buffer it is accessing.
+ // We can then replace the global memory access with a storage
+ // buffer access.
+ node = ReplaceGlobalWithStorage(node, config, storageIndex);
+ }
+ else if (config.Stage == ShaderStage.Compute && operation.Inst == Instruction.LoadGlobal)
+ {
+ // Here we effectively try to replace a LDG instruction with LDC.
+ // The hardware only supports a limited amount of constant buffers
+ // so NVN "emulates" more constant buffers using global memory access.
+ // Here we try to replace the global access back to a constant buffer
+ // load.
+ storageIndex = SearchForStorageBase(block, source, UbeBaseOffset, UbeBaseOffset + UbeDescsSize);
if (storageIndex >= 0)
{
- // Storage buffers are implemented using global memory access.
- // If we know from where the base address of the access is loaded,
- // we can guess which storage buffer it is accessing.
- // We can then replace the global memory access with a storage
- // buffer access.
- node = ReplaceGlobalWithStorage(node, config, storageIndex);
- }
- else if (config.Stage == ShaderStage.Compute && operation.Inst == Instruction.LoadGlobal)
- {
- // Here we effectively try to replace a LDG instruction with LDC.
- // The hardware only supports a limited amount of constant buffers
- // so NVN "emulates" more constant buffers using global memory access.
- // Here we try to replace the global access back to a constant buffer
- // load.
- storageIndex = SearchForStorageBase(asgOperation, UbeBaseOffset, UbeBaseOffset + UbeDescsSize);
-
- if (storageIndex >= 0)
- {
- node = ReplaceLdgWithLdc(node, config, storageIndex);
- }
+ node = ReplaceLdgWithLdc(node, config, storageIndex);
}
}
}
@@ -184,35 +181,70 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return node;
}
- private static int SearchForStorageBase(Operation operation, int sbStart, int sbEnd)
+ private static int SearchForStorageBase(BasicBlock block, Operand globalAddress, int sbStart, int sbEnd)
{
- Queue assignments = new Queue();
+ globalAddress = Utils.FindLastOperation(globalAddress, block);
- assignments.Enqueue(operation);
-
- while (assignments.TryDequeue(out operation))
+ if (globalAddress.Type == OperandType.ConstantBuffer)
{
- for (int index = 0; index < operation.SourcesCount; index++)
+ return GetStorageIndex(globalAddress, sbStart, sbEnd);
+ }
+
+ Operation operation = globalAddress.AsgOp as Operation;
+
+ if (operation == null || operation.Inst != Instruction.Add)
+ {
+ return -1;
+ }
+
+ Operand src1 = operation.GetSource(0);
+ Operand src2 = operation.GetSource(1);
+
+ if ((src1.Type == OperandType.LocalVariable && src2.Type == OperandType.Constant) ||
+ (src2.Type == OperandType.LocalVariable && src1.Type == OperandType.Constant))
+ {
+ if (src1.Type == OperandType.LocalVariable)
{
- Operand source = operation.GetSource(index);
+ operation = Utils.FindLastOperation(src1, block).AsgOp as Operation;
+ }
+ else
+ {
+ operation = Utils.FindLastOperation(src2, block).AsgOp as Operation;
+ }
- if (source.Type == OperandType.ConstantBuffer)
- {
- int slot = source.GetCbufSlot();
- int offset = source.GetCbufOffset();
+ if (operation == null || operation.Inst != Instruction.Add)
+ {
+ return -1;
+ }
+ }
- if (slot == 0 && offset >= sbStart && offset < sbEnd)
- {
- int storageIndex = (offset - sbStart) / StorageDescSize;
+ for (int index = 0; index < operation.SourcesCount; index++)
+ {
+ Operand source = operation.GetSource(index);
- return storageIndex;
- }
- }
+ int storageIndex = GetStorageIndex(source, sbStart, sbEnd);
- if (source.AsgOp is Operation asgOperation)
- {
- assignments.Enqueue(asgOperation);
- }
+ if (storageIndex != -1)
+ {
+ return storageIndex;
+ }
+ }
+
+ return -1;
+ }
+
+ private static int GetStorageIndex(Operand operand, int sbStart, int sbEnd)
+ {
+ if (operand.Type == OperandType.ConstantBuffer)
+ {
+ int slot = operand.GetCbufSlot();
+ int offset = operand.GetCbufOffset();
+
+ if (slot == 0 && offset >= sbStart && offset < sbEnd)
+ {
+ int storageIndex = (offset - sbStart) / StorageDescSize;
+
+ return storageIndex;
}
}
diff --git a/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs b/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs
index 61b1544f..ec8d8015 100644
--- a/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs
+++ b/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs
@@ -10,11 +10,22 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
public static void RunPass(BasicBlock[] blocks, ShaderConfig config)
{
+ RunOptimizationPasses(blocks);
+
+ // Those passes are looking for specific patterns and only needs to run once.
for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++)
{
GlobalToStorage.RunPass(blocks[blkIndex], config);
+ BindlessToIndexed.RunPass(blocks[blkIndex]);
+ BindlessElimination.RunPass(blocks[blkIndex], config);
}
+ // Run optimizations one last time to remove any code that is now optimizable after above passes.
+ RunOptimizationPasses(blocks);
+ }
+
+ private static void RunOptimizationPasses(BasicBlock[] blocks)
+ {
bool modified;
do
@@ -85,27 +96,6 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
}
}
while (modified);
-
- for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++)
- {
- BindlessToIndexed.RunPass(blocks[blkIndex]);
- BindlessElimination.RunPass(blocks[blkIndex], config);
-
- // Try to eliminate any operations that are now unused.
- LinkedListNode node = blocks[blkIndex].Operations.First;
-
- while (node != null)
- {
- LinkedListNode nextNode = node.Next;
-
- if (IsUnused(node.Value))
- {
- RemoveNode(blocks[blkIndex], node);
- }
-
- node = nextNode;
- }
- }
}
private static void PropagateCopy(Operation copyOp)
diff --git a/Ryujinx.Graphics.Shader/Translation/Optimizations/Utils.cs b/Ryujinx.Graphics.Shader/Translation/Optimizations/Utils.cs
new file mode 100644
index 00000000..83ff8c40
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/Translation/Optimizations/Utils.cs
@@ -0,0 +1,67 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+
+namespace Ryujinx.Graphics.Shader.Translation.Optimizations
+{
+ static class Utils
+ {
+ private static Operation FindBranchSource(BasicBlock block)
+ {
+ foreach (BasicBlock sourceBlock in block.Predecessors)
+ {
+ if (sourceBlock.Operations.Count > 0)
+ {
+ Operation lastOp = sourceBlock.Operations.Last.Value as Operation;
+
+ if (lastOp != null &&
+ ((sourceBlock.Next == block && lastOp.Inst == Instruction.BranchIfFalse) ||
+ (sourceBlock.Branch == block && lastOp.Inst == Instruction.BranchIfTrue)))
+ {
+ return lastOp;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private static bool BlockConditionsMatch(BasicBlock currentBlock, BasicBlock queryBlock)
+ {
+ // Check if all the conditions for the query block are satisfied by the current block.
+ // Just checks the top-most conditional for now.
+
+ Operation currentBranch = FindBranchSource(currentBlock);
+ Operation queryBranch = FindBranchSource(queryBlock);
+
+ Operand currentCondition = currentBranch?.GetSource(0);
+ Operand queryCondition = queryBranch?.GetSource(0);
+
+ // The condition should be the same operand instance.
+
+ return currentBranch != null && queryBranch != null &&
+ currentBranch.Inst == queryBranch.Inst &&
+ currentCondition == queryCondition;
+ }
+
+ public static Operand FindLastOperation(Operand source, BasicBlock block)
+ {
+ if (source.AsgOp is PhiNode phiNode)
+ {
+ // This source can have a different value depending on a previous branch.
+ // Ensure that conditions met for that branch are also met for the current one.
+ // Prefer the latest sources for the phi node.
+
+ for (int i = phiNode.SourcesCount - 1; i >= 0; i--)
+ {
+ BasicBlock phiBlock = phiNode.GetBlock(i);
+
+ if (BlockConditionsMatch(block, phiBlock))
+ {
+ return phiNode.GetSource(i);
+ }
+ }
+ }
+
+ return source;
+ }
+ }
+}