aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Graphics.Gpu
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.Gpu
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.Gpu')
-rw-r--r--src/Ryujinx.Graphics.Gpu/Constants.cs5
-rw-r--r--src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs2
-rw-r--r--src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs3
-rw-r--r--src/Ryujinx.Graphics.Gpu/Image/Pool.cs32
-rw-r--r--src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs12
-rw-r--r--src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs714
-rw-r--r--src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs54
-rw-r--r--src/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs23
-rw-r--r--src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs106
-rw-r--r--src/Ryujinx.Graphics.Gpu/Memory/BufferTextureArrayBinding.cs66
-rw-r--r--src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs17
-rw-r--r--src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs29
-rw-r--r--src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs2
-rw-r--r--src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadResult.cs6
-rw-r--r--src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs29
-rw-r--r--src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs77
-rw-r--r--src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs35
-rw-r--r--src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs75
18 files changed, 1207 insertions, 80 deletions
diff --git a/src/Ryujinx.Graphics.Gpu/Constants.cs b/src/Ryujinx.Graphics.Gpu/Constants.cs
index c553d988..23b9be5c 100644
--- a/src/Ryujinx.Graphics.Gpu/Constants.cs
+++ b/src/Ryujinx.Graphics.Gpu/Constants.cs
@@ -89,5 +89,10 @@ namespace Ryujinx.Graphics.Gpu
/// Maximum size that an storage buffer is assumed to have when the correct size is unknown.
/// </summary>
public const ulong MaxUnknownStorageSize = 0x100000;
+
+ /// <summary>
+ /// Size of a bindless texture handle as exposed by guest graphics APIs.
+ /// </summary>
+ public const int TextureHandleSizeInBytes = sizeof(ulong);
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs b/src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs
index 492c6ee6..7bff1c4b 100644
--- a/src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs
+++ b/src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs
@@ -16,7 +16,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
/// <returns>Texture target value</returns>
public static Target GetTarget(SamplerType type)
{
- type &= ~(SamplerType.Indexed | SamplerType.Shadow);
+ type &= ~SamplerType.Shadow;
switch (type)
{
diff --git a/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs b/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs
index 732ec5d4..5e66a3b5 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs
@@ -107,8 +107,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (texture.CacheNode != _textures.Last)
{
_textures.Remove(texture.CacheNode);
-
- texture.CacheNode = _textures.AddLast(texture);
+ _textures.AddLast(texture.CacheNode);
}
if (_totalSize > MaxTextureSizeCapacity && _textures.Count >= MinCountForDeletion)
diff --git a/src/Ryujinx.Graphics.Gpu/Image/Pool.cs b/src/Ryujinx.Graphics.Gpu/Image/Pool.cs
index 6ede0197..e12fedc7 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/Pool.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/Pool.cs
@@ -112,6 +112,21 @@ namespace Ryujinx.Graphics.Gpu.Image
public abstract T1 Get(int id);
/// <summary>
+ /// Gets the cached item with the given ID, or null if there is no cached item for the specified ID.
+ /// </summary>
+ /// <param name="id">ID of the item. This is effectively a zero-based index</param>
+ /// <returns>The cached item with the given ID</returns>
+ public T1 GetCachedItem(int id)
+ {
+ if (!IsValidId(id))
+ {
+ return default;
+ }
+
+ return Items[id];
+ }
+
+ /// <summary>
/// Checks if a given ID is valid and inside the range of the pool.
/// </summary>
/// <param name="id">ID of the descriptor. This is effectively a zero-based index</param>
@@ -197,6 +212,23 @@ namespace Ryujinx.Graphics.Gpu.Image
return false;
}
+ /// <summary>
+ /// Checks if the pool was modified by comparing the current <seealso cref="ModifiedSequenceNumber"/> with a cached one.
+ /// </summary>
+ /// <param name="sequenceNumber">Cached modified sequence number</param>
+ /// <returns>True if the pool was modified, false otherwise</returns>
+ public bool WasModified(ref int sequenceNumber)
+ {
+ if (sequenceNumber != ModifiedSequenceNumber)
+ {
+ sequenceNumber = ModifiedSequenceNumber;
+
+ return true;
+ }
+
+ return false;
+ }
+
protected abstract void InvalidateRangeImpl(ulong address, ulong size);
protected abstract void Delete(T1 item);
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs
index 606842d6..12a457db 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs
@@ -25,6 +25,11 @@ namespace Ryujinx.Graphics.Gpu.Image
public int Binding { get; }
/// <summary>
+ /// For array of textures, this indicates the length of the array. A value of one indicates it is not an array.
+ /// </summary>
+ public int ArrayLength { get; }
+
+ /// <summary>
/// Constant buffer slot with the texture handle.
/// </summary>
public int CbufSlot { get; }
@@ -45,14 +50,16 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="target">The shader sampler target type</param>
/// <param name="format">Format of the image as declared on the shader</param>
/// <param name="binding">The shader texture binding point</param>
+ /// <param name="arrayLength">For array of textures, this indicates the length of the array. A value of one indicates it is not an array</param>
/// <param name="cbufSlot">Constant buffer slot where the texture handle is located</param>
/// <param name="handle">The shader texture handle (read index into the texture constant buffer)</param>
/// <param name="flags">The texture's usage flags, indicating how it is used in the shader</param>
- public TextureBindingInfo(Target target, Format format, int binding, int cbufSlot, int handle, TextureUsageFlags flags)
+ public TextureBindingInfo(Target target, Format format, int binding, int arrayLength, int cbufSlot, int handle, TextureUsageFlags flags)
{
Target = target;
Format = format;
Binding = binding;
+ ArrayLength = arrayLength;
CbufSlot = cbufSlot;
Handle = handle;
Flags = flags;
@@ -63,10 +70,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
/// <param name="target">The shader sampler target type</param>
/// <param name="binding">The shader texture binding point</param>
+ /// <param name="arrayLength">For array of textures, this indicates the length of the array. A value of one indicates it is not an array</param>
/// <param name="cbufSlot">Constant buffer slot where the texture handle is located</param>
/// <param name="handle">The shader texture handle (read index into the texture constant buffer)</param>
/// <param name="flags">The texture's usage flags, indicating how it is used in the shader</param>
- public TextureBindingInfo(Target target, int binding, int cbufSlot, int handle, TextureUsageFlags flags) : this(target, (Format)0, binding, cbufSlot, handle, flags)
+ public TextureBindingInfo(Target target, int binding, int arrayLength, int cbufSlot, int handle, TextureUsageFlags flags) : this(target, (Format)0, binding, arrayLength, cbufSlot, handle, flags)
{
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs
new file mode 100644
index 00000000..70ea1f6b
--- /dev/null
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs
@@ -0,0 +1,714 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Engine.Types;
+using Ryujinx.Graphics.Gpu.Memory;
+using Ryujinx.Graphics.Shader;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ /// <summary>
+ /// Texture bindings array cache.
+ /// </summary>
+ class TextureBindingsArrayCache
+ {
+ /// <summary>
+ /// Minimum timestamp delta until texture array can be removed from the cache.
+ /// </summary>
+ private const int MinDeltaForRemoval = 20000;
+
+ private readonly GpuContext _context;
+ private readonly GpuChannel _channel;
+ private readonly bool _isCompute;
+
+ /// <summary>
+ /// Array cache entry key.
+ /// </summary>
+ private readonly struct CacheEntryKey : IEquatable<CacheEntryKey>
+ {
+ /// <summary>
+ /// Whether the entry is for an image.
+ /// </summary>
+ public readonly bool IsImage;
+
+ /// <summary>
+ /// Texture or image target type.
+ /// </summary>
+ public readonly Target Target;
+
+ /// <summary>
+ /// Word offset of the first handle on the constant buffer.
+ /// </summary>
+ public readonly int HandleIndex;
+
+ /// <summary>
+ /// Number of entries of the array.
+ /// </summary>
+ public readonly int ArrayLength;
+
+ private readonly TexturePool _texturePool;
+ private readonly SamplerPool _samplerPool;
+
+ private readonly BufferBounds _textureBufferBounds;
+
+ /// <summary>
+ /// Creates a new array cache entry.
+ /// </summary>
+ /// <param name="isImage">Whether the entry is for an image</param>
+ /// <param name="bindingInfo">Binding information for the array</param>
+ /// <param name="texturePool">Texture pool where the array textures are located</param>
+ /// <param name="samplerPool">Sampler pool where the array samplers are located</param>
+ /// <param name="textureBufferBounds">Constant buffer bounds with the texture handles</param>
+ public CacheEntryKey(
+ bool isImage,
+ TextureBindingInfo bindingInfo,
+ TexturePool texturePool,
+ SamplerPool samplerPool,
+ ref BufferBounds textureBufferBounds)
+ {
+ IsImage = isImage;
+ Target = bindingInfo.Target;
+ HandleIndex = bindingInfo.Handle;
+ ArrayLength = bindingInfo.ArrayLength;
+
+ _texturePool = texturePool;
+ _samplerPool = samplerPool;
+
+ _textureBufferBounds = textureBufferBounds;
+ }
+
+ /// <summary>
+ /// Checks if the texture and sampler pools matches the cached pools.
+ /// </summary>
+ /// <param name="texturePool">Texture pool instance</param>
+ /// <param name="samplerPool">Sampler pool instance</param>
+ /// <returns>True if the pools match, false otherwise</returns>
+ private bool MatchesPools(TexturePool texturePool, SamplerPool samplerPool)
+ {
+ return _texturePool == texturePool && _samplerPool == samplerPool;
+ }
+
+ /// <summary>
+ /// Checks if the cached constant buffer address and size matches.
+ /// </summary>
+ /// <param name="textureBufferBounds">New buffer address and size</param>
+ /// <returns>True if the address and size matches, false otherwise</returns>
+ private bool MatchesBufferBounds(BufferBounds textureBufferBounds)
+ {
+ return _textureBufferBounds.Equals(textureBufferBounds);
+ }
+
+ public bool Equals(CacheEntryKey other)
+ {
+ return IsImage == other.IsImage &&
+ Target == other.Target &&
+ HandleIndex == other.HandleIndex &&
+ ArrayLength == other.ArrayLength &&
+ MatchesPools(other._texturePool, other._samplerPool) &&
+ MatchesBufferBounds(other._textureBufferBounds);
+ }
+
+ public override bool Equals(object obj)
+ {
+ return obj is CacheEntryKey other && Equals(other);
+ }
+
+ public override int GetHashCode()
+ {
+ return _textureBufferBounds.Range.GetHashCode();
+ }
+ }
+
+ /// <summary>
+ /// Array cache entry.
+ /// </summary>
+ private class CacheEntry
+ {
+ /// <summary>
+ /// Key for this entry on the cache.
+ /// </summary>
+ public readonly CacheEntryKey Key;
+
+ /// <summary>
+ /// Linked list node used on the texture bindings array cache.
+ /// </summary>
+ public LinkedListNode<CacheEntry> CacheNode;
+
+ /// <summary>
+ /// Timestamp set on the last use of the array by the cache.
+ /// </summary>
+ public int CacheTimestamp;
+
+ /// <summary>
+ /// All cached textures, along with their invalidated sequence number as value.
+ /// </summary>
+ public readonly Dictionary<Texture, int> Textures;
+
+ /// <summary>
+ /// All pool texture IDs along with their textures.
+ /// </summary>
+ public readonly Dictionary<int, Texture> TextureIds;
+
+ /// <summary>
+ /// All pool sampler IDs along with their samplers.
+ /// </summary>
+ public readonly Dictionary<int, Sampler> SamplerIds;
+
+ /// <summary>
+ /// Backend texture array if the entry is for a texture, otherwise null.
+ /// </summary>
+ public readonly ITextureArray TextureArray;
+
+ /// <summary>
+ /// Backend image array if the entry is for an image, otherwise null.
+ /// </summary>
+ public readonly IImageArray ImageArray;
+
+ private readonly TexturePool _texturePool;
+ private readonly SamplerPool _samplerPool;
+
+ private int _texturePoolSequence;
+ private int _samplerPoolSequence;
+
+ private int[] _cachedTextureBuffer;
+ private int[] _cachedSamplerBuffer;
+
+ private int _lastSequenceNumber;
+
+ /// <summary>
+ /// Creates a new array cache entry.
+ /// </summary>
+ /// <param name="key">Key for this entry on the cache</param>
+ /// <param name="texturePool">Texture pool where the array textures are located</param>
+ /// <param name="samplerPool">Sampler pool where the array samplers are located</param>
+ private CacheEntry(ref CacheEntryKey key, TexturePool texturePool, SamplerPool samplerPool)
+ {
+ Key = key;
+ Textures = new Dictionary<Texture, int>();
+ TextureIds = new Dictionary<int, Texture>();
+ SamplerIds = new Dictionary<int, Sampler>();
+
+ _texturePool = texturePool;
+ _samplerPool = samplerPool;
+
+ _lastSequenceNumber = -1;
+ }
+
+ /// <summary>
+ /// Creates a new array cache entry.
+ /// </summary>
+ /// <param name="key">Key for this entry on the cache</param>
+ /// <param name="array">Backend texture array</param>
+ /// <param name="texturePool">Texture pool where the array textures are located</param>
+ /// <param name="samplerPool">Sampler pool where the array samplers are located</param>
+ public CacheEntry(ref CacheEntryKey key, ITextureArray array, TexturePool texturePool, SamplerPool samplerPool) : this(ref key, texturePool, samplerPool)
+ {
+ TextureArray = array;
+ }
+
+ /// <summary>
+ /// Creates a new array cache entry.
+ /// </summary>
+ /// <param name="key">Key for this entry on the cache</param>
+ /// <param name="array">Backend image array</param>
+ /// <param name="texturePool">Texture pool where the array textures are located</param>
+ /// <param name="samplerPool">Sampler pool where the array samplers are located</param>
+ public CacheEntry(ref CacheEntryKey key, IImageArray array, TexturePool texturePool, SamplerPool samplerPool) : this(ref key, texturePool, samplerPool)
+ {
+ ImageArray = array;
+ }
+
+ /// <summary>
+ /// Synchronizes memory for all textures in the array.
+ /// </summary>
+ /// <param name="isStore">Indicates if the texture may be modified by the access</param>
+ public void SynchronizeMemory(bool isStore)
+ {
+ foreach (Texture texture in Textures.Keys)
+ {
+ texture.SynchronizeMemory();
+
+ if (isStore)
+ {
+ texture.SignalModified();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Clears all cached texture instances.
+ /// </summary>
+ public void Reset()
+ {
+ Textures.Clear();
+ TextureIds.Clear();
+ SamplerIds.Clear();
+ }
+
+ /// <summary>
+ /// Updates the cached constant buffer data.
+ /// </summary>
+ /// <param name="cachedTextureBuffer">Constant buffer data with the texture handles (and sampler handles, if they are combined)</param>
+ /// <param name="cachedSamplerBuffer">Constant buffer data with the sampler handles</param>
+ /// <param name="separateSamplerBuffer">Whether <paramref name="cachedTextureBuffer"/> and <paramref name="cachedSamplerBuffer"/> comes from different buffers</param>
+ public void UpdateData(ReadOnlySpan<int> cachedTextureBuffer, ReadOnlySpan<int> cachedSamplerBuffer, bool separateSamplerBuffer)
+ {
+ _cachedTextureBuffer = cachedTextureBuffer.ToArray();
+ _cachedSamplerBuffer = separateSamplerBuffer ? cachedSamplerBuffer.ToArray() : _cachedTextureBuffer;
+ }
+
+ /// <summary>
+ /// Checks if any texture has been deleted since the last call to this method.
+ /// </summary>
+ /// <returns>True if one or more textures have been deleted, false otherwise</returns>
+ public bool ValidateTextures()
+ {
+ foreach ((Texture texture, int invalidatedSequence) in Textures)
+ {
+ if (texture.InvalidatedSequence != invalidatedSequence)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Checks if the cached texture or sampler pool has been modified since the last call to this method.
+ /// </summary>
+ /// <returns>True if any used entries of the pools might have been modified, false otherwise</returns>
+ public bool PoolsModified()
+ {
+ bool texturePoolModified = _texturePool.WasModified(ref _texturePoolSequence);
+ bool samplerPoolModified = _samplerPool.WasModified(ref _samplerPoolSequence);
+
+ // If both pools were not modified since the last check, we have nothing else to check.
+ if (!texturePoolModified && !samplerPoolModified)
+ {
+ return false;
+ }
+
+ // If the pools were modified, let's check if any of the entries we care about changed.
+
+ // Check if any of our cached textures changed on the pool.
+ foreach ((int textureId, Texture texture) in TextureIds)
+ {
+ if (_texturePool.GetCachedItem(textureId) != texture)
+ {
+ return true;
+ }
+ }
+
+ // Check if any of our cached samplers changed on the pool.
+ foreach ((int samplerId, Sampler sampler) in SamplerIds)
+ {
+ if (_samplerPool.GetCachedItem(samplerId) != sampler)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Checks if the sequence number matches the one used on the last call to this method.
+ /// </summary>
+ /// <param name="currentSequenceNumber">Current sequence number</param>
+ /// <returns>True if the sequence numbers match, false otherwise</returns>
+ public bool MatchesSequenceNumber(int currentSequenceNumber)
+ {
+ if (_lastSequenceNumber == currentSequenceNumber)
+ {
+ return true;
+ }
+
+ _lastSequenceNumber = currentSequenceNumber;
+
+ return false;
+ }
+
+ /// <summary>
+ /// Checks if the buffer data matches the cached data.
+ /// </summary>
+ /// <param name="cachedTextureBuffer">New texture buffer data</param>
+ /// <param name="cachedSamplerBuffer">New sampler buffer data</param>
+ /// <param name="separateSamplerBuffer">Whether <paramref name="cachedTextureBuffer"/> and <paramref name="cachedSamplerBuffer"/> comes from different buffers</param>
+ /// <param name="samplerWordOffset">Word offset of the sampler constant buffer handle that is used</param>
+ /// <returns>True if the data matches, false otherwise</returns>
+ public bool MatchesBufferData(
+ ReadOnlySpan<int> cachedTextureBuffer,
+ ReadOnlySpan<int> cachedSamplerBuffer,
+ bool separateSamplerBuffer,
+ int samplerWordOffset)
+ {
+ if (_cachedTextureBuffer != null && cachedTextureBuffer.Length > _cachedTextureBuffer.Length)
+ {
+ cachedTextureBuffer = cachedTextureBuffer[.._cachedTextureBuffer.Length];
+ }
+
+ if (!_cachedTextureBuffer.AsSpan().SequenceEqual(cachedTextureBuffer))
+ {
+ return false;
+ }
+
+ if (separateSamplerBuffer)
+ {
+ if (_cachedSamplerBuffer == null ||
+ _cachedSamplerBuffer.Length <= samplerWordOffset ||
+ cachedSamplerBuffer.Length <= samplerWordOffset)
+ {
+ return false;
+ }
+
+ int oldValue = _cachedSamplerBuffer[samplerWordOffset];
+ int newValue = cachedSamplerBuffer[samplerWordOffset];
+
+ return oldValue == newValue;
+ }
+
+ return true;
+ }
+ }
+
+ private readonly Dictionary<CacheEntryKey, CacheEntry> _cache;
+ private readonly LinkedList<CacheEntry> _lruCache;
+
+ private int _currentTimestamp;
+
+ /// <summary>
+ /// Creates a new instance of the texture bindings array cache.
+ /// </summary>
+ /// <param name="context">GPU context</param>
+ /// <param name="channel">GPU channel</param>
+ /// <param name="isCompute">Whether the bindings will be used for compute or graphics pipelines</param>
+ public TextureBindingsArrayCache(GpuContext context, GpuChannel channel, bool isCompute)
+ {
+ _context = context;
+ _channel = channel;
+ _isCompute = isCompute;
+ _cache = new Dictionary<CacheEntryKey, CacheEntry>();
+ _lruCache = new LinkedList<CacheEntry>();
+ }
+
+ /// <summary>
+ /// Updates a texture array bindings and textures.
+ /// </summary>
+ /// <param name="texturePool">Texture pool</param>
+ /// <param name="samplerPool">Sampler pool</param>
+ /// <param name="stage">Shader stage where the array is used</param>
+ /// <param name="stageIndex">Shader stage index where the array is used</param>
+ /// <param name="textureBufferIndex">Texture constant buffer index</param>
+ /// <param name="samplerIndex">Sampler handles source</param>
+ /// <param name="bindingInfo">Array binding information</param>
+ public void UpdateTextureArray(
+ TexturePool texturePool,
+ SamplerPool samplerPool,
+ ShaderStage stage,
+ int stageIndex,
+ int textureBufferIndex,
+ SamplerIndex samplerIndex,
+ TextureBindingInfo bindingInfo)
+ {
+ Update(texturePool, samplerPool, stage, stageIndex, textureBufferIndex, isImage: false, samplerIndex, bindingInfo);
+ }
+
+ /// <summary>
+ /// Updates a image array bindings and textures.
+ /// </summary>
+ /// <param name="texturePool">Texture pool</param>
+ /// <param name="stage">Shader stage where the array is used</param>
+ /// <param name="stageIndex">Shader stage index where the array is used</param>
+ /// <param name="textureBufferIndex">Texture constant buffer index</param>
+ /// <param name="bindingInfo">Array binding information</param>
+ public void UpdateImageArray(TexturePool texturePool, ShaderStage stage, int stageIndex, int textureBufferIndex, TextureBindingInfo bindingInfo)
+ {
+ Update(texturePool, null, stage, stageIndex, textureBufferIndex, isImage: true, SamplerIndex.ViaHeaderIndex, bindingInfo);
+ }
+
+ /// <summary>
+ /// Updates a texture or image array bindings and textures.
+ /// </summary>
+ /// <param name="texturePool">Texture pool</param>
+ /// <param name="samplerPool">Sampler pool</param>
+ /// <param name="stage">Shader stage where the array is used</param>
+ /// <param name="stageIndex">Shader stage index where the array is used</param>
+ /// <param name="textureBufferIndex">Texture constant buffer index</param>
+ /// <param name="isImage">Whether the array is a image or texture array</param>
+ /// <param name="samplerIndex">Sampler handles source</param>
+ /// <param name="bindingInfo">Array binding information</param>
+ private void Update(
+ TexturePool texturePool,
+ SamplerPool samplerPool,
+ ShaderStage stage,
+ int stageIndex,
+ int textureBufferIndex,
+ bool isImage,
+ SamplerIndex samplerIndex,
+ TextureBindingInfo bindingInfo)
+ {
+ (textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, textureBufferIndex);
+
+ bool separateSamplerBuffer = textureBufferIndex != samplerBufferIndex;
+
+ ref BufferBounds textureBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, textureBufferIndex);
+ ref BufferBounds samplerBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, samplerBufferIndex);
+
+ CacheEntry entry = GetOrAddEntry(
+ texturePool,
+ samplerPool,
+ bindingInfo,
+ isImage,
+ ref textureBufferBounds,
+ out bool isNewEntry);
+
+ bool poolsModified = entry.PoolsModified();
+ bool isStore = bindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
+
+ ReadOnlySpan<int> cachedTextureBuffer;
+ ReadOnlySpan<int> cachedSamplerBuffer;
+
+ if (!poolsModified && !isNewEntry && entry.ValidateTextures())
+ {
+ if (entry.MatchesSequenceNumber(_context.SequenceNumber))
+ {
+ entry.SynchronizeMemory(isStore);
+
+ if (isImage)
+ {
+ _context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray);
+ }
+ else
+ {
+ _context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray);
+ }
+
+ return;
+ }
+
+ cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(textureBufferBounds.Range));
+
+ if (separateSamplerBuffer)
+ {
+ cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(samplerBufferBounds.Range));
+ }
+ else
+ {
+ cachedSamplerBuffer = cachedTextureBuffer;
+ }
+
+ (_, int samplerWordOffset, _) = TextureHandle.UnpackOffsets(bindingInfo.Handle);
+
+ if (entry.MatchesBufferData(cachedTextureBuffer, cachedSamplerBuffer, separateSamplerBuffer, samplerWordOffset))
+ {
+ entry.SynchronizeMemory(isStore);
+
+ if (isImage)
+ {
+ _context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray);
+ }
+ else
+ {
+ _context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray);
+ }
+
+ return;
+ }
+ }
+ else
+ {
+ cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(textureBufferBounds.Range));
+
+ if (separateSamplerBuffer)
+ {
+ cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(samplerBufferBounds.Range));
+ }
+ else
+ {
+ cachedSamplerBuffer = cachedTextureBuffer;
+ }
+ }
+
+ if (!isNewEntry)
+ {
+ entry.Reset();
+ }
+
+ entry.UpdateData(cachedTextureBuffer, cachedSamplerBuffer, separateSamplerBuffer);
+
+ Format[] formats = isImage ? new Format[bindingInfo.ArrayLength] : null;
+ ISampler[] samplers = isImage ? null : new ISampler[bindingInfo.ArrayLength];
+ ITexture[] textures = new ITexture[bindingInfo.ArrayLength];
+
+ for (int index = 0; index < bindingInfo.ArrayLength; index++)
+ {
+ int handleIndex = bindingInfo.Handle + index * (Constants.TextureHandleSizeInBytes / sizeof(int));
+ int packedId = TextureHandle.ReadPackedId(handleIndex, cachedTextureBuffer, cachedSamplerBuffer);
+ int textureId = TextureHandle.UnpackTextureId(packedId);
+ int samplerId;
+
+ if (samplerIndex == SamplerIndex.ViaHeaderIndex)
+ {
+ samplerId = textureId;
+ }
+ else
+ {
+ samplerId = TextureHandle.UnpackSamplerId(packedId);
+ }
+
+ ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(textureId, out Texture texture);
+
+ if (texture != null)
+ {
+ entry.Textures[texture] = texture.InvalidatedSequence;
+
+ if (isStore)
+ {
+ texture.SignalModified();
+ }
+ }
+
+ Sampler sampler = samplerPool?.Get(samplerId);
+
+ entry.TextureIds[textureId] = texture;
+ entry.SamplerIds[samplerId] = sampler;
+
+ ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
+ ISampler hostSampler = sampler?.GetHostSampler(texture);
+
+ Format format = bindingInfo.Format;
+
+ if (hostTexture != null && texture.Target == Target.TextureBuffer)
+ {
+ // Ensure that the buffer texture is using the correct buffer as storage.
+ // Buffers are frequently re-created to accommodate larger data, so we need to re-bind
+ // to ensure we're not using a old buffer that was already deleted.
+ if (isImage)
+ {
+ if (format == 0 && texture != null)
+ {
+ format = texture.Format;
+ }
+
+ _channel.BufferManager.SetBufferTextureStorage(entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format);
+ }
+ else
+ {
+ _channel.BufferManager.SetBufferTextureStorage(entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format);
+ }
+ }
+ else if (isImage)
+ {
+ if (format == 0 && texture != null)
+ {
+ format = texture.Format;
+ }
+
+ formats[index] = format;
+ textures[index] = hostTexture;
+ }
+ else
+ {
+ samplers[index] = hostSampler;
+ textures[index] = hostTexture;
+ }
+ }
+
+ if (isImage)
+ {
+ entry.ImageArray.SetFormats(0, formats);
+ entry.ImageArray.SetImages(0, textures);
+
+ _context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray);
+ }
+ else
+ {
+ entry.TextureArray.SetSamplers(0, samplers);
+ entry.TextureArray.SetTextures(0, textures);
+
+ _context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray);
+ }
+ }
+
+ /// <summary>
+ /// Gets a cached texture entry, or creates a new one if not found.
+ /// </summary>
+ /// <param name="texturePool">Texture pool</param>
+ /// <param name="samplerPool">Sampler pool</param>
+ /// <param name="bindingInfo">Array binding information</param>
+ /// <param name="isImage">Whether the array is a image or texture array</param>
+ /// <param name="textureBufferBounds">Constant buffer bounds with the texture handles</param>
+ /// <param name="isNew">Whether a new entry was created, or an existing one was returned</param>
+ /// <returns>Cache entry</returns>
+ private CacheEntry GetOrAddEntry(
+ TexturePool texturePool,
+ SamplerPool samplerPool,
+ TextureBindingInfo bindingInfo,
+ bool isImage,
+ ref BufferBounds textureBufferBounds,
+ out bool isNew)
+ {
+ CacheEntryKey key = new CacheEntryKey(
+ isImage,
+ bindingInfo,
+ texturePool,
+ samplerPool,
+ ref textureBufferBounds);
+
+ isNew = !_cache.TryGetValue(key, out CacheEntry entry);
+
+ if (isNew)
+ {
+ int arrayLength = bindingInfo.ArrayLength;
+
+ if (isImage)
+ {
+ IImageArray array = _context.Renderer.CreateImageArray(arrayLength, bindingInfo.Target == Target.TextureBuffer);
+
+ _cache.Add(key, entry = new CacheEntry(ref key, array, texturePool, samplerPool));
+ }
+ else
+ {
+ ITextureArray array = _context.Renderer.CreateTextureArray(arrayLength, bindingInfo.Target == Target.TextureBuffer);
+
+ _cache.Add(key, entry = new CacheEntry(ref key, array, texturePool, samplerPool));
+ }
+ }
+
+ if (entry.CacheNode != null)
+ {
+ _lruCache.Remove(entry.CacheNode);
+ _lruCache.AddLast(entry.CacheNode);
+ }
+ else
+ {
+ entry.CacheNode = _lruCache.AddLast(entry);
+ }
+
+ entry.CacheTimestamp = ++_currentTimestamp;
+
+ RemoveLeastUsedEntries();
+
+ return entry;
+ }
+
+ /// <summary>
+ /// Remove entries from the cache that have not been used for some time.
+ /// </summary>
+ private void RemoveLeastUsedEntries()
+ {
+ LinkedListNode<CacheEntry> nextNode = _lruCache.First;
+
+ while (nextNode != null && _currentTimestamp - nextNode.Value.CacheTimestamp >= MinDeltaForRemoval)
+ {
+ LinkedListNode<CacheEntry> toRemove = nextNode;
+ nextNode = nextNode.Next;
+ _cache.Remove(toRemove.Value.Key);
+ _lruCache.Remove(toRemove);
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
index ef5d0dea..3c10c95e 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
@@ -34,6 +34,8 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly TexturePoolCache _texturePoolCache;
private readonly SamplerPoolCache _samplerPoolCache;
+ private readonly TextureBindingsArrayCache _arrayBindingsCache;
+
private TexturePool _cachedTexturePool;
private SamplerPool _cachedSamplerPool;
@@ -56,6 +58,8 @@ namespace Ryujinx.Graphics.Gpu.Image
private TextureState[] _textureState;
private TextureState[] _imageState;
+ private int[] _textureCounts;
+
private int _texturePoolSequence;
private int _samplerPoolSequence;
@@ -85,6 +89,8 @@ namespace Ryujinx.Graphics.Gpu.Image
_isCompute = isCompute;
+ _arrayBindingsCache = new TextureBindingsArrayCache(context, channel, isCompute);
+
int stages = isCompute ? 1 : Constants.ShaderStages;
_textureBindings = new TextureBindingInfo[stages][];
@@ -95,9 +101,11 @@ namespace Ryujinx.Graphics.Gpu.Image
for (int stage = 0; stage < stages; stage++)
{
- _textureBindings[stage] = new TextureBindingInfo[InitialTextureStateSize];
- _imageBindings[stage] = new TextureBindingInfo[InitialImageStateSize];
+ _textureBindings[stage] = Array.Empty<TextureBindingInfo>();
+ _imageBindings[stage] = Array.Empty<TextureBindingInfo>();
}
+
+ _textureCounts = Array.Empty<int>();
}
/// <summary>
@@ -109,6 +117,8 @@ namespace Ryujinx.Graphics.Gpu.Image
_textureBindings = bindings.TextureBindings;
_imageBindings = bindings.ImageBindings;
+ _textureCounts = bindings.TextureCounts;
+
SetMaxBindings(bindings.MaxTextureBinding, bindings.MaxImageBinding);
}
@@ -401,27 +411,6 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
-#pragma warning disable IDE0051 // Remove unused private member
- /// <summary>
- /// Counts the total number of texture bindings used by all shader stages.
- /// </summary>
- /// <returns>The total amount of textures used</returns>
- private int GetTextureBindingsCount()
- {
- int count = 0;
-
- foreach (TextureBindingInfo[] textureInfo in _textureBindings)
- {
- if (textureInfo != null)
- {
- count += textureInfo.Length;
- }
- }
-
- return count;
- }
-#pragma warning restore IDE0051
-
/// <summary>
/// Ensures that the texture bindings are visible to the host GPU.
/// Note: this actually performs the binding using the host graphics API.
@@ -465,6 +454,13 @@ namespace Ryujinx.Graphics.Gpu.Image
TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index];
TextureUsageFlags usageFlags = bindingInfo.Flags;
+ if (bindingInfo.ArrayLength > 1)
+ {
+ _arrayBindingsCache.UpdateTextureArray(texturePool, samplerPool, stage, stageIndex, _textureBufferIndex, _samplerIndex, bindingInfo);
+
+ continue;
+ }
+
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex);
@@ -582,7 +578,7 @@ namespace Ryujinx.Graphics.Gpu.Image
}
// Scales for images appear after the texture ones.
- int baseScaleIndex = _textureBindings[stageIndex].Length;
+ int baseScaleIndex = _textureCounts[stageIndex];
int cachedTextureBufferIndex = -1;
int cachedSamplerBufferIndex = -1;
@@ -595,6 +591,14 @@ namespace Ryujinx.Graphics.Gpu.Image
{
TextureBindingInfo bindingInfo = _imageBindings[stageIndex][index];
TextureUsageFlags usageFlags = bindingInfo.Flags;
+
+ if (bindingInfo.ArrayLength > 1)
+ {
+ _arrayBindingsCache.UpdateImageArray(pool, stage, stageIndex, _textureBufferIndex, bindingInfo);
+
+ continue;
+ }
+
int scaleIndex = baseScaleIndex + index;
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
@@ -620,7 +624,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (isStore)
{
- cachedTexture?.SignalModified();
+ cachedTexture.SignalModified();
}
Format format = bindingInfo.Format == 0 ? cachedTexture.Format : bindingInfo.Format;
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs
index aed3268a..cf783ef2 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs
@@ -1,12 +1,13 @@
using Ryujinx.Graphics.Shader;
using Ryujinx.Memory.Range;
+using System;
namespace Ryujinx.Graphics.Gpu.Memory
{
/// <summary>
/// Memory range used for buffers.
/// </summary>
- readonly struct BufferBounds
+ readonly struct BufferBounds : IEquatable<BufferBounds>
{
/// <summary>
/// Physical memory ranges where the buffer is mapped.
@@ -33,5 +34,25 @@ namespace Ryujinx.Graphics.Gpu.Memory
Range = range;
Flags = flags;
}
+
+ public override bool Equals(object obj)
+ {
+ return obj is BufferBounds bounds && Equals(bounds);
+ }
+
+ public bool Equals(BufferBounds bounds)
+ {
+ return Range == bounds.Range && Flags == bounds.Flags;
+ }
+
+ public bool Equals(ref BufferBounds bounds)
+ {
+ return Range == bounds.Range && Flags == bounds.Flags;
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(Range, Flags);
+ }
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
index 1f02b9d7..8f2201e0 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
@@ -27,6 +27,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
private readonly VertexBuffer[] _vertexBuffers;
private readonly BufferBounds[] _transformFeedbackBuffers;
private readonly List<BufferTextureBinding> _bufferTextures;
+ private readonly List<BufferTextureArrayBinding<ITextureArray>> _bufferTextureArrays;
+ private readonly List<BufferTextureArrayBinding<IImageArray>> _bufferImageArrays;
private readonly BufferAssignment[] _ranges;
/// <summary>
@@ -140,11 +142,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
_bufferTextures = new List<BufferTextureBinding>();
+ _bufferTextureArrays = new List<BufferTextureArrayBinding<ITextureArray>>();
+ _bufferImageArrays = new List<BufferTextureArrayBinding<IImageArray>>();
_ranges = new BufferAssignment[Constants.TotalGpUniformBuffers * Constants.ShaderStages];
}
-
/// <summary>
/// Sets the memory range with the index buffer data, to be used for subsequent draw calls.
/// </summary>
@@ -419,6 +422,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
/// <summary>
+ /// Gets the size of the compute uniform buffer currently bound at the given index.
+ /// </summary>
+ /// <param name="index">Index of the uniform buffer binding</param>
+ /// <returns>The uniform buffer size, or an undefined value if the buffer is not currently bound</returns>
+ public int GetComputeUniformBufferSize(int index)
+ {
+ return (int)_cpUniformBuffers.Buffers[index].Range.GetSubRange(0).Size;
+ }
+
+ /// <summary>
/// Gets the address of the graphics uniform buffer currently bound at the given index.
/// </summary>
/// <param name="stage">Index of the shader stage</param>
@@ -430,6 +443,17 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
/// <summary>
+ /// Gets the size of the graphics uniform buffer currently bound at the given index.
+ /// </summary>
+ /// <param name="stage">Index of the shader stage</param>
+ /// <param name="index">Index of the uniform buffer binding</param>
+ /// <returns>The uniform buffer size, or an undefined value if the buffer is not currently bound</returns>
+ public int GetGraphicsUniformBufferSize(int stage, int index)
+ {
+ return (int)_gpUniformBuffers[stage].Buffers[index].Range.GetSubRange(0).Size;
+ }
+
+ /// <summary>
/// Gets the bounds of the uniform buffer currently bound at the given index.
/// </summary>
/// <param name="isCompute">Indicates whenever the uniform is requested by the 3D or compute engine</param>
@@ -459,7 +483,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
BindBuffers(bufferCache, _cpStorageBuffers, isStorage: true);
BindBuffers(bufferCache, _cpUniformBuffers, isStorage: false);
- CommitBufferTextureBindings();
+ CommitBufferTextureBindings(bufferCache);
// Force rebind after doing compute work.
Rebind();
@@ -470,14 +494,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary>
/// Commit any queued buffer texture bindings.
/// </summary>
- private void CommitBufferTextureBindings()
+ /// <param name="bufferCache">Buffer cache</param>
+ private void CommitBufferTextureBindings(BufferCache bufferCache)
{
if (_bufferTextures.Count > 0)
{
foreach (var binding in _bufferTextures)
{
var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
- var range = _channel.MemoryManager.Physical.BufferCache.GetBufferRange(binding.Range, isStore);
+ var range = bufferCache.GetBufferRange(binding.Range, isStore);
binding.Texture.SetStorage(range);
// The texture must be rebound to use the new storage if it was updated.
@@ -494,6 +519,33 @@ namespace Ryujinx.Graphics.Gpu.Memory
_bufferTextures.Clear();
}
+
+ if (_bufferTextureArrays.Count > 0 || _bufferImageArrays.Count > 0)
+ {
+ ITexture[] textureArray = new ITexture[1];
+
+ foreach (var binding in _bufferTextureArrays)
+ {
+ var range = bufferCache.GetBufferRange(binding.Range);
+ binding.Texture.SetStorage(range);
+
+ textureArray[0] = binding.Texture;
+ binding.Array.SetTextures(binding.Index, textureArray);
+ }
+
+ foreach (var binding in _bufferImageArrays)
+ {
+ var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
+ var range = bufferCache.GetBufferRange(binding.Range, isStore);
+ binding.Texture.SetStorage(range);
+
+ textureArray[0] = binding.Texture;
+ binding.Array.SetImages(binding.Index, textureArray);
+ }
+
+ _bufferTextureArrays.Clear();
+ _bufferImageArrays.Clear();
+ }
}
/// <summary>
@@ -676,7 +728,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
UpdateBuffers(_gpUniformBuffers);
}
- CommitBufferTextureBindings();
+ CommitBufferTextureBindings(bufferCache);
_rebind = false;
@@ -829,6 +881,50 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
/// <summary>
+ /// Sets the buffer storage of a buffer texture array element. This will be bound when the buffer manager commits bindings.
+ /// </summary>
+ /// <param name="array">Texture array where the element will be inserted</param>
+ /// <param name="texture">Buffer texture</param>
+ /// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
+ /// <param name="bindingInfo">Binding info for the buffer texture</param>
+ /// <param name="index">Index of the binding on the array</param>
+ /// <param name="format">Format of the buffer texture</param>
+ public void SetBufferTextureStorage(
+ ITextureArray array,
+ ITexture texture,
+ MultiRange range,
+ TextureBindingInfo bindingInfo,
+ int index,
+ Format format)
+ {
+ _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range);
+
+ _bufferTextureArrays.Add(new BufferTextureArrayBinding<ITextureArray>(array, texture, range, bindingInfo, index, format));
+ }
+
+ /// <summary>
+ /// Sets the buffer storage of a buffer image array element. This will be bound when the buffer manager commits bindings.
+ /// </summary>
+ /// <param name="array">Image array where the element will be inserted</param>
+ /// <param name="texture">Buffer texture</param>
+ /// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
+ /// <param name="bindingInfo">Binding info for the buffer texture</param>
+ /// <param name="index">Index of the binding on the array</param>
+ /// <param name="format">Format of the buffer texture</param>
+ public void SetBufferTextureStorage(
+ IImageArray array,
+ ITexture texture,
+ MultiRange range,
+ TextureBindingInfo bindingInfo,
+ int index,
+ Format format)
+ {
+ _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range);
+
+ _bufferImageArrays.Add(new BufferTextureArrayBinding<IImageArray>(array, texture, range, bindingInfo, index, format));
+ }
+
+ /// <summary>
/// Force all bound textures and images to be rebound the next time CommitBindings is called.
/// </summary>
public void Rebind()
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureArrayBinding.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureArrayBinding.cs
new file mode 100644
index 00000000..fa79e4f9
--- /dev/null
+++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureArrayBinding.cs
@@ -0,0 +1,66 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Image;
+using Ryujinx.Memory.Range;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ /// <summary>
+ /// A buffer binding to apply to a buffer texture array element.
+ /// </summary>
+ readonly struct BufferTextureArrayBinding<T>
+ {
+ /// <summary>
+ /// Backend texture or image array.
+ /// </summary>
+ public T Array { get; }
+
+ /// <summary>
+ /// The buffer texture.
+ /// </summary>
+ public ITexture Texture { get; }
+
+ /// <summary>
+ /// Physical ranges of memory where the buffer texture data is located.
+ /// </summary>
+ public MultiRange Range { get; }
+
+ /// <summary>
+ /// The image or sampler binding info for the buffer texture.
+ /// </summary>
+ public TextureBindingInfo BindingInfo { get; }
+
+ /// <summary>
+ /// Index of the binding on the array.
+ /// </summary>
+ public int Index { get; }
+
+ /// <summary>
+ /// The image format for the binding.
+ /// </summary>
+ public Format Format { get; }
+
+ /// <summary>
+ /// Create a new buffer texture binding.
+ /// </summary>
+ /// <param name="texture">Buffer texture</param>
+ /// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
+ /// <param name="bindingInfo">Binding info</param>
+ /// <param name="index">Index of the binding on the array</param>
+ /// <param name="format">Binding format</param>
+ public BufferTextureArrayBinding(
+ T array,
+ ITexture texture,
+ MultiRange range,
+ TextureBindingInfo bindingInfo,
+ int index,
+ Format format)
+ {
+ Array = array;
+ Texture = texture;
+ Range = range;
+ BindingInfo = bindingInfo;
+ Index = index;
+ Format = format;
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs
index 4e1cb4e1..6e36753e 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs
@@ -17,6 +17,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
public BufferDescriptor[][] ConstantBufferBindings { get; }
public BufferDescriptor[][] StorageBufferBindings { get; }
+ public int[] TextureCounts { get; }
+
public int MaxTextureBinding { get; }
public int MaxImageBinding { get; }
@@ -34,6 +36,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
ConstantBufferBindings = new BufferDescriptor[stageCount][];
StorageBufferBindings = new BufferDescriptor[stageCount][];
+ TextureCounts = new int[stageCount];
+
int maxTextureBinding = -1;
int maxImageBinding = -1;
int offset = isCompute ? 0 : 1;
@@ -59,13 +63,19 @@ namespace Ryujinx.Graphics.Gpu.Shader
var result = new TextureBindingInfo(
target,
descriptor.Binding,
+ descriptor.ArrayLength,
descriptor.CbufSlot,
descriptor.HandleIndex,
descriptor.Flags);
- if (descriptor.Binding > maxTextureBinding)
+ if (descriptor.ArrayLength <= 1)
{
- maxTextureBinding = descriptor.Binding;
+ if (descriptor.Binding > maxTextureBinding)
+ {
+ maxTextureBinding = descriptor.Binding;
+ }
+
+ TextureCounts[i]++;
}
return result;
@@ -80,11 +90,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
target,
format,
descriptor.Binding,
+ descriptor.ArrayLength,
descriptor.CbufSlot,
descriptor.HandleIndex,
descriptor.Flags);
- if (descriptor.Binding > maxImageBinding)
+ if (descriptor.ArrayLength <= 1 && descriptor.Binding > maxImageBinding)
{
maxImageBinding = descriptor.Binding;
}
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs
index de6432bc..681838a9 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs
@@ -27,6 +27,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// <param name="cb1Data">The constant buffer 1 data of the shader</param>
/// <param name="oldSpecState">Shader specialization state of the cached shader</param>
/// <param name="newSpecState">Shader specialization state of the recompiled shader</param>
+ /// <param name="counts">Resource counts shared across all shader stages</param>
/// <param name="stageIndex">Shader stage index</param>
public DiskCacheGpuAccessor(
GpuContext context,
@@ -109,18 +110,32 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
}
/// <inheritdoc/>
- public TextureFormat QueryTextureFormat(int handle, int cbufSlot)
+ public SamplerType QuerySamplerType(int handle, int cbufSlot)
{
- _newSpecState.RecordTextureFormat(_stageIndex, handle, cbufSlot);
- (uint format, bool formatSrgb) = _oldSpecState.GetFormat(_stageIndex, handle, cbufSlot);
- return ConvertToTextureFormat(format, formatSrgb);
+ _newSpecState.RecordTextureSamplerType(_stageIndex, handle, cbufSlot);
+ return _oldSpecState.GetTextureTarget(_stageIndex, handle, cbufSlot).ConvertSamplerType();
}
/// <inheritdoc/>
- public SamplerType QuerySamplerType(int handle, int cbufSlot)
+ public int QueryTextureArrayLengthFromBuffer(int slot)
{
- _newSpecState.RecordTextureSamplerType(_stageIndex, handle, cbufSlot);
- return _oldSpecState.GetTextureTarget(_stageIndex, handle, cbufSlot).ConvertSamplerType();
+ if (!_oldSpecState.TextureArrayFromBufferRegistered(_stageIndex, 0, slot))
+ {
+ throw new DiskCacheLoadException(DiskCacheLoadResult.MissingTextureArrayLength);
+ }
+
+ int arrayLength = _oldSpecState.GetTextureArrayFromBufferLength(_stageIndex, 0, slot);
+ _newSpecState.RegisterTextureArrayLengthFromBuffer(_stageIndex, 0, slot, arrayLength);
+
+ return arrayLength;
+ }
+
+ /// <inheritdoc/>
+ public TextureFormat QueryTextureFormat(int handle, int cbufSlot)
+ {
+ _newSpecState.RecordTextureFormat(_stageIndex, handle, cbufSlot);
+ (uint format, bool formatSrgb) = _oldSpecState.GetFormat(_stageIndex, handle, cbufSlot);
+ return ConvertToTextureFormat(format, formatSrgb);
}
/// <inheritdoc/>
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
index 5036186b..b6a277a2 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
- private const uint CodeGenVersion = 6462;
+ private const uint CodeGenVersion = 6489;
private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data";
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadResult.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadResult.cs
index ba23f70e..d5abb9e5 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadResult.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadResult.cs
@@ -21,6 +21,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
InvalidCb1DataLength,
/// <summary>
+ /// The cache is missing the length of a texture array used by the shader.
+ /// </summary>
+ MissingTextureArrayLength,
+
+ /// <summary>
/// The cache is missing the descriptor of a texture used by the shader.
/// </summary>
MissingTextureDescriptor,
@@ -60,6 +65,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
DiskCacheLoadResult.Success => "No error.",
DiskCacheLoadResult.NoAccess => "Could not access the cache file.",
DiskCacheLoadResult.InvalidCb1DataLength => "Constant buffer 1 data length is too low.",
+ DiskCacheLoadResult.MissingTextureArrayLength => "Texture array length missing from the cache file.",
DiskCacheLoadResult.MissingTextureDescriptor => "Texture descriptor missing from the cache file.",
DiskCacheLoadResult.FileCorruptedGeneric => "The cache file is corrupted.",
DiskCacheLoadResult.FileCorruptedInvalidMagic => "Magic check failed, the cache file is corrupted.",
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
index 95763f31..1d22ab93 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
@@ -72,6 +72,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
public ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize)
{
int size = Math.Max(minimumSize, 0x1000 - (int)(address & 0xfff));
+
return MemoryMarshal.Cast<byte, ulong>(_channel.MemoryManager.GetSpan(address, size));
}
@@ -119,6 +120,27 @@ namespace Ryujinx.Graphics.Gpu.Shader
return _state.GraphicsState.HasUnalignedStorageBuffer || _state.ComputeState.HasUnalignedStorageBuffer;
}
+ /// <inheritdoc/>
+ public SamplerType QuerySamplerType(int handle, int cbufSlot)
+ {
+ _state.SpecializationState?.RecordTextureSamplerType(_stageIndex, handle, cbufSlot);
+ return GetTextureDescriptor(handle, cbufSlot).UnpackTextureTarget().ConvertSamplerType();
+ }
+
+ /// <inheritdoc/>
+ public int QueryTextureArrayLengthFromBuffer(int slot)
+ {
+ int size = _compute
+ ? _channel.BufferManager.GetComputeUniformBufferSize(slot)
+ : _channel.BufferManager.GetGraphicsUniformBufferSize(_stageIndex, slot);
+
+ int arrayLength = size / Constants.TextureHandleSizeInBytes;
+
+ _state.SpecializationState?.RegisterTextureArrayLengthFromBuffer(_stageIndex, 0, slot, arrayLength);
+
+ return arrayLength;
+ }
+
//// <inheritdoc/>
public TextureFormat QueryTextureFormat(int handle, int cbufSlot)
{
@@ -128,13 +150,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
/// <inheritdoc/>
- public SamplerType QuerySamplerType(int handle, int cbufSlot)
- {
- _state.SpecializationState?.RecordTextureSamplerType(_stageIndex, handle, cbufSlot);
- return GetTextureDescriptor(handle, cbufSlot).UnpackTextureTarget().ConvertSamplerType();
- }
-
- /// <inheritdoc/>
public bool QueryTextureCoordNormalized(int handle, int cbufSlot)
{
_state.SpecializationState?.RecordTextureCoordNormalized(_stageIndex, handle, cbufSlot);
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs
index a5b31363..06e5edf1 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs
@@ -20,6 +20,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
private int _reservedTextures;
private int _reservedImages;
+ private int _staticTexturesCount;
+ private int _staticImagesCount;
+
/// <summary>
/// Creates a new GPU accessor.
/// </summary>
@@ -48,7 +51,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
_reservedImages = rrc.ReservedImages;
}
- public int QueryBindingConstantBuffer(int index)
+ public int CreateConstantBufferBinding(int index)
{
int binding;
@@ -64,62 +67,84 @@ namespace Ryujinx.Graphics.Gpu.Shader
return binding + _reservedConstantBuffers;
}
- public int QueryBindingStorageBuffer(int index)
+ public int CreateImageBinding(int count, bool isBuffer)
{
int binding;
if (_context.Capabilities.Api == TargetApi.Vulkan)
{
- binding = GetBindingFromIndex(index, _context.Capabilities.MaximumStorageBuffersPerStage, "Storage buffer");
+ if (count == 1)
+ {
+ int index = _staticImagesCount++;
+
+ if (isBuffer)
+ {
+ index += (int)_context.Capabilities.MaximumImagesPerStage;
+ }
+
+ binding = GetBindingFromIndex(index, _context.Capabilities.MaximumImagesPerStage * 2, "Image");
+ }
+ else
+ {
+ binding = (int)GetDynamicBaseIndexDual(_context.Capabilities.MaximumImagesPerStage) + _resourceCounts.ImagesCount++;
+ }
}
else
{
- binding = _resourceCounts.StorageBuffersCount++;
+ binding = _resourceCounts.ImagesCount;
+
+ _resourceCounts.ImagesCount += count;
}
- return binding + _reservedStorageBuffers;
+ return binding + _reservedImages;
}
- public int QueryBindingTexture(int index, bool isBuffer)
+ public int CreateStorageBufferBinding(int index)
{
int binding;
if (_context.Capabilities.Api == TargetApi.Vulkan)
{
- if (isBuffer)
- {
- index += (int)_context.Capabilities.MaximumTexturesPerStage;
- }
-
- binding = GetBindingFromIndex(index, _context.Capabilities.MaximumTexturesPerStage * 2, "Texture");
+ binding = GetBindingFromIndex(index, _context.Capabilities.MaximumStorageBuffersPerStage, "Storage buffer");
}
else
{
- binding = _resourceCounts.TexturesCount++;
+ binding = _resourceCounts.StorageBuffersCount++;
}
- return binding + _reservedTextures;
+ return binding + _reservedStorageBuffers;
}
- public int QueryBindingImage(int index, bool isBuffer)
+ public int CreateTextureBinding(int count, bool isBuffer)
{
int binding;
if (_context.Capabilities.Api == TargetApi.Vulkan)
{
- if (isBuffer)
+ if (count == 1)
{
- index += (int)_context.Capabilities.MaximumImagesPerStage;
- }
+ int index = _staticTexturesCount++;
+
+ if (isBuffer)
+ {
+ index += (int)_context.Capabilities.MaximumTexturesPerStage;
+ }
- binding = GetBindingFromIndex(index, _context.Capabilities.MaximumImagesPerStage * 2, "Image");
+ binding = GetBindingFromIndex(index, _context.Capabilities.MaximumTexturesPerStage * 2, "Texture");
+ }
+ else
+ {
+ binding = (int)GetDynamicBaseIndexDual(_context.Capabilities.MaximumTexturesPerStage) + _resourceCounts.TexturesCount++;
+ }
}
else
{
- binding = _resourceCounts.ImagesCount++;
+ binding = _resourceCounts.TexturesCount;
+
+ _resourceCounts.TexturesCount += count;
}
- return binding + _reservedImages;
+ return binding + _reservedTextures;
}
private int GetBindingFromIndex(int index, uint maxPerStage, string resourceName)
@@ -148,6 +173,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
};
}
+ private static uint GetDynamicBaseIndexDual(uint maxPerStage)
+ {
+ return GetDynamicBaseIndex(maxPerStage) * 2;
+ }
+
+ private static uint GetDynamicBaseIndex(uint maxPerStage)
+ {
+ return maxPerStage * Constants.ShaderStages;
+ }
+
public int QueryHostGatherBiasPrecision() => _context.Capabilities.GatherBiasPrecision;
public bool QueryHostReducedPrecision() => _context.Capabilities.ReduceShaderPrecision;
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs
index c2258026..ea8f164f 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs
@@ -132,6 +132,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
AddDualDescriptor(stages, ResourceType.TextureAndSampler, ResourceType.BufferTexture, TextureSetIndex, textureBinding, texturesPerStage);
AddDualDescriptor(stages, ResourceType.Image, ResourceType.BufferImage, ImageSetIndex, imageBinding, imagesPerStage);
+ AddArrayDescriptors(info.Textures, stages, TextureSetIndex, isImage: false);
+ AddArrayDescriptors(info.Images, stages, TextureSetIndex, isImage: true);
+
AddUsage(info.CBuffers, stages, UniformSetIndex, isStorage: false);
AddUsage(info.SBuffers, stages, StorageSetIndex, isStorage: true);
AddUsage(info.Textures, stages, TextureSetIndex, isImage: false);
@@ -170,6 +173,30 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
/// <summary>
+ /// Adds all array descriptors (those with an array length greater than one).
+ /// </summary>
+ /// <param name="textures">Textures to be added</param>
+ /// <param name="stages">Stages where the textures are used</param>
+ /// <param name="setIndex">Descriptor set index where the textures will be bound</param>
+ /// <param name="isImage">True for images, false for textures</param>
+ private void AddArrayDescriptors(IEnumerable<TextureDescriptor> textures, ResourceStages stages, int setIndex, bool isImage)
+ {
+ foreach (TextureDescriptor texture in textures)
+ {
+ if (texture.ArrayLength > 1)
+ {
+ bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer;
+
+ ResourceType type = isBuffer
+ ? (isImage ? ResourceType.BufferImage : ResourceType.BufferTexture)
+ : (isImage ? ResourceType.Image : ResourceType.TextureAndSampler);
+
+ _resourceDescriptors[setIndex].Add(new ResourceDescriptor(texture.Binding, texture.ArrayLength, type, stages));
+ }
+ }
+ }
+
+ /// <summary>
/// Adds buffer usage information to the list of usages.
/// </summary>
/// <param name="stages">Shader stages where the resource is used</param>
@@ -181,7 +208,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
for (int index = 0; index < count; index++)
{
- _resourceUsages[setIndex].Add(new ResourceUsage(binding + index, type, stages));
+ _resourceUsages[setIndex].Add(new ResourceUsage(binding + index, 1, type, stages));
}
}
@@ -198,6 +225,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
_resourceUsages[setIndex].Add(new ResourceUsage(
buffer.Binding,
+ 1,
isStorage ? ResourceType.StorageBuffer : ResourceType.UniformBuffer,
stages));
}
@@ -220,10 +248,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
? (isImage ? ResourceType.BufferImage : ResourceType.BufferTexture)
: (isImage ? ResourceType.Image : ResourceType.TextureAndSampler);
- _resourceUsages[setIndex].Add(new ResourceUsage(
- texture.Binding,
- type,
- stages));
+ _resourceUsages[setIndex].Add(new ResourceUsage(texture.Binding, texture.ArrayLength, type, stages));
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
index 1477b738..c90a0b8f 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
@@ -30,6 +30,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
PrimitiveTopology = 1 << 1,
TransformFeedback = 1 << 3,
+ TextureArrayFromBuffer = 1 << 4,
}
private QueriedStateFlags _queriedState;
@@ -153,6 +154,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
private readonly Dictionary<TextureKey, Box<TextureSpecializationState>> _textureSpecialization;
+ private readonly Dictionary<TextureKey, int> _textureArraySpecialization;
private KeyValuePair<TextureKey, Box<TextureSpecializationState>>[] _allTextures;
private Box<TextureSpecializationState>[][] _textureByBinding;
private Box<TextureSpecializationState>[][] _imageByBinding;
@@ -163,6 +165,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
private ShaderSpecializationState()
{
_textureSpecialization = new Dictionary<TextureKey, Box<TextureSpecializationState>>();
+ _textureArraySpecialization = new Dictionary<TextureKey, int>();
}
/// <summary>
@@ -324,6 +327,19 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
/// <summary>
+ /// Indicates that the coordinate normalization state of a given texture was used during the shader translation process.
+ /// </summary>
+ /// <param name="stageIndex">Shader stage where the texture is used</param>
+ /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
+ /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
+ /// <param name="length">Number of elements in the texture array</param>
+ public void RegisterTextureArrayLengthFromBuffer(int stageIndex, int handle, int cbufSlot, int length)
+ {
+ _textureArraySpecialization[new TextureKey(stageIndex, handle, cbufSlot)] = length;
+ _queriedState |= QueriedStateFlags.TextureArrayFromBuffer;
+ }
+
+ /// <summary>
/// Indicates that the format of a given texture was used during the shader translation process.
/// </summary>
/// <param name="stageIndex">Shader stage where the texture is used</param>
@@ -380,6 +396,17 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
/// <summary>
+ /// Checks if a given texture array (from constant buffer) was registerd on this specialization state.
+ /// </summary>
+ /// <param name="stageIndex">Shader stage where the texture is used</param>
+ /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
+ /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
+ public bool TextureArrayFromBufferRegistered(int stageIndex, int handle, int cbufSlot)
+ {
+ return _textureArraySpecialization.ContainsKey(new TextureKey(stageIndex, handle, cbufSlot));
+ }
+
+ /// <summary>
/// Gets the recorded format of a given texture.
/// </summary>
/// <param name="stageIndex">Shader stage where the texture is used</param>
@@ -414,6 +441,17 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
/// <summary>
+ /// Gets the recorded length of a given texture array (from constant buffer).
+ /// </summary>
+ /// <param name="stageIndex">Shader stage where the texture is used</param>
+ /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
+ /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
+ public int GetTextureArrayFromBufferLength(int stageIndex, int handle, int cbufSlot)
+ {
+ return _textureArraySpecialization[new TextureKey(stageIndex, handle, cbufSlot)];
+ }
+
+ /// <summary>
/// Gets texture specialization state for a given texture, or create a new one if not present.
/// </summary>
/// <param name="stageIndex">Shader stage where the texture is used</param>
@@ -548,6 +586,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
return Matches(channel, ref poolState, checkTextures, isCompute: false);
}
+ /// <summary>
+ /// Converts special vertex attribute groups to their generic equivalents, for comparison purposes.
+ /// </summary>
+ /// <param name="channel">GPU channel</param>
+ /// <param name="type">Vertex attribute type</param>
+ /// <returns>Filtered attribute</returns>
private static AttributeType FilterAttributeType(GpuChannel channel, AttributeType type)
{
type &= ~(AttributeType.Packed | AttributeType.PackedRgb10A2Signed);
@@ -838,6 +882,22 @@ namespace Ryujinx.Graphics.Gpu.Shader
specState._textureSpecialization[textureKey] = textureState;
}
+ if (specState._queriedState.HasFlag(QueriedStateFlags.TextureArrayFromBuffer))
+ {
+ dataReader.Read(ref count);
+
+ for (int index = 0; index < count; index++)
+ {
+ TextureKey textureKey = default;
+ int length = 0;
+
+ dataReader.ReadWithMagicAndSize(ref textureKey, TexkMagic);
+ dataReader.Read(ref length);
+
+ specState._textureArraySpecialization[textureKey] = length;
+ }
+ }
+
return specState;
}
@@ -902,6 +962,21 @@ namespace Ryujinx.Graphics.Gpu.Shader
dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic);
dataWriter.WriteWithMagicAndSize(ref textureState.Value, TexsMagic);
}
+
+ if (_queriedState.HasFlag(QueriedStateFlags.TextureArrayFromBuffer))
+ {
+ count = (ushort)_textureArraySpecialization.Count;
+ dataWriter.Write(ref count);
+
+ foreach (var kv in _textureArraySpecialization)
+ {
+ var textureKey = kv.Key;
+ var length = kv.Value;
+
+ dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic);
+ dataWriter.Write(ref length);
+ }
+ }
}
}
}