diff options
| author | gdkchan <gab.dark.100@gmail.com> | 2024-04-22 15:05:55 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-04-22 15:05:55 -0300 |
| commit | c6f8bfed904e30f7c5d890a2f0ef531eb9e298e5 (patch) | |
| tree | e1c048d390867e8c9403904498184e3a64277e49 /src/Ryujinx.Graphics.Gpu | |
| parent | 9b94662b4bb2ebf846e1baf45ba8097fcd7da684 (diff) | |
Add support for bindless textures from shader input (vertex buffer) on Vulkan (#6577)
* Add support for bindless textures from shader input (vertex buffer)
* Shader cache version bump
* Format whitespace
* Remove cache entries on pool removal, disable for OpenGL
* PR feedback
Diffstat (limited to 'src/Ryujinx.Graphics.Gpu')
17 files changed, 739 insertions, 158 deletions
diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs b/src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs index ccdbe474..cd814472 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs @@ -126,6 +126,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute ulong samplerPoolGpuVa = ((ulong)_state.State.SetTexSamplerPoolAOffsetUpper << 32) | _state.State.SetTexSamplerPoolB; ulong texturePoolGpuVa = ((ulong)_state.State.SetTexHeaderPoolAOffsetUpper << 32) | _state.State.SetTexHeaderPoolB; + int samplerPoolMaximumId = _state.State.SetTexSamplerPoolCMaximumIndex; + GpuChannelPoolState poolState = new( texturePoolGpuVa, _state.State.SetTexHeaderPoolCMaximumIndex, @@ -139,7 +141,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute sharedMemorySize, _channel.BufferManager.HasUnalignedStorageBuffers); - CachedShaderProgram cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, poolState, computeState, shaderGpuVa); + CachedShaderProgram cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, samplerPoolMaximumId, poolState, computeState, shaderGpuVa); _context.Renderer.Pipeline.SetProgram(cs.HostProgram); @@ -184,7 +186,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute sharedMemorySize, _channel.BufferManager.HasUnalignedStorageBuffers); - cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, poolState, computeState, shaderGpuVa); + cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, samplerPoolMaximumId, poolState, computeState, shaderGpuVa); _context.Renderer.Pipeline.SetProgram(cs.HostProgram); } diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs index b3eb6218..1dc77b52 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs @@ -1429,7 +1429,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed addressesSpan[index] = baseAddress + shader.Offset; } - CachedShaderProgram gs = shaderCache.GetGraphicsShader(ref _state.State, ref _pipeline, _channel, ref _currentSpecState.GetPoolState(), ref _currentSpecState.GetGraphicsState(), addresses); + int samplerPoolMaximumId = _state.State.SamplerIndex == SamplerIndex.ViaHeaderIndex + ? _state.State.TexturePoolState.MaximumId + : _state.State.SamplerPoolState.MaximumId; + + CachedShaderProgram gs = shaderCache.GetGraphicsShader( + ref _state.State, + ref _pipeline, + _channel, + samplerPoolMaximumId, + ref _currentSpecState.GetPoolState(), + ref _currentSpecState.GetGraphicsState(), + addresses); // Consume the modified flag for spec state so that it isn't checked again. _currentSpecState.SetShader(gs); diff --git a/src/Ryujinx.Graphics.Gpu/Image/PoolCache.cs b/src/Ryujinx.Graphics.Gpu/Image/PoolCache.cs index d9881f89..50872ab6 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/PoolCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/PoolCache.cs @@ -62,8 +62,9 @@ namespace Ryujinx.Graphics.Gpu.Image /// <param name="channel">GPU channel that the texture pool cache belongs to</param> /// <param name="address">Start address of the texture pool</param> /// <param name="maximumId">Maximum ID of the texture pool</param> + /// <param name="bindingsArrayCache">Cache of texture array bindings</param> /// <returns>The found or newly created texture pool</returns> - public T FindOrCreate(GpuChannel channel, ulong address, int maximumId) + public T FindOrCreate(GpuChannel channel, ulong address, int maximumId, TextureBindingsArrayCache bindingsArrayCache) { // Remove old entries from the cache, if possible. while (_pools.Count > MaxCapacity && (_currentTimestamp - _pools.First.Value.CacheTimestamp) >= MinDeltaForRemoval) @@ -73,6 +74,7 @@ namespace Ryujinx.Graphics.Gpu.Image _pools.RemoveFirst(); oldestPool.Dispose(); oldestPool.CacheNode = null; + bindingsArrayCache.RemoveAllWithPool(oldestPool); } T pool; @@ -87,8 +89,7 @@ namespace Ryujinx.Graphics.Gpu.Image if (pool.CacheNode != _pools.Last) { _pools.Remove(pool.CacheNode); - - pool.CacheNode = _pools.AddLast(pool); + _pools.AddLast(pool.CacheNode); } pool.CacheTimestamp = _currentTimestamp; diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs index 12a457db..ba895c60 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs @@ -45,6 +45,11 @@ namespace Ryujinx.Graphics.Gpu.Image public TextureUsageFlags Flags { get; } /// <summary> + /// Indicates that the binding is for a sampler. + /// </summary> + public bool IsSamplerOnly { get; } + + /// <summary> /// Constructs the texture binding information structure. /// </summary> /// <param name="target">The shader sampler target type</param> @@ -74,8 +79,17 @@ namespace Ryujinx.Graphics.Gpu.Image /// <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 arrayLength, int cbufSlot, int handle, TextureUsageFlags flags) : this(target, (Format)0, binding, arrayLength, cbufSlot, handle, flags) + /// <param name="isSamplerOnly">Indicates that the binding is for a sampler</param> + public TextureBindingInfo( + Target target, + int binding, + int arrayLength, + int cbufSlot, + int handle, + TextureUsageFlags flags, + bool isSamplerOnly) : this(target, 0, binding, arrayLength, cbufSlot, handle, flags) { + IsSamplerOnly = isSamplerOnly; } } } diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs index 4645317c..7e486e0a 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs @@ -21,12 +21,98 @@ namespace Ryujinx.Graphics.Gpu.Image private readonly GpuContext _context; private readonly GpuChannel _channel; - private readonly bool _isCompute; /// <summary> /// Array cache entry key. /// </summary> - private readonly struct CacheEntryKey : IEquatable<CacheEntryKey> + private readonly struct CacheEntryFromPoolKey : IEquatable<CacheEntryFromPoolKey> + { + /// <summary> + /// Whether the entry is for an image. + /// </summary> + public readonly bool IsImage; + + /// <summary> + /// Whether the entry is for a sampler. + /// </summary> + public readonly bool IsSampler; + + /// <summary> + /// Texture or image target type. + /// </summary> + public readonly Target Target; + + /// <summary> + /// Number of entries of the array. + /// </summary> + public readonly int ArrayLength; + + private readonly TexturePool _texturePool; + private readonly SamplerPool _samplerPool; + + /// <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> + public CacheEntryFromPoolKey(bool isImage, TextureBindingInfo bindingInfo, TexturePool texturePool, SamplerPool samplerPool) + { + IsImage = isImage; + IsSampler = bindingInfo.IsSamplerOnly; + Target = bindingInfo.Target; + ArrayLength = bindingInfo.ArrayLength; + + _texturePool = texturePool; + _samplerPool = samplerPool; + } + + /// <summary> + /// Checks if the pool matches the cached pool. + /// </summary> + /// <param name="texturePool">Texture or sampler pool instance</param> + /// <returns>True if the pool matches, false otherwise</returns> + public bool MatchesPool<T>(IPool<T> pool) + { + return _texturePool == pool || _samplerPool == pool; + } + + /// <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; + } + + public bool Equals(CacheEntryFromPoolKey other) + { + return IsImage == other.IsImage && + IsSampler == other.IsSampler && + Target == other.Target && + ArrayLength == other.ArrayLength && + MatchesPools(other._texturePool, other._samplerPool); + } + + public override bool Equals(object obj) + { + return obj is CacheEntryFromBufferKey other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(_texturePool, _samplerPool, IsSampler); + } + } + + /// <summary> + /// Array cache entry key. + /// </summary> + private readonly struct CacheEntryFromBufferKey : IEquatable<CacheEntryFromBufferKey> { /// <summary> /// Whether the entry is for an image. @@ -61,7 +147,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// <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( + public CacheEntryFromBufferKey( bool isImage, TextureBindingInfo bindingInfo, TexturePool texturePool, @@ -100,7 +186,7 @@ namespace Ryujinx.Graphics.Gpu.Image return _textureBufferBounds.Equals(textureBufferBounds); } - public bool Equals(CacheEntryKey other) + public bool Equals(CacheEntryFromBufferKey other) { return IsImage == other.IsImage && Target == other.Target && @@ -112,7 +198,7 @@ namespace Ryujinx.Graphics.Gpu.Image public override bool Equals(object obj) { - return obj is CacheEntryKey other && Equals(other); + return obj is CacheEntryFromBufferKey other && Equals(other); } public override int GetHashCode() @@ -122,88 +208,58 @@ namespace Ryujinx.Graphics.Gpu.Image } /// <summary> - /// Array cache entry. + /// Array cache entry from pool. /// </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. + /// Backend texture array if the entry is for a texture, otherwise null. /// </summary> - public readonly Dictionary<int, Texture> TextureIds; + public readonly ITextureArray TextureArray; /// <summary> - /// All pool sampler IDs along with their samplers. + /// Backend image array if the entry is for an image, otherwise null. /// </summary> - public readonly Dictionary<int, Sampler> SamplerIds; + public readonly IImageArray ImageArray; /// <summary> - /// Backend texture array if the entry is for a texture, otherwise null. + /// Texture pool where the array textures are located. /// </summary> - public readonly ITextureArray TextureArray; + protected readonly TexturePool TexturePool; /// <summary> - /// Backend image array if the entry is for an image, otherwise null. + /// Sampler pool where the array samplers are located. /// </summary> - public readonly IImageArray ImageArray; - - private readonly TexturePool _texturePool; - private readonly SamplerPool _samplerPool; + protected 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) + private CacheEntry(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; + TexturePool = texturePool; + SamplerPool = samplerPool; } /// <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) + public CacheEntry(ITextureArray array, TexturePool texturePool, SamplerPool samplerPool) : this(texturePool, samplerPool) { TextureArray = array; } @@ -211,11 +267,10 @@ namespace Ryujinx.Graphics.Gpu.Image /// <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) + public CacheEntry(IImageArray array, TexturePool texturePool, SamplerPool samplerPool) : this(texturePool, samplerPool) { ImageArray = array; } @@ -248,23 +303,9 @@ namespace Ryujinx.Graphics.Gpu.Image /// <summary> /// Clears all cached texture instances. /// </summary> - public void Reset() + public virtual 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> @@ -287,39 +328,105 @@ namespace Ryujinx.Graphics.Gpu.Image /// <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() + /// <returns>True if any used entries of the pool might have been modified, false otherwise</returns> + public bool TexturePoolModified() { - bool texturePoolModified = _texturePool.WasModified(ref _texturePoolSequence); - bool samplerPoolModified = _samplerPool.WasModified(ref _samplerPoolSequence); + return TexturePool.WasModified(ref _texturePoolSequence); + } - // If both pools were not modified since the last check, we have nothing else to check. - if (!texturePoolModified && !samplerPoolModified) - { - return false; - } + /// <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 pool might have been modified, false otherwise</returns> + public bool SamplerPoolModified() + { + return SamplerPool.WasModified(ref _samplerPoolSequence); + } + } - // If the pools were modified, let's check if any of the entries we care about changed. + /// <summary> + /// Array cache entry from constant buffer. + /// </summary> + private class CacheEntryFromBuffer : CacheEntry + { + /// <summary> + /// Key for this entry on the cache. + /// </summary> + public readonly CacheEntryFromBufferKey Key; - // 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; - } - } + /// <summary> + /// Linked list node used on the texture bindings array cache. + /// </summary> + public LinkedListNode<CacheEntryFromBuffer> CacheNode; - // 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; - } - } + /// <summary> + /// Timestamp set on the last use of the array by the cache. + /// </summary> + public int CacheTimestamp; - return false; + /// <summary> + /// All pool texture IDs along with their textures. + /// </summary> + public readonly Dictionary<int, (Texture, TextureDescriptor)> TextureIds; + + /// <summary> + /// All pool sampler IDs along with their samplers. + /// </summary> + public readonly Dictionary<int, (Sampler, SamplerDescriptor)> SamplerIds; + + 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="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 CacheEntryFromBuffer(ref CacheEntryFromBufferKey key, ITextureArray array, TexturePool texturePool, SamplerPool samplerPool) : base(array, texturePool, samplerPool) + { + Key = key; + _lastSequenceNumber = -1; + TextureIds = new Dictionary<int, (Texture, TextureDescriptor)>(); + SamplerIds = new Dictionary<int, (Sampler, SamplerDescriptor)>(); + } + + /// <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 CacheEntryFromBuffer(ref CacheEntryFromBufferKey key, IImageArray array, TexturePool texturePool, SamplerPool samplerPool) : base(array, texturePool, samplerPool) + { + Key = key; + _lastSequenceNumber = -1; + TextureIds = new Dictionary<int, (Texture, TextureDescriptor)>(); + SamplerIds = new Dictionary<int, (Sampler, SamplerDescriptor)>(); + } + + /// <inheritdoc/> + public override void Reset() + { + base.Reset(); + 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> @@ -380,10 +487,51 @@ namespace Ryujinx.Graphics.Gpu.Image 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 = TexturePoolModified(); + bool samplerPoolModified = SamplerPoolModified(); + + // 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, TextureDescriptor descriptor)) in TextureIds) + { + if (TexturePool.GetCachedItem(textureId) != texture || + (texture == null && TexturePool.IsValidId(textureId) && !TexturePool.GetDescriptorRef(textureId).Equals(descriptor))) + { + return true; + } + } + + // Check if any of our cached samplers changed on the pool. + foreach ((int samplerId, (Sampler sampler, SamplerDescriptor descriptor)) in SamplerIds) + { + if (SamplerPool.GetCachedItem(samplerId) != sampler || + (sampler == null && SamplerPool.IsValidId(samplerId) && !SamplerPool.GetDescriptorRef(samplerId).Equals(descriptor))) + { + return true; + } + } + + return false; + } } - private readonly Dictionary<CacheEntryKey, CacheEntry> _cache; - private readonly LinkedList<CacheEntry> _lruCache; + private readonly Dictionary<CacheEntryFromBufferKey, CacheEntryFromBuffer> _cacheFromBuffer; + private readonly Dictionary<CacheEntryFromPoolKey, CacheEntry> _cacheFromPool; + private readonly LinkedList<CacheEntryFromBuffer> _lruCache; private int _currentTimestamp; @@ -392,14 +540,13 @@ namespace Ryujinx.Graphics.Gpu.Image /// </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) + public TextureBindingsArrayCache(GpuContext context, GpuChannel channel) { _context = context; _channel = channel; - _isCompute = isCompute; - _cache = new Dictionary<CacheEntryKey, CacheEntry>(); - _lruCache = new LinkedList<CacheEntry>(); + _cacheFromBuffer = new Dictionary<CacheEntryFromBufferKey, CacheEntryFromBuffer>(); + _cacheFromPool = new Dictionary<CacheEntryFromPoolKey, CacheEntry>(); + _lruCache = new LinkedList<CacheEntryFromBuffer>(); } /// <summary> @@ -458,14 +605,179 @@ namespace Ryujinx.Graphics.Gpu.Image SamplerIndex samplerIndex, TextureBindingInfo bindingInfo) { + if (IsDirectHandleType(bindingInfo.Handle)) + { + UpdateFromPool(texturePool, samplerPool, stage, isImage, bindingInfo); + } + else + { + UpdateFromBuffer(texturePool, samplerPool, stage, stageIndex, textureBufferIndex, isImage, samplerIndex, bindingInfo); + } + } + + /// <summary> + /// Updates a texture or image array bindings and textures from a texture or sampler pool. + /// </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="isImage">Whether the array is a image or texture array</param> + /// <param name="bindingInfo">Array binding information</param> + private void UpdateFromPool(TexturePool texturePool, SamplerPool samplerPool, ShaderStage stage, bool isImage, TextureBindingInfo bindingInfo) + { + CacheEntry entry = GetOrAddEntry(texturePool, samplerPool, bindingInfo, isImage, out bool isNewEntry); + + bool isSampler = bindingInfo.IsSamplerOnly; + bool poolModified = isSampler ? entry.SamplerPoolModified() : entry.TexturePoolModified(); + bool isStore = bindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore); + bool resScaleUnsupported = bindingInfo.Flags.HasFlag(TextureUsageFlags.ResScaleUnsupported); + + if (!poolModified && !isNewEntry && entry.ValidateTextures()) + { + entry.SynchronizeMemory(isStore, resScaleUnsupported); + + if (isImage) + { + _context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray); + } + else + { + _context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray); + } + + return; + } + + if (!isNewEntry) + { + entry.Reset(); + } + + int length = (isSampler ? samplerPool.MaximumId : texturePool.MaximumId) + 1; + length = Math.Min(length, bindingInfo.ArrayLength); + + 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 < length; index++) + { + Texture texture = null; + Sampler sampler = null; + + if (isSampler) + { + sampler = samplerPool?.Get(index); + } + else + { + ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(index, out texture); + + if (texture != null) + { + entry.Textures[texture] = texture.InvalidatedSequence; + + if (isStore) + { + texture.SignalModified(); + } + + if (resScaleUnsupported && texture.ScaleMode != TextureScaleMode.Blacklisted) + { + // Scaling textures used on arrays is currently not supported. + + texture.BlacklistScale(); + } + } + } + + 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> + /// Updates a texture or image array bindings and textures from constant buffer handles. + /// </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 UpdateFromBuffer( + 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; + bool isCompute = stage == ShaderStage.Compute; - ref BufferBounds textureBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, textureBufferIndex); - ref BufferBounds samplerBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, samplerBufferIndex); + ref BufferBounds textureBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, textureBufferIndex); + ref BufferBounds samplerBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, samplerBufferIndex); - CacheEntry entry = GetOrAddEntry( + CacheEntryFromBuffer entry = GetOrAddEntry( texturePool, samplerPool, bindingInfo, @@ -589,8 +901,8 @@ namespace Ryujinx.Graphics.Gpu.Image Sampler sampler = samplerPool?.Get(samplerId); - entry.TextureIds[textureId] = texture; - entry.SamplerIds[samplerId] = sampler; + entry.TextureIds[textureId] = (texture, descriptor); + entry.SamplerIds[samplerId] = (sampler, samplerPool?.GetDescriptorRef(samplerId) ?? default); ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target); ISampler hostSampler = sampler?.GetHostSampler(texture); @@ -650,13 +962,12 @@ namespace Ryujinx.Graphics.Gpu.Image } /// <summary> - /// Gets a cached texture entry, or creates a new one if not found. + /// Gets a cached texture entry from pool, 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( @@ -664,17 +975,59 @@ namespace Ryujinx.Graphics.Gpu.Image SamplerPool samplerPool, TextureBindingInfo bindingInfo, bool isImage, + out bool isNew) + { + CacheEntryFromPoolKey key = new CacheEntryFromPoolKey(isImage, bindingInfo, texturePool, samplerPool); + + isNew = !_cacheFromPool.TryGetValue(key, out CacheEntry entry); + + if (isNew) + { + int arrayLength = bindingInfo.ArrayLength; + + if (isImage) + { + IImageArray array = _context.Renderer.CreateImageArray(arrayLength, bindingInfo.Target == Target.TextureBuffer); + + _cacheFromPool.Add(key, entry = new CacheEntry(array, texturePool, samplerPool)); + } + else + { + ITextureArray array = _context.Renderer.CreateTextureArray(arrayLength, bindingInfo.Target == Target.TextureBuffer); + + _cacheFromPool.Add(key, entry = new CacheEntry(array, texturePool, samplerPool)); + } + } + + return entry; + } + + /// <summary> + /// Gets a cached texture entry from constant buffer, 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 CacheEntryFromBuffer GetOrAddEntry( + TexturePool texturePool, + SamplerPool samplerPool, + TextureBindingInfo bindingInfo, + bool isImage, ref BufferBounds textureBufferBounds, out bool isNew) { - CacheEntryKey key = new CacheEntryKey( + CacheEntryFromBufferKey key = new CacheEntryFromBufferKey( isImage, bindingInfo, texturePool, samplerPool, ref textureBufferBounds); - isNew = !_cache.TryGetValue(key, out CacheEntry entry); + isNew = !_cacheFromBuffer.TryGetValue(key, out CacheEntryFromBuffer entry); if (isNew) { @@ -684,13 +1037,13 @@ namespace Ryujinx.Graphics.Gpu.Image { IImageArray array = _context.Renderer.CreateImageArray(arrayLength, bindingInfo.Target == Target.TextureBuffer); - _cache.Add(key, entry = new CacheEntry(ref key, array, texturePool, samplerPool)); + _cacheFromBuffer.Add(key, entry = new CacheEntryFromBuffer(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)); + _cacheFromBuffer.Add(key, entry = new CacheEntryFromBuffer(ref key, array, texturePool, samplerPool)); } } @@ -716,15 +1069,52 @@ namespace Ryujinx.Graphics.Gpu.Image /// </summary> private void RemoveLeastUsedEntries() { - LinkedListNode<CacheEntry> nextNode = _lruCache.First; + LinkedListNode<CacheEntryFromBuffer> nextNode = _lruCache.First; while (nextNode != null && _currentTimestamp - nextNode.Value.CacheTimestamp >= MinDeltaForRemoval) { - LinkedListNode<CacheEntry> toRemove = nextNode; + LinkedListNode<CacheEntryFromBuffer> toRemove = nextNode; nextNode = nextNode.Next; - _cache.Remove(toRemove.Value.Key); + _cacheFromBuffer.Remove(toRemove.Value.Key); _lruCache.Remove(toRemove); } } + + /// <summary> + /// Removes all cached texture arrays matching the specified texture pool. + /// </summary> + /// <param name="pool">Texture pool</param> + public void RemoveAllWithPool<T>(IPool<T> pool) + { + List<CacheEntryFromPoolKey> keysToRemove = null; + + foreach (CacheEntryFromPoolKey key in _cacheFromPool.Keys) + { + if (key.MatchesPool(pool)) + { + (keysToRemove ??= new()).Add(key); + } + } + + if (keysToRemove != null) + { + foreach (CacheEntryFromPoolKey key in keysToRemove) + { + _cacheFromPool.Remove(key); + } + } + } + + /// <summary> + /// Checks if a handle indicates the binding should have all its textures sourced directly from a pool. + /// </summary> + /// <param name="handle">Handle to check</param> + /// <returns>True if the handle represents direct pool access, false otherwise</returns> + private static bool IsDirectHandleType(int handle) + { + (_, _, TextureHandleType type) = TextureHandle.UnpackOffsets(handle); + + return type == TextureHandleType.Direct; + } } } diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs index a1dde673..9f1f60d9 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs @@ -34,7 +34,7 @@ namespace Ryujinx.Graphics.Gpu.Image private readonly TexturePoolCache _texturePoolCache; private readonly SamplerPoolCache _samplerPoolCache; - private readonly TextureBindingsArrayCache _arrayBindingsCache; + private readonly TextureBindingsArrayCache _bindingsArrayCache; private TexturePool _cachedTexturePool; private SamplerPool _cachedSamplerPool; @@ -72,12 +72,14 @@ namespace Ryujinx.Graphics.Gpu.Image /// </summary> /// <param name="context">The GPU context that the texture bindings manager belongs to</param> /// <param name="channel">The GPU channel that the texture bindings manager belongs to</param> + /// <param name="bindingsArrayCache">Cache of texture array bindings</param> /// <param name="texturePoolCache">Texture pools cache used to get texture pools from</param> /// <param name="samplerPoolCache">Sampler pools cache used to get sampler pools from</param> /// <param name="isCompute">True if the bindings manager is used for the compute engine</param> public TextureBindingsManager( GpuContext context, GpuChannel channel, + TextureBindingsArrayCache bindingsArrayCache, TexturePoolCache texturePoolCache, SamplerPoolCache samplerPoolCache, bool isCompute) @@ -89,7 +91,7 @@ namespace Ryujinx.Graphics.Gpu.Image _isCompute = isCompute; - _arrayBindingsCache = new TextureBindingsArrayCache(context, channel, isCompute); + _bindingsArrayCache = bindingsArrayCache; int stages = isCompute ? 1 : Constants.ShaderStages; @@ -456,7 +458,7 @@ namespace Ryujinx.Graphics.Gpu.Image if (bindingInfo.ArrayLength > 1) { - _arrayBindingsCache.UpdateTextureArray(texturePool, samplerPool, stage, stageIndex, _textureBufferIndex, _samplerIndex, bindingInfo); + _bindingsArrayCache.UpdateTextureArray(texturePool, samplerPool, stage, stageIndex, _textureBufferIndex, _samplerIndex, bindingInfo); continue; } @@ -594,7 +596,7 @@ namespace Ryujinx.Graphics.Gpu.Image if (bindingInfo.ArrayLength > 1) { - _arrayBindingsCache.UpdateImageArray(pool, stage, stageIndex, _textureBufferIndex, bindingInfo); + _bindingsArrayCache.UpdateImageArray(pool, stage, stageIndex, _textureBufferIndex, bindingInfo); continue; } @@ -732,7 +734,7 @@ namespace Ryujinx.Graphics.Gpu.Image ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa); - TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId); + TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId, _bindingsArrayCache); TextureDescriptor descriptor; @@ -828,7 +830,7 @@ namespace Ryujinx.Graphics.Gpu.Image if (poolAddress != MemoryManager.PteUnmapped) { - texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, _texturePoolMaximumId); + texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, _texturePoolMaximumId, _bindingsArrayCache); _texturePool = texturePool; } } @@ -839,7 +841,7 @@ namespace Ryujinx.Graphics.Gpu.Image if (poolAddress != MemoryManager.PteUnmapped) { - samplerPool = _samplerPoolCache.FindOrCreate(_channel, poolAddress, _samplerPoolMaximumId); + samplerPool = _samplerPoolCache.FindOrCreate(_channel, poolAddress, _samplerPoolMaximumId, _bindingsArrayCache); _samplerPool = samplerPool; } } diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs index 8c2a8872..db292146 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs @@ -15,6 +15,7 @@ namespace Ryujinx.Graphics.Gpu.Image private readonly TextureBindingsManager _cpBindingsManager; private readonly TextureBindingsManager _gpBindingsManager; + private readonly TextureBindingsArrayCache _bindingsArrayCache; private readonly TexturePoolCache _texturePoolCache; private readonly SamplerPoolCache _samplerPoolCache; @@ -46,8 +47,9 @@ namespace Ryujinx.Graphics.Gpu.Image TexturePoolCache texturePoolCache = new(context); SamplerPoolCache samplerPoolCache = new(context); - _cpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, samplerPoolCache, isCompute: true); - _gpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, samplerPoolCache, isCompute: false); + _bindingsArrayCache = new TextureBindingsArrayCache(context, channel); + _cpBindingsManager = new TextureBindingsManager(context, channel, _bindingsArrayCache, texturePoolCache, samplerPoolCache, isCompute: true); + _gpBindingsManager = new TextureBindingsManager(context, channel, _bindingsArrayCache, texturePoolCache, samplerPoolCache, isCompute: false); _texturePoolCache = texturePoolCache; _samplerPoolCache = samplerPoolCache; @@ -384,7 +386,7 @@ namespace Ryujinx.Graphics.Gpu.Image { ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa); - TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId); + TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId, _bindingsArrayCache); return texturePool; } diff --git a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs index 6e36753e..a80dcbc8 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs @@ -58,7 +58,7 @@ namespace Ryujinx.Graphics.Gpu.Shader TextureBindings[i] = stage.Info.Textures.Select(descriptor => { - Target target = ShaderTexture.GetTarget(descriptor.Type); + Target target = descriptor.Type != SamplerType.None ? ShaderTexture.GetTarget(descriptor.Type) : default; var result = new TextureBindingInfo( target, @@ -66,7 +66,8 @@ namespace Ryujinx.Graphics.Gpu.Shader descriptor.ArrayLength, descriptor.CbufSlot, descriptor.HandleIndex, - descriptor.Flags); + descriptor.Flags, + descriptor.Type == SamplerType.None); if (descriptor.ArrayLength <= 1) { diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs index 681838a9..45f32e2d 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs @@ -110,6 +110,13 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache } /// <inheritdoc/> + /// <exception cref="DiskCacheLoadException">Pool length is not available on the cache</exception> + public int QuerySamplerArrayLengthFromPool() + { + return QueryArrayLengthFromPool(isSampler: true); + } + + /// <inheritdoc/> public SamplerType QuerySamplerType(int handle, int cbufSlot) { _newSpecState.RecordTextureSamplerType(_stageIndex, handle, cbufSlot); @@ -117,6 +124,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache } /// <inheritdoc/> + /// <exception cref="DiskCacheLoadException">Constant buffer derived length is not available on the cache</exception> public int QueryTextureArrayLengthFromBuffer(int slot) { if (!_oldSpecState.TextureArrayFromBufferRegistered(_stageIndex, 0, slot)) @@ -131,6 +139,13 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache } /// <inheritdoc/> + /// <exception cref="DiskCacheLoadException">Pool length is not available on the cache</exception> + public int QueryTextureArrayLengthFromPool() + { + return QueryArrayLengthFromPool(isSampler: false); + } + + /// <inheritdoc/> public TextureFormat QueryTextureFormat(int handle, int cbufSlot) { _newSpecState.RecordTextureFormat(_stageIndex, handle, cbufSlot); @@ -170,6 +185,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache } /// <inheritdoc/> + /// <exception cref="DiskCacheLoadException">Texture information is not available on the cache</exception> public void RegisterTexture(int handle, int cbufSlot) { if (!_oldSpecState.TextureRegistered(_stageIndex, handle, cbufSlot)) @@ -182,5 +198,24 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache bool coordNormalized = _oldSpecState.GetCoordNormalized(_stageIndex, handle, cbufSlot); _newSpecState.RegisterTexture(_stageIndex, handle, cbufSlot, format, formatSrgb, target, coordNormalized); } + + /// <summary> + /// Gets the cached texture or sampler pool capacity. + /// </summary> + /// <param name="isSampler">True to get sampler pool length, false for texture pool length</param> + /// <returns>Pool length</returns> + /// <exception cref="DiskCacheLoadException">Pool length is not available on the cache</exception> + private int QueryArrayLengthFromPool(bool isSampler) + { + if (!_oldSpecState.TextureArrayFromPoolRegistered(isSampler)) + { + throw new DiskCacheLoadException(DiskCacheLoadResult.MissingTextureArrayLength); + } + + int arrayLength = _oldSpecState.GetTextureArrayFromPoolLength(isSampler); + _newSpecState.RegisterTextureArrayLengthFromPool(isSampler, arrayLength); + + return arrayLength; + } } } diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs index b6a277a2..2c19cc4b 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 = 6489; + private const uint CodeGenVersion = 6577; private const string SharedTocFileName = "shared.toc"; private const string SharedDataFileName = "shared.data"; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs index 1d22ab93..04949690 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs @@ -121,6 +121,15 @@ namespace Ryujinx.Graphics.Gpu.Shader } /// <inheritdoc/> + public int QuerySamplerArrayLengthFromPool() + { + int length = _state.SamplerPoolMaximumId + 1; + _state.SpecializationState?.RegisterTextureArrayLengthFromPool(isSampler: true, length); + + return length; + } + + /// <inheritdoc/> public SamplerType QuerySamplerType(int handle, int cbufSlot) { _state.SpecializationState?.RecordTextureSamplerType(_stageIndex, handle, cbufSlot); @@ -141,6 +150,15 @@ namespace Ryujinx.Graphics.Gpu.Shader return arrayLength; } + /// <inheritdoc/> + public int QueryTextureArrayLengthFromPool() + { + int length = _state.PoolState.TexturePoolMaximumId + 1; + _state.SpecializationState?.RegisterTextureArrayLengthFromPool(isSampler: false, length); + + return length; + } + //// <inheritdoc/> public TextureFormat QueryTextureFormat(int handle, int cbufSlot) { diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs index 06e5edf1..0d562b0d 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs @@ -213,6 +213,8 @@ namespace Ryujinx.Graphics.Gpu.Shader public bool QueryHostSupportsScaledVertexFormats() => _context.Capabilities.SupportsScaledVertexFormats; + public bool QueryHostSupportsSeparateSampler() => _context.Capabilities.SupportsSeparateSampler; + public bool QueryHostSupportsShaderBallot() => _context.Capabilities.SupportsShaderBallot; public bool QueryHostSupportsShaderBarrierDivergence() => _context.Capabilities.SupportsShaderBarrierDivergence; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorState.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorState.cs index cfc4a2cc..808bf185 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorState.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorState.cs @@ -6,6 +6,11 @@ namespace Ryujinx.Graphics.Gpu.Shader class GpuAccessorState { /// <summary> + /// Maximum ID that a sampler pool entry may have. + /// </summary> + public readonly int SamplerPoolMaximumId; + + /// <summary> /// GPU texture pool state. /// </summary> public readonly GpuChannelPoolState PoolState; @@ -38,18 +43,21 @@ namespace Ryujinx.Graphics.Gpu.Shader /// <summary> /// Creates a new GPU accessor state. /// </summary> + /// <param name="samplerPoolMaximumId">Maximum ID that a sampler pool entry may have</param> /// <param name="poolState">GPU texture pool state</param> /// <param name="computeState">GPU compute state, for compute shaders</param> /// <param name="graphicsState">GPU graphics state, for vertex, tessellation, geometry and fragment shaders</param> /// <param name="specializationState">Shader specialization state (shared by all stages)</param> /// <param name="transformFeedbackDescriptors">Transform feedback information, if the shader uses transform feedback. Otherwise, should be null</param> public GpuAccessorState( + int samplerPoolMaximumId, GpuChannelPoolState poolState, GpuChannelComputeState computeState, GpuChannelGraphicsState graphicsState, ShaderSpecializationState specializationState, TransformFeedbackDescriptor[] transformFeedbackDescriptors = null) { + SamplerPoolMaximumId = samplerPoolMaximumId; PoolState = poolState; GraphicsState = graphicsState; ComputeState = computeState; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelPoolState.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelPoolState.cs index ddb45152..a2ab9933 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelPoolState.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelPoolState.cs @@ -2,7 +2,6 @@ using System; namespace Ryujinx.Graphics.Gpu.Shader { -#pragma warning disable CS0659 // Class overrides Object.Equals(object o) but does not override Object.GetHashCode() /// <summary> /// State used by the <see cref="GpuAccessor"/>. /// </summary> @@ -52,6 +51,10 @@ namespace Ryujinx.Graphics.Gpu.Shader { return obj is GpuChannelPoolState state && Equals(state); } + + public override int GetHashCode() + { + return HashCode.Combine(TexturePoolGpuVa, TexturePoolMaximumId, TextureBufferIndex); + } } -#pragma warning restore CS0659 } diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs index 0b17af8b..31cc94a2 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs @@ -192,12 +192,14 @@ namespace Ryujinx.Graphics.Gpu.Shader /// This automatically translates, compiles and adds the code to the cache if not present. /// </remarks> /// <param name="channel">GPU channel</param> + /// <param name="samplerPoolMaximumId">Maximum ID that an entry in the sampler pool may have</param> /// <param name="poolState">Texture pool state</param> /// <param name="computeState">Compute engine state</param> /// <param name="gpuVa">GPU virtual address of the binary shader code</param> /// <returns>Compiled compute shader code</returns> public CachedShaderProgram GetComputeShader( GpuChannel channel, + int samplerPoolMaximumId, GpuChannelPoolState poolState, GpuChannelComputeState computeState, ulong gpuVa) @@ -214,7 +216,7 @@ namespace Ryujinx.Graphics.Gpu.Shader } ShaderSpecializationState specState = new(ref computeState); - GpuAccessorState gpuAccessorState = new(poolState, computeState, default, specState); + GpuAccessorState gpuAccessorState = new(samplerPoolMaximumId, poolState, computeState, default, specState); GpuAccessor gpuAccessor = new(_context, channel, gpuAccessorState); gpuAccessor.InitializeReservedCounts(tfEnabled: false, vertexAsCompute: false); @@ -291,6 +293,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// <param name="state">GPU state</param> /// <param name="pipeline">Pipeline state</param> /// <param name="channel">GPU channel</param> + /// <param name="samplerPoolMaximumId">Maximum ID that an entry in the sampler pool may have</param> /// <param name="poolState">Texture pool state</param> /// <param name="graphicsState">3D engine state</param> /// <param name="addresses">Addresses of the shaders for each stage</param> @@ -299,6 +302,7 @@ namespace Ryujinx.Graphics.Gpu.Shader ref ThreedClassState state, ref ProgramPipelineState pipeline, GpuChannel channel, + int samplerPoolMaximumId, ref GpuChannelPoolState poolState, ref GpuChannelGraphicsState graphicsState, ShaderAddresses addresses) @@ -319,7 +323,7 @@ namespace Ryujinx.Graphics.Gpu.Shader UpdatePipelineInfo(ref state, ref pipeline, graphicsState, channel); ShaderSpecializationState specState = new(ref graphicsState, ref pipeline, transformFeedbackDescriptors); - GpuAccessorState gpuAccessorState = new(poolState, default, graphicsState, specState, transformFeedbackDescriptors); + GpuAccessorState gpuAccessorState = new(samplerPoolMaximumId, poolState, default, graphicsState, specState, transformFeedbackDescriptors); ReadOnlySpan<ulong> addressesSpan = addresses.AsSpan(); diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs index ea8f164f..ed56db3b 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs @@ -185,11 +185,7 @@ namespace Ryujinx.Graphics.Gpu.Shader { 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); + ResourceType type = GetTextureResourceType(texture, isImage); _resourceDescriptors[setIndex].Add(new ResourceDescriptor(texture.Binding, texture.ArrayLength, type, stages)); } @@ -242,16 +238,38 @@ namespace Ryujinx.Graphics.Gpu.Shader { foreach (TextureDescriptor texture in textures) { - bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer; - - ResourceType type = isBuffer - ? (isImage ? ResourceType.BufferImage : ResourceType.BufferTexture) - : (isImage ? ResourceType.Image : ResourceType.TextureAndSampler); + ResourceType type = GetTextureResourceType(texture, isImage); _resourceUsages[setIndex].Add(new ResourceUsage(texture.Binding, texture.ArrayLength, type, stages)); } } + private static ResourceType GetTextureResourceType(TextureDescriptor texture, bool isImage) + { + bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer; + + if (isBuffer) + { + return isImage ? ResourceType.BufferImage : ResourceType.BufferTexture; + } + else if (isImage) + { + return ResourceType.Image; + } + else if (texture.Type == SamplerType.None) + { + return ResourceType.Sampler; + } + else if (texture.Separate) + { + return ResourceType.Texture; + } + else + { + return ResourceType.TextureAndSampler; + } + } + /// <summary> /// Creates a new shader information structure from the added information. /// </summary> diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs index c90a0b8f..98acb6f2 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs @@ -31,6 +31,7 @@ namespace Ryujinx.Graphics.Gpu.Shader PrimitiveTopology = 1 << 1, TransformFeedback = 1 << 3, TextureArrayFromBuffer = 1 << 4, + TextureArrayFromPool = 1 << 5, } private QueriedStateFlags _queriedState; @@ -154,7 +155,8 @@ namespace Ryujinx.Graphics.Gpu.Shader } private readonly Dictionary<TextureKey, Box<TextureSpecializationState>> _textureSpecialization; - private readonly Dictionary<TextureKey, int> _textureArraySpecialization; + private readonly Dictionary<TextureKey, int> _textureArrayFromBufferSpecialization; + private readonly Dictionary<bool, int> _textureArrayFromPoolSpecialization; private KeyValuePair<TextureKey, Box<TextureSpecializationState>>[] _allTextures; private Box<TextureSpecializationState>[][] _textureByBinding; private Box<TextureSpecializationState>[][] _imageByBinding; @@ -165,7 +167,8 @@ namespace Ryujinx.Graphics.Gpu.Shader private ShaderSpecializationState() { _textureSpecialization = new Dictionary<TextureKey, Box<TextureSpecializationState>>(); - _textureArraySpecialization = new Dictionary<TextureKey, int>(); + _textureArrayFromBufferSpecialization = new Dictionary<TextureKey, int>(); + _textureArrayFromPoolSpecialization = new Dictionary<bool, int>(); } /// <summary> @@ -327,7 +330,7 @@ namespace Ryujinx.Graphics.Gpu.Shader } /// <summary> - /// Indicates that the coordinate normalization state of a given texture was used during the shader translation process. + /// Registers the length of a texture array calculated from a constant buffer size. /// </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> @@ -335,11 +338,22 @@ namespace Ryujinx.Graphics.Gpu.Shader /// <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; + _textureArrayFromBufferSpecialization[new TextureKey(stageIndex, handle, cbufSlot)] = length; _queriedState |= QueriedStateFlags.TextureArrayFromBuffer; } /// <summary> + /// Registers the length of a texture array calculated from a texture or sampler pool capacity. + /// </summary> + /// <param name="isSampler">True for sampler pool, false for texture pool</param> + /// <param name="length">Number of elements in the texture array</param> + public void RegisterTextureArrayLengthFromPool(bool isSampler, int length) + { + _textureArrayFromPoolSpecialization[isSampler] = length; + _queriedState |= QueriedStateFlags.TextureArrayFromPool; + } + + /// <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> @@ -385,7 +399,7 @@ namespace Ryujinx.Graphics.Gpu.Shader } /// <summary> - /// Checks if a given texture was registerd on this specialization state. + /// Checks if a given texture was registered 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> @@ -396,14 +410,25 @@ namespace Ryujinx.Graphics.Gpu.Shader } /// <summary> - /// Checks if a given texture array (from constant buffer) was registerd on this specialization state. + /// Checks if a given texture array (from constant buffer) was registered 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> + /// <returns>True if the length for the given buffer and stage exists, false otherwise</returns> public bool TextureArrayFromBufferRegistered(int stageIndex, int handle, int cbufSlot) { - return _textureArraySpecialization.ContainsKey(new TextureKey(stageIndex, handle, cbufSlot)); + return _textureArrayFromBufferSpecialization.ContainsKey(new TextureKey(stageIndex, handle, cbufSlot)); + } + + /// <summary> + /// Checks if a given texture array (from a sampler pool or texture pool) was registered on this specialization state. + /// </summary> + /// <param name="isSampler">True for sampler pool, false for texture pool</param> + /// <returns>True if the length for the given pool, false otherwise</returns> + public bool TextureArrayFromPoolRegistered(bool isSampler) + { + return _textureArrayFromPoolSpecialization.ContainsKey(isSampler); } /// <summary> @@ -412,6 +437,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// <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> + /// <returns>Format and sRGB tuple</returns> public (uint, bool) GetFormat(int stageIndex, int handle, int cbufSlot) { TextureSpecializationState state = GetTextureSpecState(stageIndex, handle, cbufSlot).Value; @@ -424,6 +450,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// <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> + /// <returns>Texture target</returns> public TextureTarget GetTextureTarget(int stageIndex, int handle, int cbufSlot) { return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.TextureTarget; @@ -435,6 +462,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// <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> + /// <returns>True if coordinates are normalized, false otherwise</returns> public bool GetCoordNormalized(int stageIndex, int handle, int cbufSlot) { return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.CoordNormalized; @@ -446,9 +474,20 @@ namespace Ryujinx.Graphics.Gpu.Shader /// <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> + /// <returns>Texture array length</returns> public int GetTextureArrayFromBufferLength(int stageIndex, int handle, int cbufSlot) { - return _textureArraySpecialization[new TextureKey(stageIndex, handle, cbufSlot)]; + return _textureArrayFromBufferSpecialization[new TextureKey(stageIndex, handle, cbufSlot)]; + } + + /// <summary> + /// Gets the recorded length of a given texture array (from a sampler or texture pool). + /// </summary> + /// <param name="isSampler">True to get the sampler pool length, false to get the texture pool length</param> + /// <returns>Texture array length</returns> + public int GetTextureArrayFromPoolLength(bool isSampler) + { + return _textureArrayFromPoolSpecialization[isSampler]; } /// <summary> @@ -894,7 +933,23 @@ namespace Ryujinx.Graphics.Gpu.Shader dataReader.ReadWithMagicAndSize(ref textureKey, TexkMagic); dataReader.Read(ref length); - specState._textureArraySpecialization[textureKey] = length; + specState._textureArrayFromBufferSpecialization[textureKey] = length; + } + } + + if (specState._queriedState.HasFlag(QueriedStateFlags.TextureArrayFromPool)) + { + dataReader.Read(ref count); + + for (int index = 0; index < count; index++) + { + bool textureKey = default; + int length = 0; + + dataReader.ReadWithMagicAndSize(ref textureKey, TexkMagic); + dataReader.Read(ref length); + + specState._textureArrayFromPoolSpecialization[textureKey] = length; } } @@ -965,10 +1020,25 @@ namespace Ryujinx.Graphics.Gpu.Shader if (_queriedState.HasFlag(QueriedStateFlags.TextureArrayFromBuffer)) { - count = (ushort)_textureArraySpecialization.Count; + count = (ushort)_textureArrayFromBufferSpecialization.Count; + dataWriter.Write(ref count); + + foreach (var kv in _textureArrayFromBufferSpecialization) + { + var textureKey = kv.Key; + var length = kv.Value; + + dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic); + dataWriter.Write(ref length); + } + } + + if (_queriedState.HasFlag(QueriedStateFlags.TextureArrayFromPool)) + { + count = (ushort)_textureArrayFromPoolSpecialization.Count; dataWriter.Write(ref count); - foreach (var kv in _textureArraySpecialization) + foreach (var kv in _textureArrayFromPoolSpecialization) { var textureKey = kv.Key; var length = kv.Value; |
