diff options
| author | gdkchan <gab.dark.100@gmail.com> | 2024-04-07 18:25:55 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-04-07 18:25:55 -0300 |
| commit | 3e6e0e4afaa3c3ffb118cb17b61feb16966a7eeb (patch) | |
| tree | a4652499c089b0853e39c382cad82a9db4d6ad08 /src/Ryujinx.Graphics.Gpu/Image | |
| parent | 808803d97a0c06809bf000687c252f960048fcf0 (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/Image')
| -rw-r--r-- | src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs | 3 | ||||
| -rw-r--r-- | src/Ryujinx.Graphics.Gpu/Image/Pool.cs | 32 | ||||
| -rw-r--r-- | src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs | 12 | ||||
| -rw-r--r-- | src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs | 714 | ||||
| -rw-r--r-- | src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs | 54 |
5 files changed, 786 insertions, 29 deletions
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; |
