aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Graphics.Shader/Translation/Optimizations
diff options
context:
space:
mode:
authorgdkchan <gab.dark.100@gmail.com>2024-04-07 18:25:55 -0300
committerGitHub <noreply@github.com>2024-04-07 18:25:55 -0300
commit3e6e0e4afaa3c3ffb118cb17b61feb16966a7eeb (patch)
treea4652499c089b0853e39c382cad82a9db4d6ad08 /src/Ryujinx.Graphics.Shader/Translation/Optimizations
parent808803d97a0c06809bf000687c252f960048fcf0 (diff)
Add support for large sampler arrays on Vulkan (#6489)
* Add support for large sampler arrays on Vulkan * Shader cache version bump * Format whitespace * Move DescriptorSetManager to PipelineLayoutCacheEntry to allow different pool sizes per layout * Handle array textures with different types on the same buffer * Somewhat better caching system * Avoid useless buffer data modification checks * Move redundant bindings update checking to the backend * Fix an issue where texture arrays would get the same bindings across stages on Vulkan * Backport some fixes from part 2 * Fix typo * PR feedback * Format whitespace * Add some missing XML docs
Diffstat (limited to 'src/Ryujinx.Graphics.Shader/Translation/Optimizations')
-rw-r--r--src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs308
-rw-r--r--src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs236
-rw-r--r--src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToIndexed.cs118
-rw-r--r--src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs10
4 files changed, 412 insertions, 260 deletions
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs
index a8890327..ad955278 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs
@@ -15,8 +15,12 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
// - The handle is a constant buffer value.
// - The handle is the result of a bitwise OR logical operation.
// - Both sources of the OR operation comes from a constant buffer.
- for (LinkedListNode<INode> node = block.Operations.First; node != null; node = node.Next)
+ LinkedListNode<INode> nextNode;
+
+ for (LinkedListNode<INode> node = block.Operations.First; node != null; node = nextNode)
{
+ nextNode = node.Next;
+
if (node.Value is not TextureOperation texOp)
{
continue;
@@ -27,185 +31,207 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
continue;
}
- if (texOp.Inst == Instruction.TextureSample || texOp.Inst.IsTextureQuery())
+ if (!TryConvertBindless(block, resourceManager, gpuAccessor, texOp))
{
- Operand bindlessHandle = texOp.GetSource(0);
+ // If we can't do bindless elimination, remove the texture operation.
+ // Set any destination variables to zero.
- // In some cases the compiler uses a shuffle operation to get the handle,
- // for some textureGrad implementations. In those cases, we can skip the shuffle.
- if (bindlessHandle.AsgOp is Operation shuffleOp && shuffleOp.Inst == Instruction.Shuffle)
+ for (int destIndex = 0; destIndex < texOp.DestsCount; destIndex++)
{
- bindlessHandle = shuffleOp.GetSource(0);
+ block.Operations.AddBefore(node, new Operation(Instruction.Copy, texOp.GetDest(destIndex), OperandHelper.Const(0)));
}
- bindlessHandle = Utils.FindLastOperation(bindlessHandle, block);
+ Utils.DeleteNode(node, texOp);
+ }
+ }
+ }
- // Some instructions do not encode an accurate sampler type:
- // - Most instructions uses the same type for 1D and Buffer.
- // - Query instructions may not have any type.
- // For those cases, we need to try getting the type from current GPU state,
- // as long bindless elimination is successful and we know where the texture descriptor is located.
- bool rewriteSamplerType =
- texOp.Type == SamplerType.TextureBuffer ||
- texOp.Inst == Instruction.TextureQuerySamples ||
- texOp.Inst == Instruction.TextureQuerySize;
+ private static bool TryConvertBindless(BasicBlock block, ResourceManager resourceManager, IGpuAccessor gpuAccessor, TextureOperation texOp)
+ {
+ if (texOp.Inst == Instruction.TextureSample || texOp.Inst.IsTextureQuery())
+ {
+ Operand bindlessHandle = texOp.GetSource(0);
- if (bindlessHandle.Type == OperandType.ConstantBuffer)
- {
- SetHandle(
- resourceManager,
- gpuAccessor,
- texOp,
- bindlessHandle.GetCbufOffset(),
- bindlessHandle.GetCbufSlot(),
- rewriteSamplerType,
- isImage: false);
-
- continue;
- }
+ // In some cases the compiler uses a shuffle operation to get the handle,
+ // for some textureGrad implementations. In those cases, we can skip the shuffle.
+ if (bindlessHandle.AsgOp is Operation shuffleOp && shuffleOp.Inst == Instruction.Shuffle)
+ {
+ bindlessHandle = shuffleOp.GetSource(0);
+ }
- if (!TryGetOperation(bindlessHandle.AsgOp, out Operation handleCombineOp))
- {
- continue;
- }
+ bindlessHandle = Utils.FindLastOperation(bindlessHandle, block);
- if (handleCombineOp.Inst != Instruction.BitwiseOr)
- {
- continue;
- }
+ // Some instructions do not encode an accurate sampler type:
+ // - Most instructions uses the same type for 1D and Buffer.
+ // - Query instructions may not have any type.
+ // For those cases, we need to try getting the type from current GPU state,
+ // as long bindless elimination is successful and we know where the texture descriptor is located.
+ bool rewriteSamplerType =
+ texOp.Type == SamplerType.TextureBuffer ||
+ texOp.Inst == Instruction.TextureQuerySamples ||
+ texOp.Inst == Instruction.TextureQuerySize;
- Operand src0 = Utils.FindLastOperation(handleCombineOp.GetSource(0), block);
- Operand src1 = Utils.FindLastOperation(handleCombineOp.GetSource(1), block);
+ if (bindlessHandle.Type == OperandType.ConstantBuffer)
+ {
+ SetHandle(
+ resourceManager,
+ gpuAccessor,
+ texOp,
+ bindlessHandle.GetCbufOffset(),
+ bindlessHandle.GetCbufSlot(),
+ rewriteSamplerType,
+ isImage: false);
+
+ return true;
+ }
- // For cases where we have a constant, ensure that the constant is always
- // the second operand.
- // Since this is a commutative operation, both are fine,
- // and having a "canonical" representation simplifies some checks below.
- if (src0.Type == OperandType.Constant && src1.Type != OperandType.Constant)
- {
- (src0, src1) = (src1, src0);
- }
+ if (!TryGetOperation(bindlessHandle.AsgOp, out Operation handleCombineOp))
+ {
+ return false;
+ }
- TextureHandleType handleType = TextureHandleType.SeparateSamplerHandle;
-
- // Try to match the following patterns:
- // Masked pattern:
- // - samplerHandle = samplerHandle & 0xFFF00000;
- // - textureHandle = textureHandle & 0xFFFFF;
- // - combinedHandle = samplerHandle | textureHandle;
- // Where samplerHandle and textureHandle comes from a constant buffer.
- // Shifted pattern:
- // - samplerHandle = samplerId << 20;
- // - combinedHandle = samplerHandle | textureHandle;
- // Where samplerId and textureHandle comes from a constant buffer.
- // Constant pattern:
- // - combinedHandle = samplerHandleConstant | textureHandle;
- // Where samplerHandleConstant is a constant value, and textureHandle comes from a constant buffer.
- if (src0.AsgOp is Operation src0AsgOp)
+ if (handleCombineOp.Inst != Instruction.BitwiseOr)
+ {
+ return false;
+ }
+
+ Operand src0 = Utils.FindLastOperation(handleCombineOp.GetSource(0), block);
+ Operand src1 = Utils.FindLastOperation(handleCombineOp.GetSource(1), block);
+
+ // For cases where we have a constant, ensure that the constant is always
+ // the second operand.
+ // Since this is a commutative operation, both are fine,
+ // and having a "canonical" representation simplifies some checks below.
+ if (src0.Type == OperandType.Constant && src1.Type != OperandType.Constant)
+ {
+ (src0, src1) = (src1, src0);
+ }
+
+ TextureHandleType handleType = TextureHandleType.SeparateSamplerHandle;
+
+ // Try to match the following patterns:
+ // Masked pattern:
+ // - samplerHandle = samplerHandle & 0xFFF00000;
+ // - textureHandle = textureHandle & 0xFFFFF;
+ // - combinedHandle = samplerHandle | textureHandle;
+ // Where samplerHandle and textureHandle comes from a constant buffer.
+ // Shifted pattern:
+ // - samplerHandle = samplerId << 20;
+ // - combinedHandle = samplerHandle | textureHandle;
+ // Where samplerId and textureHandle comes from a constant buffer.
+ // Constant pattern:
+ // - combinedHandle = samplerHandleConstant | textureHandle;
+ // Where samplerHandleConstant is a constant value, and textureHandle comes from a constant buffer.
+ if (src0.AsgOp is Operation src0AsgOp)
+ {
+ if (src1.AsgOp is Operation src1AsgOp &&
+ src0AsgOp.Inst == Instruction.BitwiseAnd &&
+ src1AsgOp.Inst == Instruction.BitwiseAnd)
{
- if (src1.AsgOp is Operation src1AsgOp &&
- src0AsgOp.Inst == Instruction.BitwiseAnd &&
- src1AsgOp.Inst == Instruction.BitwiseAnd)
+ src0 = GetSourceForMaskedHandle(src0AsgOp, 0xFFFFF);
+ src1 = GetSourceForMaskedHandle(src1AsgOp, 0xFFF00000);
+
+ // The OR operation is commutative, so we can also try to swap the operands to get a match.
+ if (src0 == null || src1 == null)
{
- src0 = GetSourceForMaskedHandle(src0AsgOp, 0xFFFFF);
- src1 = GetSourceForMaskedHandle(src1AsgOp, 0xFFF00000);
-
- // The OR operation is commutative, so we can also try to swap the operands to get a match.
- if (src0 == null || src1 == null)
- {
- src0 = GetSourceForMaskedHandle(src1AsgOp, 0xFFFFF);
- src1 = GetSourceForMaskedHandle(src0AsgOp, 0xFFF00000);
- }
-
- if (src0 == null || src1 == null)
- {
- continue;
- }
+ src0 = GetSourceForMaskedHandle(src1AsgOp, 0xFFFFF);
+ src1 = GetSourceForMaskedHandle(src0AsgOp, 0xFFF00000);
}
- else if (src0AsgOp.Inst == Instruction.ShiftLeft)
+
+ if (src0 == null || src1 == null)
{
- Operand shift = src0AsgOp.GetSource(1);
-
- if (shift.Type == OperandType.Constant && shift.Value == 20)
- {
- src0 = src1;
- src1 = src0AsgOp.GetSource(0);
- handleType = TextureHandleType.SeparateSamplerId;
- }
+ return false;
}
}
- else if (src1.AsgOp is Operation src1AsgOp && src1AsgOp.Inst == Instruction.ShiftLeft)
+ else if (src0AsgOp.Inst == Instruction.ShiftLeft)
{
- Operand shift = src1AsgOp.GetSource(1);
+ Operand shift = src0AsgOp.GetSource(1);
if (shift.Type == OperandType.Constant && shift.Value == 20)
{
- src1 = src1AsgOp.GetSource(0);
+ src0 = src1;
+ src1 = src0AsgOp.GetSource(0);
handleType = TextureHandleType.SeparateSamplerId;
}
}
- else if (src1.Type == OperandType.Constant && (src1.Value & 0xfffff) == 0)
- {
- handleType = TextureHandleType.SeparateConstantSamplerHandle;
- }
+ }
+ else if (src1.AsgOp is Operation src1AsgOp && src1AsgOp.Inst == Instruction.ShiftLeft)
+ {
+ Operand shift = src1AsgOp.GetSource(1);
- if (src0.Type != OperandType.ConstantBuffer)
+ if (shift.Type == OperandType.Constant && shift.Value == 20)
{
- continue;
+ src1 = src1AsgOp.GetSource(0);
+ handleType = TextureHandleType.SeparateSamplerId;
}
+ }
+ else if (src1.Type == OperandType.Constant && (src1.Value & 0xfffff) == 0)
+ {
+ handleType = TextureHandleType.SeparateConstantSamplerHandle;
+ }
- if (handleType == TextureHandleType.SeparateConstantSamplerHandle)
- {
- SetHandle(
- resourceManager,
- gpuAccessor,
- texOp,
- TextureHandle.PackOffsets(src0.GetCbufOffset(), ((src1.Value >> 20) & 0xfff), handleType),
- TextureHandle.PackSlots(src0.GetCbufSlot(), 0),
- rewriteSamplerType,
- isImage: false);
- }
- else if (src1.Type == OperandType.ConstantBuffer)
- {
- SetHandle(
- resourceManager,
- gpuAccessor,
- texOp,
- TextureHandle.PackOffsets(src0.GetCbufOffset(), src1.GetCbufOffset(), handleType),
- TextureHandle.PackSlots(src0.GetCbufSlot(), src1.GetCbufSlot()),
- rewriteSamplerType,
- isImage: false);
- }
+ if (src0.Type != OperandType.ConstantBuffer)
+ {
+ return false;
}
- else if (texOp.Inst == Instruction.ImageLoad ||
- texOp.Inst == Instruction.ImageStore ||
- texOp.Inst == Instruction.ImageAtomic)
+
+ if (handleType == TextureHandleType.SeparateConstantSamplerHandle)
{
- Operand src0 = Utils.FindLastOperation(texOp.GetSource(0), block);
+ SetHandle(
+ resourceManager,
+ gpuAccessor,
+ texOp,
+ TextureHandle.PackOffsets(src0.GetCbufOffset(), ((src1.Value >> 20) & 0xfff), handleType),
+ TextureHandle.PackSlots(src0.GetCbufSlot(), 0),
+ rewriteSamplerType,
+ isImage: false);
+
+ return true;
+ }
+ else if (src1.Type == OperandType.ConstantBuffer)
+ {
+ SetHandle(
+ resourceManager,
+ gpuAccessor,
+ texOp,
+ TextureHandle.PackOffsets(src0.GetCbufOffset(), src1.GetCbufOffset(), handleType),
+ TextureHandle.PackSlots(src0.GetCbufSlot(), src1.GetCbufSlot()),
+ rewriteSamplerType,
+ isImage: false);
+
+ return true;
+ }
+ }
+ else if (texOp.Inst.IsImage())
+ {
+ Operand src0 = Utils.FindLastOperation(texOp.GetSource(0), block);
- if (src0.Type == OperandType.ConstantBuffer)
- {
- int cbufOffset = src0.GetCbufOffset();
- int cbufSlot = src0.GetCbufSlot();
+ if (src0.Type == OperandType.ConstantBuffer)
+ {
+ int cbufOffset = src0.GetCbufOffset();
+ int cbufSlot = src0.GetCbufSlot();
- if (texOp.Format == TextureFormat.Unknown)
+ if (texOp.Format == TextureFormat.Unknown)
+ {
+ if (texOp.Inst == Instruction.ImageAtomic)
{
- if (texOp.Inst == Instruction.ImageAtomic)
- {
- texOp.Format = ShaderProperties.GetTextureFormatAtomic(gpuAccessor, cbufOffset, cbufSlot);
- }
- else
- {
- texOp.Format = ShaderProperties.GetTextureFormat(gpuAccessor, cbufOffset, cbufSlot);
- }
+ texOp.Format = ShaderProperties.GetTextureFormatAtomic(gpuAccessor, cbufOffset, cbufSlot);
}
+ else
+ {
+ texOp.Format = ShaderProperties.GetTextureFormat(gpuAccessor, cbufOffset, cbufSlot);
+ }
+ }
- bool rewriteSamplerType = texOp.Type == SamplerType.TextureBuffer;
+ bool rewriteSamplerType = texOp.Type == SamplerType.TextureBuffer;
- SetHandle(resourceManager, gpuAccessor, texOp, cbufOffset, cbufSlot, rewriteSamplerType, isImage: true);
- }
+ SetHandle(resourceManager, gpuAccessor, texOp, cbufOffset, cbufSlot, rewriteSamplerType, isImage: true);
+
+ return true;
}
}
+
+ return false;
}
private static bool TryGetOperation(INode asgOp, out Operation outOperation)
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs
new file mode 100644
index 00000000..7543d1c2
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs
@@ -0,0 +1,236 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System;
+using System.Collections.Generic;
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Translation.Optimizations
+{
+ static class BindlessToArray
+ {
+ private const int NvnTextureBufferIndex = 2;
+ private const int HardcodedArrayLengthOgl = 4;
+
+ // 1 and 0 elements are not considered arrays anymore.
+ private const int MinimumArrayLength = 2;
+
+ public static void RunPassOgl(BasicBlock block, ResourceManager resourceManager)
+ {
+ // We can turn a bindless texture access into a indexed access,
+ // as long the following conditions are true:
+ // - The handle is loaded using a LDC instruction.
+ // - The handle is loaded from the constant buffer with the handles (CB2 for NVN).
+ // - The load has a constant offset.
+ // The base offset of the array of handles on the constant buffer is the constant offset.
+ for (LinkedListNode<INode> node = block.Operations.First; node != null; node = node.Next)
+ {
+ if (node.Value is not TextureOperation texOp)
+ {
+ continue;
+ }
+
+ if ((texOp.Flags & TextureFlags.Bindless) == 0)
+ {
+ continue;
+ }
+
+ if (texOp.GetSource(0).AsgOp is not Operation handleAsgOp)
+ {
+ continue;
+ }
+
+ if (handleAsgOp.Inst != Instruction.Load ||
+ handleAsgOp.StorageKind != StorageKind.ConstantBuffer ||
+ handleAsgOp.SourcesCount != 4)
+ {
+ continue;
+ }
+
+ Operand ldcSrc0 = handleAsgOp.GetSource(0);
+
+ if (ldcSrc0.Type != OperandType.Constant ||
+ !resourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int src0CbufSlot) ||
+ src0CbufSlot != NvnTextureBufferIndex)
+ {
+ continue;
+ }
+
+ Operand ldcSrc1 = handleAsgOp.GetSource(1);
+
+ // We expect field index 0 to be accessed.
+ if (ldcSrc1.Type != OperandType.Constant || ldcSrc1.Value != 0)
+ {
+ continue;
+ }
+
+ Operand ldcSrc2 = handleAsgOp.GetSource(2);
+
+ // FIXME: This is missing some checks, for example, a check to ensure that the shift value is 2.
+ // Might be not worth fixing since if that doesn't kick in, the result will be no texture
+ // to access anyway which is also wrong.
+ // Plus this whole transform is fundamentally flawed as-is since we have no way to know the array size.
+ // Eventually, this should be entirely removed in favor of a implementation that supports true bindless
+ // texture access.
+ if (ldcSrc2.AsgOp is not Operation shrOp || shrOp.Inst != Instruction.ShiftRightU32)
+ {
+ continue;
+ }
+
+ if (shrOp.GetSource(0).AsgOp is not Operation shrOp2 || shrOp2.Inst != Instruction.ShiftRightU32)
+ {
+ continue;
+ }
+
+ if (shrOp2.GetSource(0).AsgOp is not Operation addOp || addOp.Inst != Instruction.Add)
+ {
+ continue;
+ }
+
+ Operand addSrc1 = addOp.GetSource(1);
+
+ if (addSrc1.Type != OperandType.Constant)
+ {
+ continue;
+ }
+
+ TurnIntoArray(resourceManager, texOp, NvnTextureBufferIndex, addSrc1.Value / 4, HardcodedArrayLengthOgl);
+
+ Operand index = Local();
+
+ Operand source = addOp.GetSource(0);
+
+ Operation shrBy3 = new(Instruction.ShiftRightU32, index, source, Const(3));
+
+ block.Operations.AddBefore(node, shrBy3);
+
+ texOp.SetSource(0, index);
+ }
+ }
+
+ public static void RunPass(BasicBlock block, ResourceManager resourceManager, IGpuAccessor gpuAccessor)
+ {
+ // We can turn a bindless texture access into a indexed access,
+ // as long the following conditions are true:
+ // - The handle is loaded using a LDC instruction.
+ // - The handle is loaded from the constant buffer with the handles (CB2 for NVN).
+ // - The load has a constant offset.
+ // The base offset of the array of handles on the constant buffer is the constant offset.
+ for (LinkedListNode<INode> node = block.Operations.First; node != null; node = node.Next)
+ {
+ if (node.Value is not TextureOperation texOp)
+ {
+ continue;
+ }
+
+ if ((texOp.Flags & TextureFlags.Bindless) == 0)
+ {
+ continue;
+ }
+
+ if (texOp.GetSource(0).AsgOp is not Operation handleAsgOp)
+ {
+ continue;
+ }
+
+ int secondaryCbufSlot = 0;
+ int secondaryCbufOffset = 0;
+ bool hasSecondaryHandle = false;
+
+ if (handleAsgOp.Inst == Instruction.BitwiseOr)
+ {
+ Operand src0 = handleAsgOp.GetSource(0);
+ Operand src1 = handleAsgOp.GetSource(1);
+
+ if (src0.Type == OperandType.ConstantBuffer && src1.AsgOp is Operation)
+ {
+ handleAsgOp = src1.AsgOp as Operation;
+ secondaryCbufSlot = src0.GetCbufSlot();
+ secondaryCbufOffset = src0.GetCbufOffset();
+ hasSecondaryHandle = true;
+ }
+ else if (src0.AsgOp is Operation && src1.Type == OperandType.ConstantBuffer)
+ {
+ handleAsgOp = src0.AsgOp as Operation;
+ secondaryCbufSlot = src1.GetCbufSlot();
+ secondaryCbufOffset = src1.GetCbufOffset();
+ hasSecondaryHandle = true;
+ }
+ }
+
+ if (handleAsgOp.Inst != Instruction.Load ||
+ handleAsgOp.StorageKind != StorageKind.ConstantBuffer ||
+ handleAsgOp.SourcesCount != 4)
+ {
+ continue;
+ }
+
+ Operand ldcSrc0 = handleAsgOp.GetSource(0);
+
+ if (ldcSrc0.Type != OperandType.Constant ||
+ !resourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int src0CbufSlot))
+ {
+ continue;
+ }
+
+ Operand ldcSrc1 = handleAsgOp.GetSource(1);
+
+ // We expect field index 0 to be accessed.
+ if (ldcSrc1.Type != OperandType.Constant || ldcSrc1.Value != 0)
+ {
+ continue;
+ }
+
+ Operand ldcVecIndex = handleAsgOp.GetSource(2);
+ Operand ldcElemIndex = handleAsgOp.GetSource(3);
+
+ if (ldcVecIndex.Type != OperandType.LocalVariable || ldcElemIndex.Type != OperandType.LocalVariable)
+ {
+ continue;
+ }
+
+ int cbufSlot;
+ int handleIndex;
+
+ if (hasSecondaryHandle)
+ {
+ cbufSlot = TextureHandle.PackSlots(src0CbufSlot, secondaryCbufSlot);
+ handleIndex = TextureHandle.PackOffsets(0, secondaryCbufOffset, TextureHandleType.SeparateSamplerHandle);
+ }
+ else
+ {
+ cbufSlot = src0CbufSlot;
+ handleIndex = 0;
+ }
+
+ int length = Math.Max(MinimumArrayLength, gpuAccessor.QueryTextureArrayLengthFromBuffer(src0CbufSlot));
+
+ TurnIntoArray(resourceManager, texOp, cbufSlot, handleIndex, length);
+
+ Operand vecIndex = Local();
+ Operand elemIndex = Local();
+ Operand index = Local();
+ Operand indexMin = Local();
+
+ block.Operations.AddBefore(node, new Operation(Instruction.ShiftLeft, vecIndex, ldcVecIndex, Const(1)));
+ block.Operations.AddBefore(node, new Operation(Instruction.ShiftRightU32, elemIndex, ldcElemIndex, Const(1)));
+ block.Operations.AddBefore(node, new Operation(Instruction.Add, index, vecIndex, elemIndex));
+ block.Operations.AddBefore(node, new Operation(Instruction.MinimumU32, indexMin, index, Const(length - 1)));
+
+ texOp.SetSource(0, indexMin);
+ }
+ }
+
+ private static void TurnIntoArray(ResourceManager resourceManager, TextureOperation texOp, int cbufSlot, int handleIndex, int length)
+ {
+ int binding = resourceManager.GetTextureOrImageBinding(
+ texOp.Inst,
+ texOp.Type,
+ texOp.Format,
+ texOp.Flags & ~TextureFlags.Bindless,
+ cbufSlot,
+ handleIndex,
+ length);
+
+ texOp.TurnIntoArray(binding);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToIndexed.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToIndexed.cs
deleted file mode 100644
index 2bd31fe1..00000000
--- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToIndexed.cs
+++ /dev/null
@@ -1,118 +0,0 @@
-using Ryujinx.Graphics.Shader.IntermediateRepresentation;
-using System.Collections.Generic;
-
-using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
-
-namespace Ryujinx.Graphics.Shader.Translation.Optimizations
-{
- static class BindlessToIndexed
- {
- private const int NvnTextureBufferIndex = 2;
-
- public static void RunPass(BasicBlock block, ResourceManager resourceManager)
- {
- // We can turn a bindless texture access into a indexed access,
- // as long the following conditions are true:
- // - The handle is loaded using a LDC instruction.
- // - The handle is loaded from the constant buffer with the handles (CB2 for NVN).
- // - The load has a constant offset.
- // The base offset of the array of handles on the constant buffer is the constant offset.
- for (LinkedListNode<INode> node = block.Operations.First; node != null; node = node.Next)
- {
- if (node.Value is not TextureOperation texOp)
- {
- continue;
- }
-
- if ((texOp.Flags & TextureFlags.Bindless) == 0)
- {
- continue;
- }
-
- if (texOp.GetSource(0).AsgOp is not Operation handleAsgOp)
- {
- continue;
- }
-
- if (handleAsgOp.Inst != Instruction.Load ||
- handleAsgOp.StorageKind != StorageKind.ConstantBuffer ||
- handleAsgOp.SourcesCount != 4)
- {
- continue;
- }
-
- Operand ldcSrc0 = handleAsgOp.GetSource(0);
-
- if (ldcSrc0.Type != OperandType.Constant ||
- !resourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int src0CbufSlot) ||
- src0CbufSlot != NvnTextureBufferIndex)
- {
- continue;
- }
-
- Operand ldcSrc1 = handleAsgOp.GetSource(1);
-
- // We expect field index 0 to be accessed.
- if (ldcSrc1.Type != OperandType.Constant || ldcSrc1.Value != 0)
- {
- continue;
- }
-
- Operand ldcSrc2 = handleAsgOp.GetSource(2);
-
- // FIXME: This is missing some checks, for example, a check to ensure that the shift value is 2.
- // Might be not worth fixing since if that doesn't kick in, the result will be no texture
- // to access anyway which is also wrong.
- // Plus this whole transform is fundamentally flawed as-is since we have no way to know the array size.
- // Eventually, this should be entirely removed in favor of a implementation that supports true bindless
- // texture access.
- if (ldcSrc2.AsgOp is not Operation shrOp || shrOp.Inst != Instruction.ShiftRightU32)
- {
- continue;
- }
-
- if (shrOp.GetSource(0).AsgOp is not Operation shrOp2 || shrOp2.Inst != Instruction.ShiftRightU32)
- {
- continue;
- }
-
- if (shrOp2.GetSource(0).AsgOp is not Operation addOp || addOp.Inst != Instruction.Add)
- {
- continue;
- }
-
- Operand addSrc1 = addOp.GetSource(1);
-
- if (addSrc1.Type != OperandType.Constant)
- {
- continue;
- }
-
- TurnIntoIndexed(resourceManager, texOp, addSrc1.Value / 4);
-
- Operand index = Local();
-
- Operand source = addOp.GetSource(0);
-
- Operation shrBy3 = new(Instruction.ShiftRightU32, index, source, Const(3));
-
- block.Operations.AddBefore(node, shrBy3);
-
- texOp.SetSource(0, index);
- }
- }
-
- private static void TurnIntoIndexed(ResourceManager resourceManager, TextureOperation texOp, int handle)
- {
- int binding = resourceManager.GetTextureOrImageBinding(
- texOp.Inst,
- texOp.Type | SamplerType.Indexed,
- texOp.Format,
- texOp.Flags & ~TextureFlags.Bindless,
- NvnTextureBufferIndex,
- handle);
-
- texOp.TurnIntoIndexed(binding);
- }
- }
-}
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs
index ea06691b..49eb3a89 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs
@@ -20,7 +20,15 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
// Those passes are looking for specific patterns and only needs to run once.
for (int blkIndex = 0; blkIndex < context.Blocks.Length; blkIndex++)
{
- BindlessToIndexed.RunPass(context.Blocks[blkIndex], context.ResourceManager);
+ if (context.TargetApi == TargetApi.OpenGL)
+ {
+ BindlessToArray.RunPassOgl(context.Blocks[blkIndex], context.ResourceManager);
+ }
+ else
+ {
+ BindlessToArray.RunPass(context.Blocks[blkIndex], context.ResourceManager, context.GpuAccessor);
+ }
+
BindlessElimination.RunPass(context.Blocks[blkIndex], context.ResourceManager, context.GpuAccessor);
// FragmentCoord only exists on fragment shaders, so we don't need to check other stages.