mirror of
https://github.com/Ryujinx/Ryujinx.git
synced 2024-11-09 08:48:44 +00:00
f23b2878cc
We have a conversion from LDG on the compute shader to a special constant buffer binding that's used to exceed hardware limits on compute, but it was only running if the byte offset could be identified. The fallback that checks all of the bindings at runtime only checks the storage buffers. This PR adds checking ube ranges to the LoadGlobal fallback. This extends the changes in #4011 to only check ube entries which are accessed by the shader. Fixes particles affected by the wind in The Legend of Zelda: Breath of the Wild. May fix other weird issues with compute shaders in some games. Try a bunch of games and drivers to make sure they don't blow up loading constants willynilly from searchable buffers.
382 lines
12 KiB
C#
382 lines
12 KiB
C#
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
|
|
namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
|
{
|
|
static class Optimizer
|
|
{
|
|
public static void RunPass(BasicBlock[] blocks, ShaderConfig config)
|
|
{
|
|
RunOptimizationPasses(blocks);
|
|
|
|
int sbUseMask = 0;
|
|
int ubeUseMask = 0;
|
|
|
|
// 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, ref sbUseMask, ref ubeUseMask);
|
|
BindlessToIndexed.RunPass(blocks[blkIndex], config);
|
|
BindlessElimination.RunPass(blocks[blkIndex], config);
|
|
}
|
|
|
|
config.SetAccessibleBufferMasks(sbUseMask, ubeUseMask);
|
|
|
|
// 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
|
|
{
|
|
modified = false;
|
|
|
|
for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++)
|
|
{
|
|
BasicBlock block = blocks[blkIndex];
|
|
|
|
LinkedListNode<INode> node = block.Operations.First;
|
|
|
|
while (node != null)
|
|
{
|
|
LinkedListNode<INode> nextNode = node.Next;
|
|
|
|
bool isUnused = IsUnused(node.Value);
|
|
|
|
if (!(node.Value is Operation operation) || isUnused)
|
|
{
|
|
if (node.Value is PhiNode phi && !isUnused)
|
|
{
|
|
isUnused = PropagatePhi(phi);
|
|
}
|
|
|
|
if (isUnused)
|
|
{
|
|
RemoveNode(block, node);
|
|
|
|
modified = true;
|
|
}
|
|
|
|
node = nextNode;
|
|
|
|
continue;
|
|
}
|
|
|
|
ConstantFolding.RunPass(operation);
|
|
|
|
Simplification.RunPass(operation);
|
|
|
|
if (DestIsLocalVar(operation))
|
|
{
|
|
if (operation.Inst == Instruction.Copy)
|
|
{
|
|
PropagateCopy(operation);
|
|
|
|
RemoveNode(block, node);
|
|
|
|
modified = true;
|
|
}
|
|
else if ((operation.Inst == Instruction.PackHalf2x16 && PropagatePack(operation)) ||
|
|
(operation.Inst == Instruction.ShuffleXor && MatchDdxOrDdy(operation)))
|
|
{
|
|
if (DestHasNoUses(operation))
|
|
{
|
|
RemoveNode(block, node);
|
|
}
|
|
|
|
modified = true;
|
|
}
|
|
}
|
|
|
|
node = nextNode;
|
|
}
|
|
|
|
if (BranchElimination.RunPass(block))
|
|
{
|
|
RemoveNode(block, block.Operations.Last);
|
|
|
|
modified = true;
|
|
}
|
|
}
|
|
}
|
|
while (modified);
|
|
}
|
|
|
|
private static void PropagateCopy(Operation copyOp)
|
|
{
|
|
// Propagate copy source operand to all uses of
|
|
// the destination operand.
|
|
|
|
Operand dest = copyOp.Dest;
|
|
Operand src = copyOp.GetSource(0);
|
|
|
|
INode[] uses = dest.UseOps.ToArray();
|
|
|
|
foreach (INode useNode in uses)
|
|
{
|
|
for (int index = 0; index < useNode.SourcesCount; index++)
|
|
{
|
|
if (useNode.GetSource(index) == dest)
|
|
{
|
|
useNode.SetSource(index, src);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static bool PropagatePhi(PhiNode phi)
|
|
{
|
|
// If all phi sources are the same, we can propagate it and remove the phi.
|
|
|
|
Operand firstSrc = phi.GetSource(0);
|
|
|
|
for (int index = 1; index < phi.SourcesCount; index++)
|
|
{
|
|
if (!IsSameOperand(firstSrc, phi.GetSource(index)))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// All sources are equal, we can propagate the value.
|
|
|
|
Operand dest = phi.Dest;
|
|
|
|
INode[] uses = dest.UseOps.ToArray();
|
|
|
|
foreach (INode useNode in uses)
|
|
{
|
|
for (int index = 0; index < useNode.SourcesCount; index++)
|
|
{
|
|
if (useNode.GetSource(index) == dest)
|
|
{
|
|
useNode.SetSource(index, firstSrc);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static bool IsSameOperand(Operand x, Operand y)
|
|
{
|
|
if (x.Type != y.Type || x.Value != y.Value)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return x.Type == OperandType.Attribute ||
|
|
x.Type == OperandType.AttributePerPatch ||
|
|
x.Type == OperandType.Constant ||
|
|
x.Type == OperandType.ConstantBuffer;
|
|
}
|
|
|
|
private static bool PropagatePack(Operation packOp)
|
|
{
|
|
// Propagate pack source operands to uses by unpack
|
|
// instruction. The source depends on the unpack instruction.
|
|
bool modified = false;
|
|
|
|
Operand dest = packOp.Dest;
|
|
Operand src0 = packOp.GetSource(0);
|
|
Operand src1 = packOp.GetSource(1);
|
|
|
|
INode[] uses = dest.UseOps.ToArray();
|
|
|
|
foreach (INode useNode in uses)
|
|
{
|
|
if (!(useNode is Operation operation) || operation.Inst != Instruction.UnpackHalf2x16)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (operation.GetSource(0) == dest)
|
|
{
|
|
operation.TurnIntoCopy(operation.Index == 1 ? src1 : src0);
|
|
|
|
modified = true;
|
|
}
|
|
}
|
|
|
|
return modified;
|
|
}
|
|
|
|
public static bool MatchDdxOrDdy(Operation operation)
|
|
{
|
|
// It's assumed that "operation.Inst" is ShuffleXor,
|
|
// that should be checked before calling this method.
|
|
Debug.Assert(operation.Inst == Instruction.ShuffleXor);
|
|
|
|
bool modified = false;
|
|
|
|
Operand src2 = operation.GetSource(1);
|
|
Operand src3 = operation.GetSource(2);
|
|
|
|
if (src2.Type != OperandType.Constant || (src2.Value != 1 && src2.Value != 2))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (src3.Type != OperandType.Constant || src3.Value != 0x1c03)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool isDdy = src2.Value == 2;
|
|
bool isDdx = !isDdy;
|
|
|
|
// We can replace any use by a FSWZADD with DDX/DDY, when
|
|
// the following conditions are true:
|
|
// - The mask should be 0b10100101 for DDY, or 0b10011001 for DDX.
|
|
// - The first source operand must be the shuffle output.
|
|
// - The second source operand must be the shuffle first source operand.
|
|
INode[] uses = operation.Dest.UseOps.ToArray();
|
|
|
|
foreach (INode use in uses)
|
|
{
|
|
if (!(use is Operation test))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!(use is Operation useOp) || useOp.Inst != Instruction.SwizzleAdd)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Operand fswzaddSrc1 = useOp.GetSource(0);
|
|
Operand fswzaddSrc2 = useOp.GetSource(1);
|
|
Operand fswzaddSrc3 = useOp.GetSource(2);
|
|
|
|
if (fswzaddSrc1 != operation.Dest)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (fswzaddSrc2 != operation.GetSource(0))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (fswzaddSrc3.Type != OperandType.Constant)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
int mask = fswzaddSrc3.Value;
|
|
|
|
if ((isDdx && mask != 0b10011001) ||
|
|
(isDdy && mask != 0b10100101))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
useOp.TurnInto(isDdx ? Instruction.Ddx : Instruction.Ddy, fswzaddSrc2);
|
|
|
|
modified = true;
|
|
}
|
|
|
|
return modified;
|
|
}
|
|
|
|
private static void RemoveNode(BasicBlock block, LinkedListNode<INode> llNode)
|
|
{
|
|
// Remove a node from the nodes list, and also remove itself
|
|
// from all the use lists on the operands that this node uses.
|
|
block.Operations.Remove(llNode);
|
|
|
|
Queue<INode> nodes = new Queue<INode>();
|
|
|
|
nodes.Enqueue(llNode.Value);
|
|
|
|
while (nodes.TryDequeue(out INode node))
|
|
{
|
|
for (int index = 0; index < node.SourcesCount; index++)
|
|
{
|
|
Operand src = node.GetSource(index);
|
|
|
|
if (src.Type != OperandType.LocalVariable)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (src.UseOps.Remove(node) && src.UseOps.Count == 0)
|
|
{
|
|
Debug.Assert(src.AsgOp != null);
|
|
nodes.Enqueue(src.AsgOp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static bool IsUnused(INode node)
|
|
{
|
|
return !HasSideEffects(node) && DestIsLocalVar(node) && DestHasNoUses(node);
|
|
}
|
|
|
|
private static bool HasSideEffects(INode node)
|
|
{
|
|
if (node is Operation operation)
|
|
{
|
|
switch (operation.Inst & Instruction.Mask)
|
|
{
|
|
case Instruction.AtomicAdd:
|
|
case Instruction.AtomicAnd:
|
|
case Instruction.AtomicCompareAndSwap:
|
|
case Instruction.AtomicMaxS32:
|
|
case Instruction.AtomicMaxU32:
|
|
case Instruction.AtomicMinS32:
|
|
case Instruction.AtomicMinU32:
|
|
case Instruction.AtomicOr:
|
|
case Instruction.AtomicSwap:
|
|
case Instruction.AtomicXor:
|
|
case Instruction.Call:
|
|
case Instruction.ImageAtomic:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static bool DestIsLocalVar(INode node)
|
|
{
|
|
if (node.DestsCount == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (int index = 0; index < node.DestsCount; index++)
|
|
{
|
|
Operand dest = node.GetDest(index);
|
|
|
|
if (dest != null && dest.Type != OperandType.LocalVariable)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static bool DestHasNoUses(INode node)
|
|
{
|
|
for (int index = 0; index < node.DestsCount; index++)
|
|
{
|
|
Operand dest = node.GetDest(index);
|
|
|
|
if (dest != null && dest.UseOps.Count != 0)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
} |