aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs')
-rw-r--r--src/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs944
1 files changed, 944 insertions, 0 deletions
diff --git a/src/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs b/src/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs
new file mode 100644
index 00000000..22f5a671
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs
@@ -0,0 +1,944 @@
+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; }
+
+ public bool GpPassthrough { get; }
+ public bool LastInVertexPipeline { get; private set; }
+
+ public bool HasLayerInputAttribute { get; private set; }
+ public int GpLayerInputAttribute { get; private set; }
+ public int ThreadsPerInputPrimitive { get; }
+
+ public OutputTopology OutputTopology { get; }
+
+ public int MaxOutputVertices { get; }
+
+ public int LocalMemorySize { get; }
+
+ public ImapPixelType[] ImapTypes { get; }
+
+ public int OmapTargets { get; }
+ public bool OmapSampleMask { get; }
+ public bool OmapDepth { get; }
+
+ public IGpuAccessor GpuAccessor { get; }
+
+ public TranslationOptions Options { get; }
+
+ public bool TransformFeedbackEnabled { get; }
+
+ private TransformFeedbackOutput[] _transformFeedbackOutputs;
+
+ readonly struct TransformFeedbackVariable : IEquatable<TransformFeedbackVariable>
+ {
+ public IoVariable IoVariable { get; }
+ public int Location { get; }
+ public int Component { get; }
+
+ public TransformFeedbackVariable(IoVariable ioVariable, int location = 0, int component = 0)
+ {
+ IoVariable = ioVariable;
+ Location = location;
+ Component = component;
+ }
+
+ public override bool Equals(object other)
+ {
+ return other is TransformFeedbackVariable tfbVar && Equals(tfbVar);
+ }
+
+ public bool Equals(TransformFeedbackVariable other)
+ {
+ return IoVariable == other.IoVariable &&
+ Location == other.Location &&
+ Component == other.Component;
+ }
+
+ public override int GetHashCode()
+ {
+ return (int)IoVariable | (Location << 8) | (Component << 16);
+ }
+
+ public override string ToString()
+ {
+ return $"{IoVariable}.{Location}.{Component}";
+ }
+ }
+
+ private readonly Dictionary<TransformFeedbackVariable, TransformFeedbackOutput> _transformFeedbackDefinitions;
+
+ public int Size { get; private set; }
+
+ public byte ClipDistancesWritten { get; private set; }
+
+ public FeatureFlags UsedFeatures { get; private set; }
+
+ public int Cb1DataSize { get; private set; }
+
+ public bool LayerOutputWritten { get; private set; }
+ public int LayerOutputAttribute { get; private set; }
+
+ public bool NextUsesFixedFuncAttributes { get; private set; }
+ public int UsedInputAttributes { get; private set; }
+ public int UsedOutputAttributes { get; private set; }
+ public HashSet<int> UsedInputAttributesPerPatch { get; }
+ public HashSet<int> UsedOutputAttributesPerPatch { get; }
+ public HashSet<int> NextUsedInputAttributesPerPatch { get; private set; }
+ public int PassthroughAttributes { get; private set; }
+ private int _nextUsedInputAttributes;
+ private int _thisUsedInputAttributes;
+ private Dictionary<int, int> _perPatchAttributeLocations;
+
+ public UInt128 NextInputAttributesComponents { get; private set; }
+ public UInt128 ThisInputAttributesComponents { get; private set; }
+
+ public int AccessibleStorageBuffersMask { get; private set; }
+ public int AccessibleConstantBuffersMask { get; private set; }
+
+ private int _usedConstantBuffers;
+ private int _usedStorageBuffers;
+ private int _usedStorageBuffersWrite;
+
+ 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 BufferDescriptor[] _cachedConstantBufferDescriptors;
+ private BufferDescriptor[] _cachedStorageBufferDescriptors;
+ private TextureDescriptor[] _cachedTextureDescriptors;
+ private TextureDescriptor[] _cachedImageDescriptors;
+
+ private int _firstConstantBufferBinding;
+ private int _firstStorageBufferBinding;
+
+ public int FirstConstantBufferBinding => _firstConstantBufferBinding;
+ public int FirstStorageBufferBinding => _firstStorageBufferBinding;
+
+ public ShaderConfig(IGpuAccessor gpuAccessor, TranslationOptions options)
+ {
+ Stage = ShaderStage.Compute;
+ GpuAccessor = gpuAccessor;
+ Options = options;
+
+ _transformFeedbackDefinitions = new Dictionary<TransformFeedbackVariable, TransformFeedbackOutput>();
+
+ AccessibleStorageBuffersMask = (1 << GlobalMemory.StorageMaxCount) - 1;
+ AccessibleConstantBuffersMask = (1 << GlobalMemory.UbeMaxCount) - 1;
+
+ UsedInputAttributesPerPatch = new HashSet<int>();
+ UsedOutputAttributesPerPatch = new HashSet<int>();
+
+ _usedTextures = new Dictionary<TextureInfo, TextureMeta>();
+ _usedImages = new Dictionary<TextureInfo, TextureMeta>();
+ }
+
+ public ShaderConfig(
+ ShaderStage stage,
+ OutputTopology outputTopology,
+ int maxOutputVertices,
+ IGpuAccessor gpuAccessor,
+ TranslationOptions options) : this(gpuAccessor, options)
+ {
+ Stage = stage;
+ ThreadsPerInputPrimitive = 1;
+ OutputTopology = outputTopology;
+ MaxOutputVertices = maxOutputVertices;
+ TransformFeedbackEnabled = gpuAccessor.QueryTransformFeedbackEnabled();
+
+ if (Stage != ShaderStage.Compute)
+ {
+ AccessibleConstantBuffersMask = 0;
+ }
+ }
+
+ public ShaderConfig(ShaderHeader header, IGpuAccessor gpuAccessor, TranslationOptions options) : this(gpuAccessor, options)
+ {
+ Stage = header.Stage;
+ GpPassthrough = header.Stage == ShaderStage.Geometry && header.GpPassthrough;
+ ThreadsPerInputPrimitive = header.ThreadsPerInputPrimitive;
+ OutputTopology = header.OutputTopology;
+ MaxOutputVertices = header.MaxOutputVertexCount;
+ LocalMemorySize = header.ShaderLocalMemoryLowSize + header.ShaderLocalMemoryHighSize + (header.ShaderLocalMemoryCrsSize / ThreadsPerWarp);
+ ImapTypes = header.ImapTypes;
+ OmapTargets = header.OmapTargets;
+ OmapSampleMask = header.OmapSampleMask;
+ OmapDepth = header.OmapDepth;
+ TransformFeedbackEnabled = gpuAccessor.QueryTransformFeedbackEnabled();
+ LastInVertexPipeline = header.Stage < ShaderStage.Fragment;
+ }
+
+ private void EnsureTransformFeedbackInitialized()
+ {
+ if (HasTransformFeedbackOutputs() && _transformFeedbackOutputs == null)
+ {
+ TransformFeedbackOutput[] transformFeedbackOutputs = new TransformFeedbackOutput[0xc0];
+ ulong vecMap = 0UL;
+
+ for (int tfbIndex = 0; tfbIndex < 4; tfbIndex++)
+ {
+ var locations = GpuAccessor.QueryTransformFeedbackVaryingLocations(tfbIndex);
+ var stride = GpuAccessor.QueryTransformFeedbackStride(tfbIndex);
+
+ for (int i = 0; i < locations.Length; i++)
+ {
+ byte wordOffset = locations[i];
+ if (wordOffset < 0xc0)
+ {
+ transformFeedbackOutputs[wordOffset] = new TransformFeedbackOutput(tfbIndex, i * 4, stride);
+ vecMap |= 1UL << (wordOffset / 4);
+ }
+ }
+ }
+
+ _transformFeedbackOutputs = transformFeedbackOutputs;
+
+ while (vecMap != 0)
+ {
+ int vecIndex = BitOperations.TrailingZeroCount(vecMap);
+
+ for (int subIndex = 0; subIndex < 4; subIndex++)
+ {
+ int wordOffset = vecIndex * 4 + subIndex;
+ int byteOffset = wordOffset * 4;
+
+ if (transformFeedbackOutputs[wordOffset].Valid)
+ {
+ IoVariable ioVariable = Instructions.AttributeMap.GetIoVariable(this, byteOffset, out int location);
+ int component = 0;
+
+ if (HasPerLocationInputOrOutputComponent(ioVariable, location, subIndex, isOutput: true))
+ {
+ component = subIndex;
+ }
+
+ var transformFeedbackVariable = new TransformFeedbackVariable(ioVariable, location, component);
+ _transformFeedbackDefinitions.TryAdd(transformFeedbackVariable, transformFeedbackOutputs[wordOffset]);
+ }
+ }
+
+ vecMap &= ~(1UL << vecIndex);
+ }
+ }
+ }
+
+ public TransformFeedbackOutput[] GetTransformFeedbackOutputs()
+ {
+ EnsureTransformFeedbackInitialized();
+ return _transformFeedbackOutputs;
+ }
+
+ public bool TryGetTransformFeedbackOutput(IoVariable ioVariable, int location, int component, out TransformFeedbackOutput transformFeedbackOutput)
+ {
+ EnsureTransformFeedbackInitialized();
+ var transformFeedbackVariable = new TransformFeedbackVariable(ioVariable, location, component);
+ return _transformFeedbackDefinitions.TryGetValue(transformFeedbackVariable, out transformFeedbackOutput);
+ }
+
+ private bool HasTransformFeedbackOutputs()
+ {
+ return TransformFeedbackEnabled && (LastInVertexPipeline || Stage == ShaderStage.Fragment);
+ }
+
+ public bool HasTransformFeedbackOutputs(bool isOutput)
+ {
+ return TransformFeedbackEnabled && ((isOutput && LastInVertexPipeline) || (!isOutput && Stage == ShaderStage.Fragment));
+ }
+
+ public bool HasPerLocationInputOrOutput(IoVariable ioVariable, bool isOutput)
+ {
+ if (ioVariable == IoVariable.UserDefined)
+ {
+ return (!isOutput && !UsedFeatures.HasFlag(FeatureFlags.IaIndexing)) ||
+ (isOutput && !UsedFeatures.HasFlag(FeatureFlags.OaIndexing));
+ }
+
+ return ioVariable == IoVariable.FragmentOutputColor;
+ }
+
+ public bool HasPerLocationInputOrOutputComponent(IoVariable ioVariable, int location, int component, bool isOutput)
+ {
+ if (ioVariable != IoVariable.UserDefined || !HasTransformFeedbackOutputs(isOutput))
+ {
+ return false;
+ }
+
+ return GetTransformFeedbackOutputComponents(location, component) == 1;
+ }
+
+ public TransformFeedbackOutput GetTransformFeedbackOutput(int wordOffset)
+ {
+ EnsureTransformFeedbackInitialized();
+
+ return _transformFeedbackOutputs[wordOffset];
+ }
+
+ public TransformFeedbackOutput GetTransformFeedbackOutput(int location, int component)
+ {
+ return GetTransformFeedbackOutput((AttributeConsts.UserAttributeBase / 4) + location * 4 + component);
+ }
+
+ public int GetTransformFeedbackOutputComponents(int location, int component)
+ {
+ EnsureTransformFeedbackInitialized();
+
+ int baseIndex = (AttributeConsts.UserAttributeBase / 4) + location * 4;
+ int index = baseIndex + component;
+ int count = 1;
+
+ for (; count < 4; count++)
+ {
+ ref var prev = ref _transformFeedbackOutputs[baseIndex + count - 1];
+ ref var curr = ref _transformFeedbackOutputs[baseIndex + count];
+
+ int prevOffset = prev.Offset;
+ int currOffset = curr.Offset;
+
+ if (!prev.Valid || !curr.Valid || prevOffset + 4 != currOffset)
+ {
+ break;
+ }
+ }
+
+ if (baseIndex + count <= index)
+ {
+ return 1;
+ }
+
+ return count;
+ }
+
+ public AggregateType GetFragmentOutputColorType(int location)
+ {
+ return AggregateType.Vector4 | GpuAccessor.QueryFragmentOutputType(location).ToAggregateType();
+ }
+
+ public AggregateType GetUserDefinedType(int location, bool isOutput)
+ {
+ if ((!isOutput && UsedFeatures.HasFlag(FeatureFlags.IaIndexing)) ||
+ (isOutput && UsedFeatures.HasFlag(FeatureFlags.OaIndexing)))
+ {
+ return AggregateType.Array | AggregateType.Vector4 | AggregateType.FP32;
+ }
+
+ AggregateType type = AggregateType.Vector4;
+
+ if (Stage == ShaderStage.Vertex && !isOutput)
+ {
+ type |= GpuAccessor.QueryAttributeType(location).ToAggregateType();
+ }
+ else
+ {
+ type |= AggregateType.FP32;
+ }
+
+ return type;
+ }
+
+ public int GetDepthRegister()
+ {
+ // The depth register is always two registers after the last color output.
+ return BitOperations.PopCount((uint)OmapTargets) + 1;
+ }
+
+ public uint ConstantBuffer1Read(int offset)
+ {
+ if (Cb1DataSize < offset + 4)
+ {
+ Cb1DataSize = offset + 4;
+ }
+
+ return GpuAccessor.ConstantBuffer1Read(offset);
+ }
+
+ public TextureFormat GetTextureFormat(int handle, int cbufSlot = -1)
+ {
+ // When the formatted load extension is supported, we don't need to
+ // specify a format, we can just declare it without a format and the GPU will handle it.
+ if (GpuAccessor.QueryHostSupportsImageLoadFormatted())
+ {
+ return TextureFormat.Unknown;
+ }
+
+ var format = GpuAccessor.QueryTextureFormat(handle, cbufSlot);
+
+ if (format == TextureFormat.Unknown)
+ {
+ GpuAccessor.Log($"Unknown format for texture {handle}.");
+
+ format = TextureFormat.R8G8B8A8Unorm;
+ }
+
+ return format;
+ }
+
+ private static bool FormatSupportsAtomic(TextureFormat format)
+ {
+ return format == TextureFormat.R32Sint || format == TextureFormat.R32Uint;
+ }
+
+ public TextureFormat GetTextureFormatAtomic(int handle, int cbufSlot = -1)
+ {
+ // Atomic image instructions do not support GL_EXT_shader_image_load_formatted,
+ // and must have a type specified. Default to R32Sint if not available.
+
+ var format = GpuAccessor.QueryTextureFormat(handle, cbufSlot);
+
+ if (!FormatSupportsAtomic(format))
+ {
+ GpuAccessor.Log($"Unsupported format for texture {handle}: {format}.");
+
+ format = TextureFormat.R32Sint;
+ }
+
+ return format;
+ }
+
+ public void SizeAdd(int size)
+ {
+ Size += size;
+ }
+
+ public void InheritFrom(ShaderConfig other)
+ {
+ ClipDistancesWritten |= other.ClipDistancesWritten;
+ UsedFeatures |= other.UsedFeatures;
+
+ UsedInputAttributes |= other.UsedInputAttributes;
+ UsedOutputAttributes |= other.UsedOutputAttributes;
+ _usedConstantBuffers |= other._usedConstantBuffers;
+ _usedStorageBuffers |= other._usedStorageBuffers;
+ _usedStorageBuffersWrite |= other._usedStorageBuffersWrite;
+
+ 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)
+ {
+ LayerOutputWritten = true;
+ LayerOutputAttribute = attr;
+ }
+
+ public void SetGeometryShaderLayerInputAttribute(int attr)
+ {
+ HasLayerInputAttribute = true;
+ GpLayerInputAttribute = attr;
+ }
+
+ public void SetLastInVertexPipeline()
+ {
+ LastInVertexPipeline = true;
+ }
+
+ public void SetInputUserAttributeFixedFunc(int index)
+ {
+ UsedInputAttributes |= 1 << index;
+ }
+
+ public void SetOutputUserAttributeFixedFunc(int index)
+ {
+ UsedOutputAttributes |= 1 << index;
+ }
+
+ public void SetInputUserAttribute(int index, int component)
+ {
+ int mask = 1 << index;
+
+ UsedInputAttributes |= mask;
+ _thisUsedInputAttributes |= mask;
+ ThisInputAttributesComponents |= UInt128.One << (index * 4 + component);
+ }
+
+ public void SetInputUserAttributePerPatch(int index)
+ {
+ UsedInputAttributesPerPatch.Add(index);
+ }
+
+ public void SetOutputUserAttribute(int index)
+ {
+ UsedOutputAttributes |= 1 << index;
+ }
+
+ public void SetOutputUserAttributePerPatch(int index)
+ {
+ UsedOutputAttributesPerPatch.Add(index);
+ }
+
+ public void MergeFromtNextStage(ShaderConfig config)
+ {
+ NextInputAttributesComponents = config.ThisInputAttributesComponents;
+ NextUsedInputAttributesPerPatch = config.UsedInputAttributesPerPatch;
+ NextUsesFixedFuncAttributes = config.UsedFeatures.HasFlag(FeatureFlags.FixedFuncAttr);
+ MergeOutputUserAttributes(config.UsedInputAttributes, config.UsedInputAttributesPerPatch);
+
+ if (UsedOutputAttributesPerPatch.Count != 0)
+ {
+ // Regular and per-patch input/output locations can't overlap,
+ // so we must assign on our location using unused regular input/output locations.
+
+ Dictionary<int, int> locationsMap = new Dictionary<int, int>();
+
+ int freeMask = ~UsedOutputAttributes;
+
+ foreach (int attr in UsedOutputAttributesPerPatch)
+ {
+ int location = BitOperations.TrailingZeroCount(freeMask);
+ if (location == 32)
+ {
+ config.GpuAccessor.Log($"No enough free locations for patch input/output 0x{attr:X}.");
+ break;
+ }
+
+ locationsMap.Add(attr, location);
+ freeMask &= ~(1 << location);
+ }
+
+ // Both stages must agree on the locations, so use the same "map" for both.
+ _perPatchAttributeLocations = locationsMap;
+ config._perPatchAttributeLocations = locationsMap;
+ }
+
+ // We don't consider geometry shaders using the geometry shader passthrough feature
+ // as being the last because when this feature is used, it can't actually modify any of the outputs,
+ // so the stage that comes before it is the last one that can do modifications.
+ if (config.Stage != ShaderStage.Fragment && (config.Stage != ShaderStage.Geometry || !config.GpPassthrough))
+ {
+ LastInVertexPipeline = false;
+ }
+ }
+
+ public void MergeOutputUserAttributes(int mask, IEnumerable<int> perPatch)
+ {
+ _nextUsedInputAttributes = mask;
+
+ if (GpPassthrough)
+ {
+ PassthroughAttributes = mask & ~UsedOutputAttributes;
+ }
+ else
+ {
+ UsedOutputAttributes |= mask;
+ UsedOutputAttributesPerPatch.UnionWith(perPatch);
+ }
+ }
+
+ public int GetPerPatchAttributeLocation(int index)
+ {
+ if (_perPatchAttributeLocations == null || !_perPatchAttributeLocations.TryGetValue(index, out int location))
+ {
+ return index;
+ }
+
+ return location;
+ }
+
+ public bool IsUsedOutputAttribute(int attr)
+ {
+ // The check for fixed function attributes on the next stage is conservative,
+ // returning false if the output is just not used by the next stage is also valid.
+ if (NextUsesFixedFuncAttributes &&
+ attr >= AttributeConsts.UserAttributeBase &&
+ attr < AttributeConsts.UserAttributeEnd)
+ {
+ int index = (attr - AttributeConsts.UserAttributeBase) >> 4;
+ return (_nextUsedInputAttributes & (1 << index)) != 0;
+ }
+
+ return true;
+ }
+
+ public int GetFreeUserAttribute(bool isOutput, int index)
+ {
+ int useMask = isOutput ? _nextUsedInputAttributes : _thisUsedInputAttributes;
+ int bit = -1;
+
+ while (useMask != -1)
+ {
+ bit = BitOperations.TrailingZeroCount(~useMask);
+
+ if (bit == 32)
+ {
+ bit = -1;
+ break;
+ }
+ else if (index < 1)
+ {
+ break;
+ }
+
+ useMask |= 1 << bit;
+ index--;
+ }
+
+ return bit;
+ }
+
+ public void SetAllInputUserAttributes()
+ {
+ UsedInputAttributes |= Constants.AllAttributesMask;
+ ThisInputAttributesComponents |= ~UInt128.Zero >> (128 - Constants.MaxAttributes * 4);
+ }
+
+ public void SetAllOutputUserAttributes()
+ {
+ UsedOutputAttributes |= Constants.AllAttributesMask;
+ }
+
+ public void SetClipDistanceWritten(int index)
+ {
+ ClipDistancesWritten |= (byte)(1 << index);
+ }
+
+ public void SetUsedFeature(FeatureFlags flags)
+ {
+ UsedFeatures |= flags;
+ }
+
+ public void SetAccessibleBufferMasks(int sbMask, int ubeMask)
+ {
+ AccessibleStorageBuffersMask = sbMask;
+ AccessibleConstantBuffersMask = ubeMask;
+ }
+
+ public void SetUsedConstantBuffer(int slot)
+ {
+ _usedConstantBuffers |= 1 << slot;
+ }
+
+ public void SetUsedStorageBuffer(int slot, bool write)
+ {
+ int mask = 1 << slot;
+ _usedStorageBuffers |= mask;
+
+ if (write)
+ {
+ _usedStorageBuffersWrite |= mask;
+ }
+ }
+
+ 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 BufferDescriptor[] GetConstantBufferDescriptors()
+ {
+ if (_cachedConstantBufferDescriptors != null)
+ {
+ return _cachedConstantBufferDescriptors;
+ }
+
+ int usedMask = _usedConstantBuffers;
+
+ if (UsedFeatures.HasFlag(FeatureFlags.CbIndexing))
+ {
+ usedMask |= (int)GpuAccessor.QueryConstantBufferUse();
+ }
+
+ return _cachedConstantBufferDescriptors = GetBufferDescriptors(
+ usedMask,
+ 0,
+ UsedFeatures.HasFlag(FeatureFlags.CbIndexing),
+ out _firstConstantBufferBinding,
+ GpuAccessor.QueryBindingConstantBuffer);
+ }
+
+ public BufferDescriptor[] GetStorageBufferDescriptors()
+ {
+ if (_cachedStorageBufferDescriptors != null)
+ {
+ return _cachedStorageBufferDescriptors;
+ }
+
+ return _cachedStorageBufferDescriptors = GetBufferDescriptors(
+ _usedStorageBuffers,
+ _usedStorageBuffersWrite,
+ true,
+ out _firstStorageBufferBinding,
+ GpuAccessor.QueryBindingStorageBuffer);
+ }
+
+ private static BufferDescriptor[] GetBufferDescriptors(
+ int usedMask,
+ int writtenMask,
+ bool isArray,
+ out int firstBinding,
+ Func<int, int> getBindingCallback)
+ {
+ firstBinding = 0;
+ bool hasFirstBinding = false;
+ var descriptors = new BufferDescriptor[BitOperations.PopCount((uint)usedMask)];
+
+ int lastSlot = -1;
+
+ for (int i = 0; i < descriptors.Length; i++)
+ {
+ int slot = BitOperations.TrailingZeroCount(usedMask);
+
+ if (isArray)
+ {
+ // The next array entries also consumes bindings, even if they are unused.
+ for (int j = lastSlot + 1; j < slot; j++)
+ {
+ int binding = getBindingCallback(j);
+
+ if (!hasFirstBinding)
+ {
+ firstBinding = binding;
+ hasFirstBinding = true;
+ }
+ }
+ }
+
+ lastSlot = slot;
+
+ descriptors[i] = new BufferDescriptor(getBindingCallback(slot), slot);
+
+ if (!hasFirstBinding)
+ {
+ firstBinding = descriptors[i].Binding;
+ hasFirstBinding = true;
+ }
+
+ if ((writtenMask & (1 << slot)) != 0)
+ {
+ descriptors[i].SetFlag(BufferUsageFlags.Write);
+ }
+
+ usedMask &= ~(1 << slot);
+ }
+
+ return descriptors;
+ }
+
+ 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).OrderBy(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, int) 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, i);
+ }
+ }
+
+ return (default, -1);
+ }
+
+ private static int FindDescriptorIndex(TextureDescriptor[] array, AstTextureOperation texOp)
+ {
+ for (int i = 0; i < array.Length; i++)
+ {
+ var descriptor = array[i];
+
+ if (descriptor.Type == texOp.Type &&
+ descriptor.CbufSlot == texOp.CbufSlot &&
+ descriptor.HandleIndex == texOp.Handle &&
+ descriptor.Format == texOp.Format)
+ {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ public int FindTextureDescriptorIndex(AstTextureOperation texOp)
+ {
+ return FindDescriptorIndex(GetTextureDescriptors(), texOp);
+ }
+
+ public int FindImageDescriptorIndex(AstTextureOperation texOp)
+ {
+ return FindDescriptorIndex(GetImageDescriptors(), texOp);
+ }
+
+ public ShaderProgramInfo CreateProgramInfo(ShaderIdentification identification = ShaderIdentification.None)
+ {
+ return new ShaderProgramInfo(
+ GetConstantBufferDescriptors(),
+ GetStorageBufferDescriptors(),
+ GetTextureDescriptors(),
+ GetImageDescriptors(),
+ identification,
+ GpLayerInputAttribute,
+ Stage,
+ UsedFeatures.HasFlag(FeatureFlags.InstanceId),
+ UsedFeatures.HasFlag(FeatureFlags.DrawParameters),
+ UsedFeatures.HasFlag(FeatureFlags.RtLayer),
+ ClipDistancesWritten,
+ OmapTargets);
+ }
+ }
+} \ No newline at end of file