mirror of
https://github.com/Ryubing/Ryujinx.git
synced 2025-12-01 03:43:00 -05:00
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
This commit is contained in:
@@ -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)
|
||||
// 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);
|
||||
}
|
||||
|
||||
bindlessHandle = Utils.FindLastOperation(bindlessHandle, block);
|
||||
|
||||
// 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;
|
||||
|
||||
if (bindlessHandle.Type == OperandType.ConstantBuffer)
|
||||
{
|
||||
SetHandle(
|
||||
resourceManager,
|
||||
gpuAccessor,
|
||||
texOp,
|
||||
bindlessHandle.GetCbufOffset(),
|
||||
bindlessHandle.GetCbufSlot(),
|
||||
rewriteSamplerType,
|
||||
isImage: false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!TryGetOperation(bindlessHandle.AsgOp, out Operation handleCombineOp))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
SetHandle(
|
||||
resourceManager,
|
||||
gpuAccessor,
|
||||
texOp,
|
||||
bindlessHandle.GetCbufOffset(),
|
||||
bindlessHandle.GetCbufSlot(),
|
||||
rewriteSamplerType,
|
||||
isImage: false);
|
||||
src0 = GetSourceForMaskedHandle(src0AsgOp, 0xFFFFF);
|
||||
src1 = GetSourceForMaskedHandle(src1AsgOp, 0xFFF00000);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TryGetOperation(bindlessHandle.AsgOp, out Operation handleCombineOp))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (handleCombineOp.Inst != Instruction.BitwiseOr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
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)
|
||||
// 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)
|
||||
{
|
||||
Operand shift = src0AsgOp.GetSource(1);
|
||||
|
||||
if (shift.Type == OperandType.Constant && shift.Value == 20)
|
||||
{
|
||||
src0 = src1;
|
||||
src1 = src0AsgOp.GetSource(0);
|
||||
handleType = TextureHandleType.SeparateSamplerId;
|
||||
}
|
||||
if (src0 == null || src1 == null)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
src1 = src1AsgOp.GetSource(0);
|
||||
handleType = TextureHandleType.SeparateSamplerId;
|
||||
}
|
||||
}
|
||||
else if (texOp.Inst == Instruction.ImageLoad ||
|
||||
texOp.Inst == Instruction.ImageStore ||
|
||||
texOp.Inst == Instruction.ImageAtomic)
|
||||
else if (src1.Type == OperandType.Constant && (src1.Value & 0xfffff) == 0)
|
||||
{
|
||||
Operand src0 = Utils.FindLastOperation(texOp.GetSource(0), block);
|
||||
handleType = TextureHandleType.SeparateConstantSamplerHandle;
|
||||
}
|
||||
|
||||
if (src0.Type == OperandType.ConstantBuffer)
|
||||
{
|
||||
int cbufOffset = src0.GetCbufOffset();
|
||||
int cbufSlot = src0.GetCbufSlot();
|
||||
if (src0.Type != OperandType.ConstantBuffer)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (texOp.Format == TextureFormat.Unknown)
|
||||
{
|
||||
if (texOp.Inst == Instruction.ImageAtomic)
|
||||
{
|
||||
texOp.Format = ShaderProperties.GetTextureFormatAtomic(gpuAccessor, cbufOffset, cbufSlot);
|
||||
}
|
||||
else
|
||||
{
|
||||
texOp.Format = ShaderProperties.GetTextureFormat(gpuAccessor, cbufOffset, cbufSlot);
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
bool rewriteSamplerType = texOp.Type == SamplerType.TextureBuffer;
|
||||
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);
|
||||
|
||||
SetHandle(resourceManager, gpuAccessor, texOp, cbufOffset, cbufSlot, rewriteSamplerType, isImage: true);
|
||||
}
|
||||
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 (texOp.Format == TextureFormat.Unknown)
|
||||
{
|
||||
if (texOp.Inst == Instruction.ImageAtomic)
|
||||
{
|
||||
texOp.Format = ShaderProperties.GetTextureFormatAtomic(gpuAccessor, cbufOffset, cbufSlot);
|
||||
}
|
||||
else
|
||||
{
|
||||
texOp.Format = ShaderProperties.GetTextureFormat(gpuAccessor, cbufOffset, cbufSlot);
|
||||
}
|
||||
}
|
||||
|
||||
bool rewriteSamplerType = texOp.Type == SamplerType.TextureBuffer;
|
||||
|
||||
SetHandle(resourceManager, gpuAccessor, texOp, cbufOffset, cbufSlot, rewriteSamplerType, isImage: true);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryGetOperation(INode asgOp, out Operation outOperation)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user