Stop identifying shader textures with handle and cbuf, use binding instead (#5266)

* Stop identifying shader textures with handle and cbuf, use binding instead

* Remove now unused code

* Consider image operations as having accurate type information too

I don't know why that was not the case before

* Fix missing unscale on InsertCoordNormalization, stop calling SetUsageFlagsForTextureQuery when not needed

* Shader cache version bump

* Change get texture methods to return descriptors created from ResourceManager state

 This is required to ensure that reserved textures and images will not be bound as a guest texture/image

* Fix BindlessElimination.SetHandle inserting coords at the wrong place
This commit is contained in:
gdkchan 2023-07-03 14:29:27 -03:00 committed by GitHub
parent 3b46bb73f7
commit 1c7a90ef35
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 656 additions and 659 deletions

View file

@ -115,36 +115,6 @@ namespace Ryujinx.Graphics.Shader.Translation
_operations.Add(operation);
}
public TextureOperation CreateTextureOperation(
Instruction inst,
SamplerType type,
TextureFlags flags,
int handle,
int compIndex,
Operand[] dests,
params Operand[] sources)
{
return CreateTextureOperation(inst, type, TextureFormat.Unknown, flags, handle, compIndex, dests, sources);
}
public TextureOperation CreateTextureOperation(
Instruction inst,
SamplerType type,
TextureFormat format,
TextureFlags flags,
int handle,
int compIndex,
Operand[] dests,
params Operand[] sources)
{
if (!flags.HasFlag(TextureFlags.Bindless))
{
Config.SetUsedTexture(inst, type, format, flags, TextureOperation.DefaultCbufSlot, handle);
}
return new TextureOperation(inst, type, format, flags, handle, compIndex, dests, sources);
}
public void FlagAttributeRead(int attribute)
{
if (Config.Stage == ShaderStage.Vertex && attribute == AttributeConsts.InstanceId)

View file

@ -604,6 +604,45 @@ namespace Ryujinx.Graphics.Shader.Translation
return context.Add(Instruction.Subtract, Local(), a, b);
}
public static Operand ImageAtomic(
this EmitterContext context,
SamplerType type,
TextureFormat format,
TextureFlags flags,
int binding,
Operand[] sources)
{
Operand dest = Local();
context.Add(new TextureOperation(Instruction.ImageAtomic, type, format, flags, binding, 0, new[] { dest }, sources));
return dest;
}
public static void ImageLoad(
this EmitterContext context,
SamplerType type,
TextureFormat format,
TextureFlags flags,
int binding,
int compMask,
Operand[] dests,
Operand[] sources)
{
context.Add(new TextureOperation(Instruction.ImageLoad, type, format, flags, binding, compMask, dests, sources));
}
public static void ImageStore(
this EmitterContext context,
SamplerType type,
TextureFormat format,
TextureFlags flags,
int binding,
Operand[] sources)
{
context.Add(new TextureOperation(Instruction.ImageStore, type, format, flags, binding, 0, null, sources));
}
public static Operand IsNan(this EmitterContext context, Operand a, Instruction fpType = Instruction.FP32)
{
return context.Add(fpType | Instruction.IsNan, Local(), a);
@ -666,6 +705,21 @@ namespace Ryujinx.Graphics.Shader.Translation
: context.Load(storageKind, (int)ioVariable, arrayIndex, elemIndex);
}
public static Operand Lod(
this EmitterContext context,
SamplerType type,
TextureFlags flags,
int binding,
int compIndex,
Operand[] sources)
{
Operand dest = Local();
context.Add(new TextureOperation(Instruction.Lod, type, TextureFormat.Unknown, flags, binding, compIndex, new[] { dest }, sources));
return dest;
}
public static Operand MemoryBarrier(this EmitterContext context)
{
return context.Add(Instruction.MemoryBarrier);
@ -797,6 +851,33 @@ namespace Ryujinx.Graphics.Shader.Translation
: context.Add(Instruction.Store, storageKind, null, Const((int)ioVariable), arrayIndex, elemIndex, value);
}
public static void TextureSample(
this EmitterContext context,
SamplerType type,
TextureFlags flags,
int binding,
int compMask,
Operand[] dests,
Operand[] sources)
{
context.Add(new TextureOperation(Instruction.TextureSample, type, TextureFormat.Unknown, flags, binding, compMask, dests, sources));
}
public static Operand TextureSize(
this EmitterContext context,
SamplerType type,
TextureFlags flags,
int binding,
int compIndex,
Operand[] sources)
{
Operand dest = Local();
context.Add(new TextureOperation(Instruction.TextureSize, type, TextureFormat.Unknown, flags, binding, compIndex, new[] { dest }, sources));
return dest;
}
public static Operand UnpackDouble2x32High(this EmitterContext context, Operand a)
{
return UnpackDouble2x32(context, a, 1);

View file

@ -222,8 +222,6 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
private static void SetHandle(ShaderConfig config, TextureOperation texOp, int cbufOffset, int cbufSlot, bool rewriteSamplerType, bool isImage)
{
texOp.SetHandle(cbufOffset, cbufSlot);
if (rewriteSamplerType)
{
SamplerType newType = config.GpuAccessor.QuerySamplerType(cbufOffset, cbufSlot);
@ -234,7 +232,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
}
else if (texOp.Type == SamplerType.TextureBuffer && newType == SamplerType.Texture1D)
{
int coordsCount = 1;
int coordsCount = 2;
if (InstEmit.Sample1DAs2D)
{
@ -255,7 +253,15 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
}
}
config.SetUsedTexture(texOp.Inst, texOp.Type, texOp.Format, texOp.Flags, cbufSlot, cbufOffset);
int binding = config.ResourceManager.GetTextureOrImageBinding(
texOp.Inst,
texOp.Type,
texOp.Format,
texOp.Flags & ~TextureFlags.Bindless,
cbufSlot,
cbufOffset);
texOp.SetBinding(binding);
}
}
}

View file

@ -7,6 +7,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
static class BindlessToIndexed
{
private const int NvnTextureBufferIndex = 2;
public static void RunPass(BasicBlock block, ShaderConfig config)
{
// We can turn a bindless texture access into a indexed access,
@ -43,7 +45,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
if (ldcSrc0.Type != OperandType.Constant ||
!config.ResourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int src0CbufSlot) ||
src0CbufSlot != 2)
src0CbufSlot != NvnTextureBufferIndex)
{
continue;
}
@ -102,8 +104,15 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
private static void TurnIntoIndexed(ShaderConfig config, TextureOperation texOp, int handle)
{
texOp.TurnIntoIndexed(handle);
config.SetUsedTexture(texOp.Inst, texOp.Type, texOp.Format, texOp.Flags, texOp.CbufSlot, handle);
int binding = config.ResourceManager.GetTextureOrImageBinding(
texOp.Inst,
texOp.Type | SamplerType.Indexed,
texOp.Format,
texOp.Flags & ~TextureFlags.Bindless,
NvnTextureBufferIndex,
handle);
texOp.TurnIntoIndexed(binding);
}
}
}

View file

@ -1,4 +1,5 @@
using Ryujinx.Common;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using System;
using System.Collections.Generic;
@ -13,9 +14,13 @@ namespace Ryujinx.Graphics.Shader.Translation
private const int DefaultLocalMemorySize = 128;
private const int DefaultSharedMemorySize = 4096;
private static readonly string[] _stagePrefixes = { "cp", "vp", "tcp", "tep", "gp", "fp" };
// TODO: Non-hardcoded array size.
public const int SamplerArraySize = 4;
private static readonly string[] _stagePrefixes = new string[] { "cp", "vp", "tcp", "tep", "gp", "fp" };
private readonly IGpuAccessor _gpuAccessor;
private readonly ShaderStage _stage;
private readonly string _stagePrefix;
private readonly int[] _cbSlotToBindingMap;
@ -27,6 +32,19 @@ namespace Ryujinx.Graphics.Shader.Translation
private readonly HashSet<int> _usedConstantBufferBindings;
private readonly record struct TextureInfo(int CbufSlot, int Handle, bool Indexed, TextureFormat Format);
private struct TextureMeta
{
public int Binding;
public bool AccurateType;
public SamplerType Type;
public TextureUsageFlags UsageFlags;
}
private readonly Dictionary<TextureInfo, TextureMeta> _usedTextures;
private readonly Dictionary<TextureInfo, TextureMeta> _usedImages;
public int LocalMemoryId { get; private set; }
public int SharedMemoryId { get; private set; }
@ -36,6 +54,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{
_gpuAccessor = gpuAccessor;
Properties = properties;
_stage = stage;
_stagePrefix = GetShaderStagePrefix(stage);
_cbSlotToBindingMap = new int[18];
@ -48,7 +67,10 @@ namespace Ryujinx.Graphics.Shader.Translation
_usedConstantBufferBindings = new HashSet<int>();
properties.AddConstantBuffer(0, new BufferDefinition(BufferLayout.Std140, 0, 0, "support_buffer", SupportBuffer.GetStructureType()));
_usedTextures = new Dictionary<TextureInfo, TextureMeta>();
_usedImages = new Dictionary<TextureInfo, TextureMeta>();
properties.AddOrUpdateConstantBuffer(0, new BufferDefinition(BufferLayout.Std140, 0, 0, "support_buffer", SupportBuffer.GetStructureType()));
LocalMemoryId = -1;
SharedMemoryId = -1;
@ -166,6 +188,198 @@ namespace Ryujinx.Graphics.Shader.Translation
return false;
}
public int GetTextureOrImageBinding(
Instruction inst,
SamplerType type,
TextureFormat format,
TextureFlags flags,
int cbufSlot,
int handle)
{
inst &= Instruction.Mask;
bool isImage = inst == Instruction.ImageLoad || inst == Instruction.ImageStore || inst == Instruction.ImageAtomic;
bool isWrite = inst == Instruction.ImageStore || inst == Instruction.ImageAtomic;
bool accurateType = inst != Instruction.Lod && inst != Instruction.TextureSize;
bool intCoords = isImage || flags.HasFlag(TextureFlags.IntCoords) || inst == Instruction.TextureSize;
bool coherent = flags.HasFlag(TextureFlags.Coherent);
if (!isImage)
{
format = TextureFormat.Unknown;
}
int binding = GetTextureOrImageBinding(cbufSlot, handle, type, format, isImage, intCoords, isWrite, accurateType, coherent);
_gpuAccessor.RegisterTexture(handle, cbufSlot);
return binding;
}
private int GetTextureOrImageBinding(
int cbufSlot,
int handle,
SamplerType type,
TextureFormat format,
bool isImage,
bool intCoords,
bool write,
bool accurateType,
bool coherent)
{
var dimensions = type.GetDimensions();
var isIndexed = type.HasFlag(SamplerType.Indexed);
var dict = isImage ? _usedImages : _usedTextures;
var usageFlags = TextureUsageFlags.None;
if (intCoords)
{
usageFlags |= TextureUsageFlags.NeedsScaleValue;
var canScale = _stage.SupportsRenderScale() && !isIndexed && !write && dimensions == 2;
if (!canScale)
{
// Resolution scaling cannot be applied to this texture right now.
// Flag so that we know to blacklist scaling on related textures when binding them.
usageFlags |= TextureUsageFlags.ResScaleUnsupported;
}
}
if (write)
{
usageFlags |= TextureUsageFlags.ImageStore;
}
if (coherent)
{
usageFlags |= TextureUsageFlags.ImageCoherent;
}
int arraySize = isIndexed ? SamplerArraySize : 1;
int firstBinding = -1;
for (int layer = 0; layer < arraySize; layer++)
{
var info = new TextureInfo(cbufSlot, handle + layer * 2, isIndexed, format);
var meta = new TextureMeta()
{
AccurateType = accurateType,
Type = type,
UsageFlags = usageFlags
};
int binding;
if (dict.TryGetValue(info, out var existingMeta))
{
dict[info] = MergeTextureMeta(meta, existingMeta);
binding = existingMeta.Binding;
}
else
{
bool isBuffer = (type & SamplerType.Mask) == SamplerType.TextureBuffer;
binding = isImage
? _gpuAccessor.QueryBindingImage(dict.Count, isBuffer)
: _gpuAccessor.QueryBindingTexture(dict.Count, isBuffer);
meta.Binding = binding;
dict.Add(info, meta);
}
string nameSuffix;
if (isImage)
{
nameSuffix = cbufSlot < 0
? $"i_tcb_{handle:X}_{format.ToGlslFormat()}"
: $"i_cb{cbufSlot}_{handle:X}_{format.ToGlslFormat()}";
}
else
{
nameSuffix = cbufSlot < 0 ? $"t_tcb_{handle:X}" : $"t_cb{cbufSlot}_{handle:X}";
}
var definition = new TextureDefinition(
isImage ? 3 : 2,
binding,
$"{_stagePrefix}_{nameSuffix}",
meta.Type,
info.Format,
meta.UsageFlags);
if (isImage)
{
Properties.AddOrUpdateImage(binding, definition);
}
else
{
Properties.AddOrUpdateTexture(binding, definition);
}
if (layer == 0)
{
firstBinding = binding;
}
}
return firstBinding;
}
private static TextureMeta MergeTextureMeta(TextureMeta meta, TextureMeta existingMeta)
{
meta.Binding = existingMeta.Binding;
meta.UsageFlags |= existingMeta.UsageFlags;
// If the texture we have has inaccurate type information, then
// we prefer the most accurate one.
if (existingMeta.AccurateType)
{
meta.AccurateType = true;
meta.Type = existingMeta.Type;
}
return meta;
}
public void SetUsageFlagsForTextureQuery(int binding, SamplerType type)
{
TextureInfo selectedInfo = default;
TextureMeta selectedMeta = default;
bool found = false;
foreach ((TextureInfo info, TextureMeta meta) in _usedTextures)
{
if (meta.Binding == binding)
{
selectedInfo = info;
selectedMeta = meta;
found = true;
break;
}
}
if (found)
{
selectedMeta.UsageFlags |= TextureUsageFlags.NeedsScaleValue;
var dimensions = type.GetDimensions();
var isIndexed = type.HasFlag(SamplerType.Indexed);
var canScale = _stage.SupportsRenderScale() && !isIndexed && dimensions == 2;
if (!canScale)
{
// Resolution scaling cannot be applied to this texture right now.
// Flag so that we know to blacklist scaling on related textures when binding them.
selectedMeta.UsageFlags |= TextureUsageFlags.ResScaleUnsupported;
}
_usedTextures[selectedInfo] = selectedMeta;
}
}
public void SetUsedConstantBufferBinding(int binding)
{
_usedConstantBufferBindings.Add(binding);
@ -208,10 +422,8 @@ namespace Ryujinx.Graphics.Shader.Translation
if (binding >= 0)
{
(int sbCbSlot, int sbCbOffset) = UnpackSbCbInfo(key);
descriptors[descriptorIndex++] = new BufferDescriptor(binding, slot, sbCbSlot, sbCbOffset)
{
Flags = (_sbSlotWritten & (1u << slot)) != 0 ? BufferUsageFlags.Write : BufferUsageFlags.None,
};
BufferUsageFlags flags = (_sbSlotWritten & (1u << slot)) != 0 ? BufferUsageFlags.Write : BufferUsageFlags.None;
descriptors[descriptorIndex++] = new BufferDescriptor(binding, slot, sbCbSlot, sbCbOffset, flags);
}
}
@ -223,6 +435,64 @@ namespace Ryujinx.Graphics.Shader.Translation
return descriptors;
}
public TextureDescriptor[] GetTextureDescriptors()
{
return GetDescriptors(_usedTextures, _usedTextures.Count);
}
public TextureDescriptor[] GetImageDescriptors()
{
return GetDescriptors(_usedImages, _usedImages.Count);
}
private static TextureDescriptor[] GetDescriptors(IReadOnlyDictionary<TextureInfo, TextureMeta> usedResources, int count)
{
TextureDescriptor[] descriptors = new TextureDescriptor[count];
int descriptorIndex = 0;
foreach ((TextureInfo info, TextureMeta meta) in usedResources)
{
descriptors[descriptorIndex++] = new TextureDescriptor(
meta.Binding,
meta.Type,
info.Format,
info.CbufSlot,
info.Handle,
meta.UsageFlags);
}
return descriptors;
}
public (int, int) GetCbufSlotAndHandleForTexture(int binding)
{
foreach ((TextureInfo info, TextureMeta meta) in _usedTextures)
{
if (meta.Binding == binding)
{
return (info.CbufSlot, info.Handle);
}
}
throw new ArgumentException($"Binding {binding} is invalid.");
}
private static int FindDescriptorIndex(TextureDescriptor[] array, int binding)
{
return Array.FindIndex(array, x => x.Binding == binding);
}
public int FindTextureDescriptorIndex(int binding)
{
return FindDescriptorIndex(GetTextureDescriptors(), binding);
}
public int FindImageDescriptorIndex(int binding)
{
return FindDescriptorIndex(GetImageDescriptors(), binding);
}
private void AddNewConstantBuffer(int binding, string name)
{
StructureType type = new(new[]
@ -230,7 +500,7 @@ namespace Ryujinx.Graphics.Shader.Translation
new StructureField(AggregateType.Array | AggregateType.Vector4 | AggregateType.FP32, "data", Constants.ConstantBufferSize / 16),
});
Properties.AddConstantBuffer(binding, new BufferDefinition(BufferLayout.Std140, 0, binding, name, type));
Properties.AddOrUpdateConstantBuffer(binding, new BufferDefinition(BufferLayout.Std140, 0, binding, name, type));
}
private void AddNewStorageBuffer(int binding, string name)
@ -240,7 +510,7 @@ namespace Ryujinx.Graphics.Shader.Translation
new StructureField(AggregateType.Array | AggregateType.U32, "data", 0),
});
Properties.AddStorageBuffer(binding, new BufferDefinition(BufferLayout.Std430, 1, binding, name, type));
Properties.AddOrUpdateStorageBuffer(binding, new BufferDefinition(BufferLayout.Std430, 1, binding, name, type));
}
public static string GetShaderStagePrefix(ShaderStage stage)

View file

@ -61,7 +61,7 @@ namespace Ryujinx.Graphics.Shader.Translation
if (texOp.Inst == Instruction.TextureSample)
{
node = InsertCoordNormalization(node, config);
node = InsertCoordNormalization(hfm, node, config);
node = InsertCoordGatherBias(node, config);
node = InsertConstOffsets(node, config);
@ -285,8 +285,8 @@ namespace Ryujinx.Graphics.Shader.Translation
{
int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TexelFetchScale);
int samplerIndex = isImage
? config.GetTextureDescriptors().Length + config.FindImageDescriptorIndex(texOp)
: config.FindTextureDescriptorIndex(texOp);
? config.ResourceManager.GetTextureDescriptors().Length + config.ResourceManager.FindImageDescriptorIndex(texOp.Binding)
: config.ResourceManager.FindTextureDescriptorIndex(texOp.Binding);
for (int index = 0; index < coordsCount; index++)
{
@ -326,7 +326,7 @@ namespace Ryujinx.Graphics.Shader.Translation
TypeSupportsScale(texOp.Type))
{
int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TextureSizeUnscale);
int samplerIndex = config.FindTextureDescriptorIndex(texOp, ignoreType: true);
int samplerIndex = config.ResourceManager.FindTextureDescriptorIndex(texOp.Binding);
for (int index = texOp.DestsCount - 1; index >= 0; index--)
{
@ -368,7 +368,7 @@ namespace Ryujinx.Graphics.Shader.Translation
return (type & SamplerType.Mask) == SamplerType.Texture2D;
}
private static LinkedListNode<INode> InsertCoordNormalization(LinkedListNode<INode> node, ShaderConfig config)
private static LinkedListNode<INode> InsertCoordNormalization(HelperFunctionManager hfm, LinkedListNode<INode> node, ShaderConfig config)
{
// Emulate non-normalized coordinates by normalizing the coordinates on the shader.
// Without normalization, the coordinates are expected to the in the [0, W or H] range,
@ -378,9 +378,17 @@ namespace Ryujinx.Graphics.Shader.Translation
TextureOperation texOp = (TextureOperation)node.Value;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
if (isBindless)
{
return node;
}
bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
bool isCoordNormalized = isBindless || config.GpuAccessor.QueryTextureCoordNormalized(texOp.Handle, texOp.CbufSlot);
(int cbufSlot, int handle) = config.ResourceManager.GetCbufSlotAndHandleForTexture(texOp.Binding);
bool isCoordNormalized = config.GpuAccessor.QueryTextureCoordNormalized(handle, cbufSlot);
if (isCoordNormalized || intCoords)
{
@ -411,18 +419,17 @@ namespace Ryujinx.Graphics.Shader.Translation
texSizeSources = new Operand[] { Const(0) };
}
node.List.AddBefore(node, new TextureOperation(
LinkedListNode<INode> textureSizeNode = node.List.AddBefore(node, new TextureOperation(
Instruction.TextureSize,
texOp.Type,
texOp.Format,
texOp.Flags,
texOp.CbufSlot,
texOp.Handle,
texOp.Binding,
index,
new[] { coordSize },
texSizeSources));
config.SetUsedTexture(Instruction.TextureSize, texOp.Type, texOp.Format, texOp.Flags, texOp.CbufSlot, texOp.Handle);
config.ResourceManager.SetUsageFlagsForTextureQuery(texOp.Binding, texOp.Type);
Operand source = texOp.GetSource(coordsIndex + index);
@ -431,6 +438,8 @@ namespace Ryujinx.Graphics.Shader.Translation
node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Divide, coordNormalized, source, GenerateI2f(node, coordSize)));
texOp.SetSource(coordsIndex + index, coordNormalized);
InsertTextureSizeUnscale(hfm, textureSizeNode, config);
}
return node;
@ -491,14 +500,11 @@ namespace Ryujinx.Graphics.Shader.Translation
texOp.Type,
texOp.Format,
texOp.Flags,
texOp.CbufSlot,
texOp.Handle,
texOp.Binding,
index,
new[] { coordSize },
texSizeSources));
config.SetUsedTexture(Instruction.TextureSize, texOp.Type, texOp.Format, texOp.Flags, texOp.CbufSlot, texOp.Handle);
node.List.AddBefore(node, new Operation(
Instruction.FP32 | Instruction.Multiply,
scaledSize,
@ -686,8 +692,6 @@ namespace Ryujinx.Graphics.Shader.Translation
for (int index = 0; index < coordsCount; index++)
{
config.SetUsedTexture(Instruction.TextureSize, texOp.Type, texOp.Format, texOp.Flags, texOp.CbufSlot, texOp.Handle);
Operand offset = Local();
Operand intOffset = offsets[index + (hasOffsets ? compIndex * coordsCount : 0)];
@ -712,8 +716,7 @@ namespace Ryujinx.Graphics.Shader.Translation
texOp.Type,
texOp.Format,
texOp.Flags & ~(TextureFlags.Offset | TextureFlags.Offsets),
texOp.CbufSlot,
texOp.Handle,
texOp.Binding,
1,
new[] { dests[destIndex++] },
newSources);
@ -744,8 +747,6 @@ namespace Ryujinx.Graphics.Shader.Translation
for (int index = 0; index < coordsCount; index++)
{
config.SetUsedTexture(Instruction.TextureSize, texOp.Type, texOp.Format, texOp.Flags, texOp.CbufSlot, texOp.Handle);
Operand offset = Local();
Operand intOffset = offsets[index];
@ -771,8 +772,7 @@ namespace Ryujinx.Graphics.Shader.Translation
texOp.Type,
texOp.Format,
texOp.Flags & ~(TextureFlags.Offset | TextureFlags.Offsets),
texOp.CbufSlot,
texOp.Handle,
texOp.Binding,
componentIndex,
dests,
sources);
@ -806,8 +806,7 @@ namespace Ryujinx.Graphics.Shader.Translation
texOp.Type,
texOp.Format,
texOp.Flags,
texOp.CbufSlot,
texOp.Handle,
texOp.Binding,
0,
new[] { lod },
lodSources));
@ -832,8 +831,7 @@ namespace Ryujinx.Graphics.Shader.Translation
texOp.Type,
texOp.Format,
texOp.Flags,
texOp.CbufSlot,
texOp.Handle,
texOp.Binding,
index,
new[] { texSizes[index] },
texSizeSources));
@ -853,7 +851,9 @@ namespace Ryujinx.Graphics.Shader.Translation
return node;
}
TextureFormat format = config.GpuAccessor.QueryTextureFormat(texOp.Handle, texOp.CbufSlot);
(int cbufSlot, int handle) = config.ResourceManager.GetCbufSlotAndHandleForTexture(texOp.Binding);
TextureFormat format = config.GpuAccessor.QueryTextureFormat(handle, cbufSlot);
int maxPositive = format switch
{

View file

@ -2,16 +2,12 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace Ryujinx.Graphics.Shader.Translation
{
class ShaderConfig
{
// TODO: Non-hardcoded array size.
public const int SamplerArraySize = 4;
private const int ThreadsPerWarp = 32;
public ShaderStage Stage { get; }
@ -110,20 +106,6 @@ namespace Ryujinx.Graphics.Shader.Translation
public UInt128 NextInputAttributesComponents { get; private set; }
public UInt128 ThisInputAttributesComponents { get; private set; }
private readonly record struct TextureInfo(int CbufSlot, int Handle, bool Indexed, TextureFormat Format);
private struct TextureMeta
{
public bool AccurateType;
public SamplerType Type;
public TextureUsageFlags UsageFlags;
}
private readonly Dictionary<TextureInfo, TextureMeta> _usedTextures;
private readonly Dictionary<TextureInfo, TextureMeta> _usedImages;
private TextureDescriptor[] _cachedTextureDescriptors;
private TextureDescriptor[] _cachedImageDescriptors;
public ShaderConfig(ShaderStage stage, IGpuAccessor gpuAccessor, TranslationOptions options, int localMemorySize)
{
Stage = stage;
@ -141,9 +123,6 @@ namespace Ryujinx.Graphics.Shader.Translation
UsedInputAttributesPerPatch = new HashSet<int>();
UsedOutputAttributesPerPatch = new HashSet<int>();
_usedTextures = new Dictionary<TextureInfo, TextureMeta>();
_usedImages = new Dictionary<TextureInfo, TextureMeta>();
ResourceManager = new ResourceManager(stage, gpuAccessor, new ShaderProperties());
if (!gpuAccessor.QueryHostSupportsTransformFeedback() && gpuAccessor.QueryTransformFeedbackEnabled())
@ -156,7 +135,7 @@ namespace Ryujinx.Graphics.Shader.Translation
BufferDefinition tfeInfoBuffer = new(BufferLayout.Std430, 1, Constants.TfeInfoBinding, "tfe_info", tfeInfoStruct);
Properties.AddStorageBuffer(Constants.TfeInfoBinding, tfeInfoBuffer);
Properties.AddOrUpdateStorageBuffer(Constants.TfeInfoBinding, tfeInfoBuffer);
StructureType tfeDataStruct = new(new StructureField[]
{
@ -167,7 +146,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{
int binding = Constants.TfeBufferBaseBinding + i;
BufferDefinition tfeDataBuffer = new(BufferLayout.Std430, 1, binding, $"tfe_data{i}", tfeDataStruct);
Properties.AddStorageBuffer(binding, tfeDataBuffer);
Properties.AddOrUpdateStorageBuffer(binding, tfeDataBuffer);
}
}
}
@ -443,22 +422,6 @@ namespace Ryujinx.Graphics.Shader.Translation
UsedInputAttributes |= other.UsedInputAttributes;
UsedOutputAttributes |= other.UsedOutputAttributes;
foreach (var kv in other._usedTextures)
{
if (!_usedTextures.TryAdd(kv.Key, kv.Value))
{
_usedTextures[kv.Key] = MergeTextureMeta(kv.Value, _usedTextures[kv.Key]);
}
}
foreach (var kv in other._usedImages)
{
if (!_usedImages.TryAdd(kv.Key, kv.Value))
{
_usedImages[kv.Key] = MergeTextureMeta(kv.Value, _usedImages[kv.Key]);
}
}
}
public void SetLayerOutputAttribute(int attr)
@ -642,196 +605,13 @@ namespace Ryujinx.Graphics.Shader.Translation
UsedFeatures |= flags;
}
public void SetUsedTexture(
Instruction inst,
SamplerType type,
TextureFormat format,
TextureFlags flags,
int cbufSlot,
int handle)
{
inst &= Instruction.Mask;
bool isImage = inst == Instruction.ImageLoad || inst == Instruction.ImageStore || inst == Instruction.ImageAtomic;
bool isWrite = inst == Instruction.ImageStore || inst == Instruction.ImageAtomic;
bool accurateType = inst != Instruction.Lod && inst != Instruction.TextureSize;
bool coherent = flags.HasFlag(TextureFlags.Coherent);
if (isImage)
{
SetUsedTextureOrImage(_usedImages, cbufSlot, handle, type, format, true, isWrite, false, coherent);
}
else
{
bool intCoords = flags.HasFlag(TextureFlags.IntCoords) || inst == Instruction.TextureSize;
SetUsedTextureOrImage(_usedTextures, cbufSlot, handle, type, TextureFormat.Unknown, intCoords, false, accurateType, coherent);
}
GpuAccessor.RegisterTexture(handle, cbufSlot);
}
private void SetUsedTextureOrImage(
Dictionary<TextureInfo, TextureMeta> dict,
int cbufSlot,
int handle,
SamplerType type,
TextureFormat format,
bool intCoords,
bool write,
bool accurateType,
bool coherent)
{
var dimensions = type.GetDimensions();
var isIndexed = type.HasFlag(SamplerType.Indexed);
var usageFlags = TextureUsageFlags.None;
if (intCoords)
{
usageFlags |= TextureUsageFlags.NeedsScaleValue;
var canScale = Stage.SupportsRenderScale() && !isIndexed && !write && dimensions == 2;
if (!canScale)
{
// Resolution scaling cannot be applied to this texture right now.
// Flag so that we know to blacklist scaling on related textures when binding them.
usageFlags |= TextureUsageFlags.ResScaleUnsupported;
}
}
if (write)
{
usageFlags |= TextureUsageFlags.ImageStore;
}
if (coherent)
{
usageFlags |= TextureUsageFlags.ImageCoherent;
}
int arraySize = isIndexed ? SamplerArraySize : 1;
for (int layer = 0; layer < arraySize; layer++)
{
var info = new TextureInfo(cbufSlot, handle + layer * 2, isIndexed, format);
var meta = new TextureMeta()
{
AccurateType = accurateType,
Type = type,
UsageFlags = usageFlags,
};
if (dict.TryGetValue(info, out var existingMeta))
{
dict[info] = MergeTextureMeta(meta, existingMeta);
}
else
{
dict.Add(info, meta);
}
}
}
private static TextureMeta MergeTextureMeta(TextureMeta meta, TextureMeta existingMeta)
{
meta.UsageFlags |= existingMeta.UsageFlags;
// If the texture we have has inaccurate type information, then
// we prefer the most accurate one.
if (existingMeta.AccurateType)
{
meta.AccurateType = true;
meta.Type = existingMeta.Type;
}
return meta;
}
public TextureDescriptor[] GetTextureDescriptors()
{
return _cachedTextureDescriptors ??= GetTextureOrImageDescriptors(_usedTextures, GpuAccessor.QueryBindingTexture);
}
public TextureDescriptor[] GetImageDescriptors()
{
return _cachedImageDescriptors ??= GetTextureOrImageDescriptors(_usedImages, GpuAccessor.QueryBindingImage);
}
private static TextureDescriptor[] GetTextureOrImageDescriptors(Dictionary<TextureInfo, TextureMeta> dict, Func<int, bool, int> getBindingCallback)
{
var descriptors = new TextureDescriptor[dict.Count];
int i = 0;
foreach (var kv in dict.OrderBy(x => x.Key.Indexed).ThenBy(x => x.Key.Handle))
{
var info = kv.Key;
var meta = kv.Value;
bool isBuffer = (meta.Type & SamplerType.Mask) == SamplerType.TextureBuffer;
int binding = getBindingCallback(i, isBuffer);
descriptors[i] = new TextureDescriptor(binding, meta.Type, info.Format, info.CbufSlot, info.Handle);
descriptors[i].SetFlag(meta.UsageFlags);
i++;
}
return descriptors;
}
public TextureDescriptor FindTextureDescriptor(AstTextureOperation texOp)
{
TextureDescriptor[] descriptors = GetTextureDescriptors();
for (int i = 0; i < descriptors.Length; i++)
{
var descriptor = descriptors[i];
if (descriptor.CbufSlot == texOp.CbufSlot &&
descriptor.HandleIndex == texOp.Handle &&
descriptor.Format == texOp.Format)
{
return descriptor;
}
}
return default;
}
private static int FindDescriptorIndex(TextureDescriptor[] array, TextureOperation texOp, bool ignoreType = false)
{
for (int i = 0; i < array.Length; i++)
{
var descriptor = array[i];
if ((descriptor.Type == texOp.Type || ignoreType) &&
descriptor.CbufSlot == texOp.CbufSlot &&
descriptor.HandleIndex == texOp.Handle &&
descriptor.Format == texOp.Format)
{
return i;
}
}
return -1;
}
public int FindTextureDescriptorIndex(TextureOperation texOp, bool ignoreType = false)
{
return FindDescriptorIndex(GetTextureDescriptors(), texOp, ignoreType);
}
public int FindImageDescriptorIndex(TextureOperation texOp)
{
return FindDescriptorIndex(GetImageDescriptors(), texOp);
}
public ShaderProgramInfo CreateProgramInfo(ShaderIdentification identification = ShaderIdentification.None)
{
return new ShaderProgramInfo(
ResourceManager.GetConstantBufferDescriptors(),
ResourceManager.GetStorageBufferDescriptors(),
GetTextureDescriptors(),
GetImageDescriptors(),
ResourceManager.GetTextureDescriptors(),
ResourceManager.GetImageDescriptors(),
identification,
GpLayerInputAttribute,
Stage,