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 | |
| 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')
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); + } + } } } } |
