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
This commit is contained in:
gdkchan 2024-04-22 15:05:55 -03:00 committed by GitHub
parent 9b94662b4b
commit c6f8bfed90
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 1091 additions and 311 deletions

View File

@ -36,6 +36,7 @@ namespace Ryujinx.Graphics.GAL
public readonly bool SupportsMismatchingViewFormat; public readonly bool SupportsMismatchingViewFormat;
public readonly bool SupportsCubemapView; public readonly bool SupportsCubemapView;
public readonly bool SupportsNonConstantTextureOffset; public readonly bool SupportsNonConstantTextureOffset;
public readonly bool SupportsSeparateSampler;
public readonly bool SupportsShaderBallot; public readonly bool SupportsShaderBallot;
public readonly bool SupportsShaderBarrierDivergence; public readonly bool SupportsShaderBarrierDivergence;
public readonly bool SupportsShaderFloat64; public readonly bool SupportsShaderFloat64;
@ -92,6 +93,7 @@ namespace Ryujinx.Graphics.GAL
bool supportsMismatchingViewFormat, bool supportsMismatchingViewFormat,
bool supportsCubemapView, bool supportsCubemapView,
bool supportsNonConstantTextureOffset, bool supportsNonConstantTextureOffset,
bool supportsSeparateSampler,
bool supportsShaderBallot, bool supportsShaderBallot,
bool supportsShaderBarrierDivergence, bool supportsShaderBarrierDivergence,
bool supportsShaderFloat64, bool supportsShaderFloat64,
@ -144,6 +146,7 @@ namespace Ryujinx.Graphics.GAL
SupportsMismatchingViewFormat = supportsMismatchingViewFormat; SupportsMismatchingViewFormat = supportsMismatchingViewFormat;
SupportsCubemapView = supportsCubemapView; SupportsCubemapView = supportsCubemapView;
SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset; SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset;
SupportsSeparateSampler = supportsSeparateSampler;
SupportsShaderBallot = supportsShaderBallot; SupportsShaderBallot = supportsShaderBallot;
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence; SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
SupportsShaderFloat64 = supportsShaderFloat64; SupportsShaderFloat64 = supportsShaderFloat64;

View File

@ -126,6 +126,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
ulong samplerPoolGpuVa = ((ulong)_state.State.SetTexSamplerPoolAOffsetUpper << 32) | _state.State.SetTexSamplerPoolB; ulong samplerPoolGpuVa = ((ulong)_state.State.SetTexSamplerPoolAOffsetUpper << 32) | _state.State.SetTexSamplerPoolB;
ulong texturePoolGpuVa = ((ulong)_state.State.SetTexHeaderPoolAOffsetUpper << 32) | _state.State.SetTexHeaderPoolB; ulong texturePoolGpuVa = ((ulong)_state.State.SetTexHeaderPoolAOffsetUpper << 32) | _state.State.SetTexHeaderPoolB;
int samplerPoolMaximumId = _state.State.SetTexSamplerPoolCMaximumIndex;
GpuChannelPoolState poolState = new( GpuChannelPoolState poolState = new(
texturePoolGpuVa, texturePoolGpuVa,
_state.State.SetTexHeaderPoolCMaximumIndex, _state.State.SetTexHeaderPoolCMaximumIndex,
@ -139,7 +141,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
sharedMemorySize, sharedMemorySize,
_channel.BufferManager.HasUnalignedStorageBuffers); _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); _context.Renderer.Pipeline.SetProgram(cs.HostProgram);
@ -184,7 +186,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
sharedMemorySize, sharedMemorySize,
_channel.BufferManager.HasUnalignedStorageBuffers); _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); _context.Renderer.Pipeline.SetProgram(cs.HostProgram);
} }

View File

@ -1429,7 +1429,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
addressesSpan[index] = baseAddress + shader.Offset; 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. // Consume the modified flag for spec state so that it isn't checked again.
_currentSpecState.SetShader(gs); _currentSpecState.SetShader(gs);

View File

@ -62,8 +62,9 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="channel">GPU channel that the texture pool cache belongs to</param> /// <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="address">Start address of the texture pool</param>
/// <param name="maximumId">Maximum ID 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> /// <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. // Remove old entries from the cache, if possible.
while (_pools.Count > MaxCapacity && (_currentTimestamp - _pools.First.Value.CacheTimestamp) >= MinDeltaForRemoval) while (_pools.Count > MaxCapacity && (_currentTimestamp - _pools.First.Value.CacheTimestamp) >= MinDeltaForRemoval)
@ -73,6 +74,7 @@ namespace Ryujinx.Graphics.Gpu.Image
_pools.RemoveFirst(); _pools.RemoveFirst();
oldestPool.Dispose(); oldestPool.Dispose();
oldestPool.CacheNode = null; oldestPool.CacheNode = null;
bindingsArrayCache.RemoveAllWithPool(oldestPool);
} }
T pool; T pool;
@ -87,8 +89,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (pool.CacheNode != _pools.Last) if (pool.CacheNode != _pools.Last)
{ {
_pools.Remove(pool.CacheNode); _pools.Remove(pool.CacheNode);
_pools.AddLast(pool.CacheNode);
pool.CacheNode = _pools.AddLast(pool);
} }
pool.CacheTimestamp = _currentTimestamp; pool.CacheTimestamp = _currentTimestamp;

View File

@ -44,6 +44,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
public TextureUsageFlags Flags { get; } public TextureUsageFlags Flags { get; }
/// <summary>
/// Indicates that the binding is for a sampler.
/// </summary>
public bool IsSamplerOnly { get; }
/// <summary> /// <summary>
/// Constructs the texture binding information structure. /// Constructs the texture binding information structure.
/// </summary> /// </summary>
@ -74,8 +79,17 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="cbufSlot">Constant buffer slot where the texture handle is located</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="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> /// <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;
} }
} }
} }

View File

@ -21,12 +21,98 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly GpuContext _context; private readonly GpuContext _context;
private readonly GpuChannel _channel; private readonly GpuChannel _channel;
private readonly bool _isCompute;
/// <summary> /// <summary>
/// Array cache entry key. /// Array cache entry key.
/// </summary> /// </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> /// <summary>
/// Whether the entry is for an image. /// 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="texturePool">Texture pool where the array textures are located</param>
/// <param name="samplerPool">Sampler pool where the array samplers 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> /// <param name="textureBufferBounds">Constant buffer bounds with the texture handles</param>
public CacheEntryKey( public CacheEntryFromBufferKey(
bool isImage, bool isImage,
TextureBindingInfo bindingInfo, TextureBindingInfo bindingInfo,
TexturePool texturePool, TexturePool texturePool,
@ -100,7 +186,7 @@ namespace Ryujinx.Graphics.Gpu.Image
return _textureBufferBounds.Equals(textureBufferBounds); return _textureBufferBounds.Equals(textureBufferBounds);
} }
public bool Equals(CacheEntryKey other) public bool Equals(CacheEntryFromBufferKey other)
{ {
return IsImage == other.IsImage && return IsImage == other.IsImage &&
Target == other.Target && Target == other.Target &&
@ -112,7 +198,7 @@ namespace Ryujinx.Graphics.Gpu.Image
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
return obj is CacheEntryKey other && Equals(other); return obj is CacheEntryFromBufferKey other && Equals(other);
} }
public override int GetHashCode() public override int GetHashCode()
@ -122,40 +208,15 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
/// <summary> /// <summary>
/// Array cache entry. /// Array cache entry from pool.
/// </summary> /// </summary>
private class CacheEntry 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> /// <summary>
/// All cached textures, along with their invalidated sequence number as value. /// All cached textures, along with their invalidated sequence number as value.
/// </summary> /// </summary>
public readonly Dictionary<Texture, int> Textures; 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> /// <summary>
/// Backend texture array if the entry is for a texture, otherwise null. /// Backend texture array if the entry is for a texture, otherwise null.
/// </summary> /// </summary>
@ -166,44 +227,39 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
public readonly IImageArray ImageArray; public readonly IImageArray ImageArray;
private readonly TexturePool _texturePool; /// <summary>
private readonly SamplerPool _samplerPool; /// Texture pool where the array textures are located.
/// </summary>
protected readonly TexturePool TexturePool;
/// <summary>
/// Sampler pool where the array samplers are located.
/// </summary>
protected readonly SamplerPool SamplerPool;
private int _texturePoolSequence; private int _texturePoolSequence;
private int _samplerPoolSequence; private int _samplerPoolSequence;
private int[] _cachedTextureBuffer;
private int[] _cachedSamplerBuffer;
private int _lastSequenceNumber;
/// <summary> /// <summary>
/// Creates a new array cache entry. /// Creates a new array cache entry.
/// </summary> /// </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="texturePool">Texture pool where the array textures are located</param>
/// <param name="samplerPool">Sampler pool where the array samplers 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>(); Textures = new Dictionary<Texture, int>();
TextureIds = new Dictionary<int, Texture>();
SamplerIds = new Dictionary<int, Sampler>();
_texturePool = texturePool; TexturePool = texturePool;
_samplerPool = samplerPool; SamplerPool = samplerPool;
_lastSequenceNumber = -1;
} }
/// <summary> /// <summary>
/// Creates a new array cache entry. /// Creates a new array cache entry.
/// </summary> /// </summary>
/// <param name="key">Key for this entry on the cache</param>
/// <param name="array">Backend texture array</param> /// <param name="array">Backend texture array</param>
/// <param name="texturePool">Texture pool where the array textures are located</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="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; TextureArray = array;
} }
@ -211,11 +267,10 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <summary> /// <summary>
/// Creates a new array cache entry. /// Creates a new array cache entry.
/// </summary> /// </summary>
/// <param name="key">Key for this entry on the cache</param>
/// <param name="array">Backend image array</param> /// <param name="array">Backend image array</param>
/// <param name="texturePool">Texture pool where the array textures are located</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="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; ImageArray = array;
} }
@ -248,23 +303,9 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <summary> /// <summary>
/// Clears all cached texture instances. /// Clears all cached texture instances.
/// </summary> /// </summary>
public void Reset() public virtual void Reset()
{ {
Textures.Clear(); 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> /// <summary>
@ -287,39 +328,105 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <summary> /// <summary>
/// Checks if the cached texture or sampler pool has been modified since the last call to this method. /// Checks if the cached texture or sampler pool has been modified since the last call to this method.
/// </summary> /// </summary>
/// <returns>True if any used entries of the pools might have been modified, false otherwise</returns> /// <returns>True if any used entries of the pool might have been modified, false otherwise</returns>
public bool PoolsModified() public bool TexturePoolModified()
{ {
bool texturePoolModified = _texturePool.WasModified(ref _texturePoolSequence); return 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. /// <summary>
/// Checks if the cached texture or sampler pool has been modified since the last call to this method.
// Check if any of our cached textures changed on the pool. /// </summary>
foreach ((int textureId, Texture texture) in TextureIds) /// <returns>True if any used entries of the pool might have been modified, false otherwise</returns>
public bool SamplerPoolModified()
{ {
if (_texturePool.GetCachedItem(textureId) != texture) return SamplerPool.WasModified(ref _samplerPoolSequence);
{
return true;
} }
} }
// Check if any of our cached samplers changed on the pool. /// <summary>
foreach ((int samplerId, Sampler sampler) in SamplerIds) /// Array cache entry from constant buffer.
/// </summary>
private class CacheEntryFromBuffer : CacheEntry
{ {
if (_samplerPool.GetCachedItem(samplerId) != sampler) /// <summary>
/// Key for this entry on the cache.
/// </summary>
public readonly CacheEntryFromBufferKey Key;
/// <summary>
/// Linked list node used on the texture bindings array cache.
/// </summary>
public LinkedListNode<CacheEntryFromBuffer> CacheNode;
/// <summary>
/// Timestamp set on the last use of the array by the cache.
/// </summary>
public int CacheTimestamp;
/// <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)
{ {
return true; Key = key;
} _lastSequenceNumber = -1;
TextureIds = new Dictionary<int, (Texture, TextureDescriptor)>();
SamplerIds = new Dictionary<int, (Sampler, SamplerDescriptor)>();
} }
return false; /// <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> /// <summary>
@ -380,10 +487,51 @@ namespace Ryujinx.Graphics.Gpu.Image
return true; 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;
} }
private readonly Dictionary<CacheEntryKey, CacheEntry> _cache; // If the pools were modified, let's check if any of the entries we care about changed.
private readonly LinkedList<CacheEntry> _lruCache;
// 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<CacheEntryFromBufferKey, CacheEntryFromBuffer> _cacheFromBuffer;
private readonly Dictionary<CacheEntryFromPoolKey, CacheEntry> _cacheFromPool;
private readonly LinkedList<CacheEntryFromBuffer> _lruCache;
private int _currentTimestamp; private int _currentTimestamp;
@ -392,14 +540,13 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
/// <param name="context">GPU context</param> /// <param name="context">GPU context</param>
/// <param name="channel">GPU channel</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)
public TextureBindingsArrayCache(GpuContext context, GpuChannel channel, bool isCompute)
{ {
_context = context; _context = context;
_channel = channel; _channel = channel;
_isCompute = isCompute; _cacheFromBuffer = new Dictionary<CacheEntryFromBufferKey, CacheEntryFromBuffer>();
_cache = new Dictionary<CacheEntryKey, CacheEntry>(); _cacheFromPool = new Dictionary<CacheEntryFromPoolKey, CacheEntry>();
_lruCache = new LinkedList<CacheEntry>(); _lruCache = new LinkedList<CacheEntryFromBuffer>();
} }
/// <summary> /// <summary>
@ -457,15 +604,180 @@ namespace Ryujinx.Graphics.Gpu.Image
bool isImage, bool isImage,
SamplerIndex samplerIndex, SamplerIndex samplerIndex,
TextureBindingInfo bindingInfo) 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); (textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, textureBufferIndex);
bool separateSamplerBuffer = textureBufferIndex != samplerBufferIndex; bool separateSamplerBuffer = textureBufferIndex != samplerBufferIndex;
bool isCompute = stage == ShaderStage.Compute;
ref BufferBounds textureBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, textureBufferIndex); ref BufferBounds textureBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, textureBufferIndex);
ref BufferBounds samplerBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, samplerBufferIndex); ref BufferBounds samplerBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, samplerBufferIndex);
CacheEntry entry = GetOrAddEntry( CacheEntryFromBuffer entry = GetOrAddEntry(
texturePool, texturePool,
samplerPool, samplerPool,
bindingInfo, bindingInfo,
@ -589,8 +901,8 @@ namespace Ryujinx.Graphics.Gpu.Image
Sampler sampler = samplerPool?.Get(samplerId); Sampler sampler = samplerPool?.Get(samplerId);
entry.TextureIds[textureId] = texture; entry.TextureIds[textureId] = (texture, descriptor);
entry.SamplerIds[samplerId] = sampler; entry.SamplerIds[samplerId] = (sampler, samplerPool?.GetDescriptorRef(samplerId) ?? default);
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target); ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
ISampler hostSampler = sampler?.GetHostSampler(texture); ISampler hostSampler = sampler?.GetHostSampler(texture);
@ -650,13 +962,12 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
/// <summary> /// <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> /// </summary>
/// <param name="texturePool">Texture pool</param> /// <param name="texturePool">Texture pool</param>
/// <param name="samplerPool">Sampler pool</param> /// <param name="samplerPool">Sampler pool</param>
/// <param name="bindingInfo">Array binding information</param> /// <param name="bindingInfo">Array binding information</param>
/// <param name="isImage">Whether the array is a image or texture array</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> /// <param name="isNew">Whether a new entry was created, or an existing one was returned</param>
/// <returns>Cache entry</returns> /// <returns>Cache entry</returns>
private CacheEntry GetOrAddEntry( private CacheEntry GetOrAddEntry(
@ -664,17 +975,11 @@ namespace Ryujinx.Graphics.Gpu.Image
SamplerPool samplerPool, SamplerPool samplerPool,
TextureBindingInfo bindingInfo, TextureBindingInfo bindingInfo,
bool isImage, bool isImage,
ref BufferBounds textureBufferBounds,
out bool isNew) out bool isNew)
{ {
CacheEntryKey key = new CacheEntryKey( CacheEntryFromPoolKey key = new CacheEntryFromPoolKey(isImage, bindingInfo, texturePool, samplerPool);
isImage,
bindingInfo,
texturePool,
samplerPool,
ref textureBufferBounds);
isNew = !_cache.TryGetValue(key, out CacheEntry entry); isNew = !_cacheFromPool.TryGetValue(key, out CacheEntry entry);
if (isNew) if (isNew)
{ {
@ -684,13 +989,61 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
IImageArray array = _context.Renderer.CreateImageArray(arrayLength, bindingInfo.Target == Target.TextureBuffer); IImageArray array = _context.Renderer.CreateImageArray(arrayLength, bindingInfo.Target == Target.TextureBuffer);
_cache.Add(key, entry = new CacheEntry(ref key, array, texturePool, samplerPool)); _cacheFromPool.Add(key, entry = new CacheEntry(array, texturePool, samplerPool));
} }
else else
{ {
ITextureArray array = _context.Renderer.CreateTextureArray(arrayLength, bindingInfo.Target == Target.TextureBuffer); ITextureArray array = _context.Renderer.CreateTextureArray(arrayLength, bindingInfo.Target == Target.TextureBuffer);
_cache.Add(key, entry = new CacheEntry(ref key, array, texturePool, samplerPool)); _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)
{
CacheEntryFromBufferKey key = new CacheEntryFromBufferKey(
isImage,
bindingInfo,
texturePool,
samplerPool,
ref textureBufferBounds);
isNew = !_cacheFromBuffer.TryGetValue(key, out CacheEntryFromBuffer entry);
if (isNew)
{
int arrayLength = bindingInfo.ArrayLength;
if (isImage)
{
IImageArray array = _context.Renderer.CreateImageArray(arrayLength, bindingInfo.Target == Target.TextureBuffer);
_cacheFromBuffer.Add(key, entry = new CacheEntryFromBuffer(ref key, array, texturePool, samplerPool));
}
else
{
ITextureArray array = _context.Renderer.CreateTextureArray(arrayLength, bindingInfo.Target == Target.TextureBuffer);
_cacheFromBuffer.Add(key, entry = new CacheEntryFromBuffer(ref key, array, texturePool, samplerPool));
} }
} }
@ -716,15 +1069,52 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
private void RemoveLeastUsedEntries() private void RemoveLeastUsedEntries()
{ {
LinkedListNode<CacheEntry> nextNode = _lruCache.First; LinkedListNode<CacheEntryFromBuffer> nextNode = _lruCache.First;
while (nextNode != null && _currentTimestamp - nextNode.Value.CacheTimestamp >= MinDeltaForRemoval) while (nextNode != null && _currentTimestamp - nextNode.Value.CacheTimestamp >= MinDeltaForRemoval)
{ {
LinkedListNode<CacheEntry> toRemove = nextNode; LinkedListNode<CacheEntryFromBuffer> toRemove = nextNode;
nextNode = nextNode.Next; nextNode = nextNode.Next;
_cache.Remove(toRemove.Value.Key); _cacheFromBuffer.Remove(toRemove.Value.Key);
_lruCache.Remove(toRemove); _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;
}
} }
} }

View File

@ -34,7 +34,7 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly TexturePoolCache _texturePoolCache; private readonly TexturePoolCache _texturePoolCache;
private readonly SamplerPoolCache _samplerPoolCache; private readonly SamplerPoolCache _samplerPoolCache;
private readonly TextureBindingsArrayCache _arrayBindingsCache; private readonly TextureBindingsArrayCache _bindingsArrayCache;
private TexturePool _cachedTexturePool; private TexturePool _cachedTexturePool;
private SamplerPool _cachedSamplerPool; private SamplerPool _cachedSamplerPool;
@ -72,12 +72,14 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
/// <param name="context">The GPU context that the texture bindings manager belongs to</param> /// <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="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="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="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> /// <param name="isCompute">True if the bindings manager is used for the compute engine</param>
public TextureBindingsManager( public TextureBindingsManager(
GpuContext context, GpuContext context,
GpuChannel channel, GpuChannel channel,
TextureBindingsArrayCache bindingsArrayCache,
TexturePoolCache texturePoolCache, TexturePoolCache texturePoolCache,
SamplerPoolCache samplerPoolCache, SamplerPoolCache samplerPoolCache,
bool isCompute) bool isCompute)
@ -89,7 +91,7 @@ namespace Ryujinx.Graphics.Gpu.Image
_isCompute = isCompute; _isCompute = isCompute;
_arrayBindingsCache = new TextureBindingsArrayCache(context, channel, isCompute); _bindingsArrayCache = bindingsArrayCache;
int stages = isCompute ? 1 : Constants.ShaderStages; int stages = isCompute ? 1 : Constants.ShaderStages;
@ -456,7 +458,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (bindingInfo.ArrayLength > 1) if (bindingInfo.ArrayLength > 1)
{ {
_arrayBindingsCache.UpdateTextureArray(texturePool, samplerPool, stage, stageIndex, _textureBufferIndex, _samplerIndex, bindingInfo); _bindingsArrayCache.UpdateTextureArray(texturePool, samplerPool, stage, stageIndex, _textureBufferIndex, _samplerIndex, bindingInfo);
continue; continue;
} }
@ -594,7 +596,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (bindingInfo.ArrayLength > 1) if (bindingInfo.ArrayLength > 1)
{ {
_arrayBindingsCache.UpdateImageArray(pool, stage, stageIndex, _textureBufferIndex, bindingInfo); _bindingsArrayCache.UpdateImageArray(pool, stage, stageIndex, _textureBufferIndex, bindingInfo);
continue; continue;
} }
@ -732,7 +734,7 @@ namespace Ryujinx.Graphics.Gpu.Image
ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa); ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa);
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId); TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId, _bindingsArrayCache);
TextureDescriptor descriptor; TextureDescriptor descriptor;
@ -828,7 +830,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (poolAddress != MemoryManager.PteUnmapped) if (poolAddress != MemoryManager.PteUnmapped)
{ {
texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, _texturePoolMaximumId); texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, _texturePoolMaximumId, _bindingsArrayCache);
_texturePool = texturePool; _texturePool = texturePool;
} }
} }
@ -839,7 +841,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (poolAddress != MemoryManager.PteUnmapped) if (poolAddress != MemoryManager.PteUnmapped)
{ {
samplerPool = _samplerPoolCache.FindOrCreate(_channel, poolAddress, _samplerPoolMaximumId); samplerPool = _samplerPoolCache.FindOrCreate(_channel, poolAddress, _samplerPoolMaximumId, _bindingsArrayCache);
_samplerPool = samplerPool; _samplerPool = samplerPool;
} }
} }

View File

@ -15,6 +15,7 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly TextureBindingsManager _cpBindingsManager; private readonly TextureBindingsManager _cpBindingsManager;
private readonly TextureBindingsManager _gpBindingsManager; private readonly TextureBindingsManager _gpBindingsManager;
private readonly TextureBindingsArrayCache _bindingsArrayCache;
private readonly TexturePoolCache _texturePoolCache; private readonly TexturePoolCache _texturePoolCache;
private readonly SamplerPoolCache _samplerPoolCache; private readonly SamplerPoolCache _samplerPoolCache;
@ -46,8 +47,9 @@ namespace Ryujinx.Graphics.Gpu.Image
TexturePoolCache texturePoolCache = new(context); TexturePoolCache texturePoolCache = new(context);
SamplerPoolCache samplerPoolCache = new(context); SamplerPoolCache samplerPoolCache = new(context);
_cpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, samplerPoolCache, isCompute: true); _bindingsArrayCache = new TextureBindingsArrayCache(context, channel);
_gpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, samplerPoolCache, isCompute: false); _cpBindingsManager = new TextureBindingsManager(context, channel, _bindingsArrayCache, texturePoolCache, samplerPoolCache, isCompute: true);
_gpBindingsManager = new TextureBindingsManager(context, channel, _bindingsArrayCache, texturePoolCache, samplerPoolCache, isCompute: false);
_texturePoolCache = texturePoolCache; _texturePoolCache = texturePoolCache;
_samplerPoolCache = samplerPoolCache; _samplerPoolCache = samplerPoolCache;
@ -384,7 +386,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa); ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa);
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId); TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId, _bindingsArrayCache);
return texturePool; return texturePool;
} }

View File

@ -58,7 +58,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
TextureBindings[i] = stage.Info.Textures.Select(descriptor => 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( var result = new TextureBindingInfo(
target, target,
@ -66,7 +66,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
descriptor.ArrayLength, descriptor.ArrayLength,
descriptor.CbufSlot, descriptor.CbufSlot,
descriptor.HandleIndex, descriptor.HandleIndex,
descriptor.Flags); descriptor.Flags,
descriptor.Type == SamplerType.None);
if (descriptor.ArrayLength <= 1) if (descriptor.ArrayLength <= 1)
{ {

View File

@ -109,6 +109,13 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return _oldSpecState.GraphicsState.HasConstantBufferDrawParameters; return _oldSpecState.GraphicsState.HasConstantBufferDrawParameters;
} }
/// <inheritdoc/>
/// <exception cref="DiskCacheLoadException">Pool length is not available on the cache</exception>
public int QuerySamplerArrayLengthFromPool()
{
return QueryArrayLengthFromPool(isSampler: true);
}
/// <inheritdoc/> /// <inheritdoc/>
public SamplerType QuerySamplerType(int handle, int cbufSlot) public SamplerType QuerySamplerType(int handle, int cbufSlot)
{ {
@ -117,6 +124,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
} }
/// <inheritdoc/> /// <inheritdoc/>
/// <exception cref="DiskCacheLoadException">Constant buffer derived length is not available on the cache</exception>
public int QueryTextureArrayLengthFromBuffer(int slot) public int QueryTextureArrayLengthFromBuffer(int slot)
{ {
if (!_oldSpecState.TextureArrayFromBufferRegistered(_stageIndex, 0, slot)) if (!_oldSpecState.TextureArrayFromBufferRegistered(_stageIndex, 0, slot))
@ -130,6 +138,13 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return arrayLength; return arrayLength;
} }
/// <inheritdoc/>
/// <exception cref="DiskCacheLoadException">Pool length is not available on the cache</exception>
public int QueryTextureArrayLengthFromPool()
{
return QueryArrayLengthFromPool(isSampler: false);
}
/// <inheritdoc/> /// <inheritdoc/>
public TextureFormat QueryTextureFormat(int handle, int cbufSlot) public TextureFormat QueryTextureFormat(int handle, int cbufSlot)
{ {
@ -170,6 +185,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
} }
/// <inheritdoc/> /// <inheritdoc/>
/// <exception cref="DiskCacheLoadException">Texture information is not available on the cache</exception>
public void RegisterTexture(int handle, int cbufSlot) public void RegisterTexture(int handle, int cbufSlot)
{ {
if (!_oldSpecState.TextureRegistered(_stageIndex, handle, cbufSlot)) if (!_oldSpecState.TextureRegistered(_stageIndex, handle, cbufSlot))
@ -182,5 +198,24 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
bool coordNormalized = _oldSpecState.GetCoordNormalized(_stageIndex, handle, cbufSlot); bool coordNormalized = _oldSpecState.GetCoordNormalized(_stageIndex, handle, cbufSlot);
_newSpecState.RegisterTexture(_stageIndex, handle, cbufSlot, format, formatSrgb, target, coordNormalized); _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;
}
} }
} }

View File

@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2; private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; 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 SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data"; private const string SharedDataFileName = "shared.data";

View File

@ -120,6 +120,15 @@ namespace Ryujinx.Graphics.Gpu.Shader
return _state.GraphicsState.HasUnalignedStorageBuffer || _state.ComputeState.HasUnalignedStorageBuffer; return _state.GraphicsState.HasUnalignedStorageBuffer || _state.ComputeState.HasUnalignedStorageBuffer;
} }
/// <inheritdoc/>
public int QuerySamplerArrayLengthFromPool()
{
int length = _state.SamplerPoolMaximumId + 1;
_state.SpecializationState?.RegisterTextureArrayLengthFromPool(isSampler: true, length);
return length;
}
/// <inheritdoc/> /// <inheritdoc/>
public SamplerType QuerySamplerType(int handle, int cbufSlot) public SamplerType QuerySamplerType(int handle, int cbufSlot)
{ {
@ -141,6 +150,15 @@ namespace Ryujinx.Graphics.Gpu.Shader
return arrayLength; return arrayLength;
} }
/// <inheritdoc/>
public int QueryTextureArrayLengthFromPool()
{
int length = _state.PoolState.TexturePoolMaximumId + 1;
_state.SpecializationState?.RegisterTextureArrayLengthFromPool(isSampler: false, length);
return length;
}
//// <inheritdoc/> //// <inheritdoc/>
public TextureFormat QueryTextureFormat(int handle, int cbufSlot) public TextureFormat QueryTextureFormat(int handle, int cbufSlot)
{ {

View File

@ -213,6 +213,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
public bool QueryHostSupportsScaledVertexFormats() => _context.Capabilities.SupportsScaledVertexFormats; public bool QueryHostSupportsScaledVertexFormats() => _context.Capabilities.SupportsScaledVertexFormats;
public bool QueryHostSupportsSeparateSampler() => _context.Capabilities.SupportsSeparateSampler;
public bool QueryHostSupportsShaderBallot() => _context.Capabilities.SupportsShaderBallot; public bool QueryHostSupportsShaderBallot() => _context.Capabilities.SupportsShaderBallot;
public bool QueryHostSupportsShaderBarrierDivergence() => _context.Capabilities.SupportsShaderBarrierDivergence; public bool QueryHostSupportsShaderBarrierDivergence() => _context.Capabilities.SupportsShaderBarrierDivergence;

View File

@ -5,6 +5,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary> /// </summary>
class GpuAccessorState class GpuAccessorState
{ {
/// <summary>
/// Maximum ID that a sampler pool entry may have.
/// </summary>
public readonly int SamplerPoolMaximumId;
/// <summary> /// <summary>
/// GPU texture pool state. /// GPU texture pool state.
/// </summary> /// </summary>
@ -38,18 +43,21 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <summary> /// <summary>
/// Creates a new GPU accessor state. /// Creates a new GPU accessor state.
/// </summary> /// </summary>
/// <param name="samplerPoolMaximumId">Maximum ID that a sampler pool entry may have</param>
/// <param name="poolState">GPU texture pool state</param> /// <param name="poolState">GPU texture pool state</param>
/// <param name="computeState">GPU compute state, for compute shaders</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="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="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> /// <param name="transformFeedbackDescriptors">Transform feedback information, if the shader uses transform feedback. Otherwise, should be null</param>
public GpuAccessorState( public GpuAccessorState(
int samplerPoolMaximumId,
GpuChannelPoolState poolState, GpuChannelPoolState poolState,
GpuChannelComputeState computeState, GpuChannelComputeState computeState,
GpuChannelGraphicsState graphicsState, GpuChannelGraphicsState graphicsState,
ShaderSpecializationState specializationState, ShaderSpecializationState specializationState,
TransformFeedbackDescriptor[] transformFeedbackDescriptors = null) TransformFeedbackDescriptor[] transformFeedbackDescriptors = null)
{ {
SamplerPoolMaximumId = samplerPoolMaximumId;
PoolState = poolState; PoolState = poolState;
GraphicsState = graphicsState; GraphicsState = graphicsState;
ComputeState = computeState; ComputeState = computeState;

View File

@ -2,7 +2,6 @@ using System;
namespace Ryujinx.Graphics.Gpu.Shader namespace Ryujinx.Graphics.Gpu.Shader
{ {
#pragma warning disable CS0659 // Class overrides Object.Equals(object o) but does not override Object.GetHashCode()
/// <summary> /// <summary>
/// State used by the <see cref="GpuAccessor"/>. /// State used by the <see cref="GpuAccessor"/>.
/// </summary> /// </summary>
@ -52,6 +51,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
{ {
return obj is GpuChannelPoolState state && Equals(state); return obj is GpuChannelPoolState state && Equals(state);
} }
public override int GetHashCode()
{
return HashCode.Combine(TexturePoolGpuVa, TexturePoolMaximumId, TextureBufferIndex);
}
} }
#pragma warning restore CS0659
} }

View File

@ -192,12 +192,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// This automatically translates, compiles and adds the code to the cache if not present. /// This automatically translates, compiles and adds the code to the cache if not present.
/// </remarks> /// </remarks>
/// <param name="channel">GPU channel</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="poolState">Texture pool state</param>
/// <param name="computeState">Compute engine state</param> /// <param name="computeState">Compute engine state</param>
/// <param name="gpuVa">GPU virtual address of the binary shader code</param> /// <param name="gpuVa">GPU virtual address of the binary shader code</param>
/// <returns>Compiled compute shader code</returns> /// <returns>Compiled compute shader code</returns>
public CachedShaderProgram GetComputeShader( public CachedShaderProgram GetComputeShader(
GpuChannel channel, GpuChannel channel,
int samplerPoolMaximumId,
GpuChannelPoolState poolState, GpuChannelPoolState poolState,
GpuChannelComputeState computeState, GpuChannelComputeState computeState,
ulong gpuVa) ulong gpuVa)
@ -214,7 +216,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
} }
ShaderSpecializationState specState = new(ref computeState); 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 gpuAccessor = new(_context, channel, gpuAccessorState);
gpuAccessor.InitializeReservedCounts(tfEnabled: false, vertexAsCompute: false); gpuAccessor.InitializeReservedCounts(tfEnabled: false, vertexAsCompute: false);
@ -291,6 +293,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="state">GPU state</param> /// <param name="state">GPU state</param>
/// <param name="pipeline">Pipeline state</param> /// <param name="pipeline">Pipeline state</param>
/// <param name="channel">GPU channel</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="poolState">Texture pool state</param>
/// <param name="graphicsState">3D engine state</param> /// <param name="graphicsState">3D engine state</param>
/// <param name="addresses">Addresses of the shaders for each stage</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 ThreedClassState state,
ref ProgramPipelineState pipeline, ref ProgramPipelineState pipeline,
GpuChannel channel, GpuChannel channel,
int samplerPoolMaximumId,
ref GpuChannelPoolState poolState, ref GpuChannelPoolState poolState,
ref GpuChannelGraphicsState graphicsState, ref GpuChannelGraphicsState graphicsState,
ShaderAddresses addresses) ShaderAddresses addresses)
@ -319,7 +323,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
UpdatePipelineInfo(ref state, ref pipeline, graphicsState, channel); UpdatePipelineInfo(ref state, ref pipeline, graphicsState, channel);
ShaderSpecializationState specState = new(ref graphicsState, ref pipeline, transformFeedbackDescriptors); 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(); ReadOnlySpan<ulong> addressesSpan = addresses.AsSpan();

View File

@ -185,11 +185,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{ {
if (texture.ArrayLength > 1) if (texture.ArrayLength > 1)
{ {
bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer; ResourceType type = GetTextureResourceType(texture, isImage);
ResourceType type = isBuffer
? (isImage ? ResourceType.BufferImage : ResourceType.BufferTexture)
: (isImage ? ResourceType.Image : ResourceType.TextureAndSampler);
_resourceDescriptors[setIndex].Add(new ResourceDescriptor(texture.Binding, texture.ArrayLength, type, stages)); _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) foreach (TextureDescriptor texture in textures)
{ {
bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer; ResourceType type = GetTextureResourceType(texture, isImage);
ResourceType type = isBuffer
? (isImage ? ResourceType.BufferImage : ResourceType.BufferTexture)
: (isImage ? ResourceType.Image : ResourceType.TextureAndSampler);
_resourceUsages[setIndex].Add(new ResourceUsage(texture.Binding, texture.ArrayLength, type, stages)); _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> /// <summary>
/// Creates a new shader information structure from the added information. /// Creates a new shader information structure from the added information.
/// </summary> /// </summary>

View File

@ -31,6 +31,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
PrimitiveTopology = 1 << 1, PrimitiveTopology = 1 << 1,
TransformFeedback = 1 << 3, TransformFeedback = 1 << 3,
TextureArrayFromBuffer = 1 << 4, TextureArrayFromBuffer = 1 << 4,
TextureArrayFromPool = 1 << 5,
} }
private QueriedStateFlags _queriedState; private QueriedStateFlags _queriedState;
@ -154,7 +155,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
} }
private readonly Dictionary<TextureKey, Box<TextureSpecializationState>> _textureSpecialization; 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 KeyValuePair<TextureKey, Box<TextureSpecializationState>>[] _allTextures;
private Box<TextureSpecializationState>[][] _textureByBinding; private Box<TextureSpecializationState>[][] _textureByBinding;
private Box<TextureSpecializationState>[][] _imageByBinding; private Box<TextureSpecializationState>[][] _imageByBinding;
@ -165,7 +167,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
private ShaderSpecializationState() private ShaderSpecializationState()
{ {
_textureSpecialization = new Dictionary<TextureKey, Box<TextureSpecializationState>>(); _textureSpecialization = new Dictionary<TextureKey, Box<TextureSpecializationState>>();
_textureArraySpecialization = new Dictionary<TextureKey, int>(); _textureArrayFromBufferSpecialization = new Dictionary<TextureKey, int>();
_textureArrayFromPoolSpecialization = new Dictionary<bool, int>();
} }
/// <summary> /// <summary>
@ -327,7 +330,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
} }
/// <summary> /// <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> /// </summary>
/// <param name="stageIndex">Shader stage where the texture is used</param> /// <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="handle">Offset in words of the texture handle on the texture buffer</param>
@ -335,10 +338,21 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="length">Number of elements in the texture array</param> /// <param name="length">Number of elements in the texture array</param>
public void RegisterTextureArrayLengthFromBuffer(int stageIndex, int handle, int cbufSlot, int length) 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; _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> /// <summary>
/// Indicates that the format of a given texture was used during the shader translation process. /// Indicates that the format of a given texture was used during the shader translation process.
/// </summary> /// </summary>
@ -385,7 +399,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
} }
/// <summary> /// <summary>
/// Checks if a given texture was registerd on this specialization state. /// Checks if a given texture was registered on this specialization state.
/// </summary> /// </summary>
/// <param name="stageIndex">Shader stage where the texture is used</param> /// <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="handle">Offset in words of the texture handle on the texture buffer</param>
@ -396,14 +410,25 @@ namespace Ryujinx.Graphics.Gpu.Shader
} }
/// <summary> /// <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> /// </summary>
/// <param name="stageIndex">Shader stage where the texture is used</param> /// <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="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="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) 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> /// <summary>
@ -412,6 +437,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="stageIndex">Shader stage where the texture is used</param> /// <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="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="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) public (uint, bool) GetFormat(int stageIndex, int handle, int cbufSlot)
{ {
TextureSpecializationState state = GetTextureSpecState(stageIndex, handle, cbufSlot).Value; 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="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="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="cbufSlot">Slot of the texture buffer constant buffer</param>
/// <returns>Texture target</returns>
public TextureTarget GetTextureTarget(int stageIndex, int handle, int cbufSlot) public TextureTarget GetTextureTarget(int stageIndex, int handle, int cbufSlot)
{ {
return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.TextureTarget; 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="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="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="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) public bool GetCoordNormalized(int stageIndex, int handle, int cbufSlot)
{ {
return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.CoordNormalized; 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="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="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="cbufSlot">Slot of the texture buffer constant buffer</param>
/// <returns>Texture array length</returns>
public int GetTextureArrayFromBufferLength(int stageIndex, int handle, int cbufSlot) 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> /// <summary>
@ -894,7 +933,23 @@ namespace Ryujinx.Graphics.Gpu.Shader
dataReader.ReadWithMagicAndSize(ref textureKey, TexkMagic); dataReader.ReadWithMagicAndSize(ref textureKey, TexkMagic);
dataReader.Read(ref length); 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)) if (_queriedState.HasFlag(QueriedStateFlags.TextureArrayFromBuffer))
{ {
count = (ushort)_textureArraySpecialization.Count; count = (ushort)_textureArrayFromBufferSpecialization.Count;
dataWriter.Write(ref count); dataWriter.Write(ref count);
foreach (var kv in _textureArraySpecialization) 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 _textureArrayFromPoolSpecialization)
{ {
var textureKey = kv.Key; var textureKey = kv.Key;
var length = kv.Value; var length = kv.Value;

View File

@ -176,6 +176,7 @@ namespace Ryujinx.Graphics.OpenGL
supportsCubemapView: true, supportsCubemapView: true,
supportsNonConstantTextureOffset: HwCapabilities.SupportsNonConstantTextureOffset, supportsNonConstantTextureOffset: HwCapabilities.SupportsNonConstantTextureOffset,
supportsScaledVertexFormats: true, supportsScaledVertexFormats: true,
supportsSeparateSampler: false,
supportsShaderBallot: HwCapabilities.SupportsShaderBallot, supportsShaderBallot: HwCapabilities.SupportsShaderBallot,
supportsShaderBarrierDivergence: !(intelWindows || intelUnix), supportsShaderBarrierDivergence: !(intelWindows || intelUnix),
supportsShaderFloat64: true, supportsShaderFloat64: true,

View File

@ -6,7 +6,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Numerics;
namespace Ryujinx.Graphics.Shader.CodeGen.Glsl namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{ {
@ -352,7 +351,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
arrayDecl = "[]"; arrayDecl = "[]";
} }
string samplerTypeName = definition.Type.ToGlslSamplerType(); string samplerTypeName = definition.Separate ? definition.Type.ToGlslTextureType() : definition.Type.ToGlslSamplerType();
string layout = string.Empty; string layout = string.Empty;

View File

@ -639,14 +639,27 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
private static string GetSamplerName(CodeGenContext context, AstTextureOperation texOp, ref int srcIndex) private static string GetSamplerName(CodeGenContext context, AstTextureOperation texOp, ref int srcIndex)
{ {
TextureDefinition definition = context.Properties.Textures[texOp.Binding]; TextureDefinition textureDefinition = context.Properties.Textures[texOp.Binding];
string name = definition.Name; string name = textureDefinition.Name;
if (definition.ArrayLength != 1) if (textureDefinition.ArrayLength != 1)
{ {
name = $"{name}[{GetSourceExpr(context, texOp.GetSource(srcIndex++), AggregateType.S32)}]"; name = $"{name}[{GetSourceExpr(context, texOp.GetSource(srcIndex++), AggregateType.S32)}]";
} }
if (texOp.IsSeparate)
{
TextureDefinition samplerDefinition = context.Properties.Textures[texOp.SamplerBinding];
string samplerName = samplerDefinition.Name;
if (samplerDefinition.ArrayLength != 1)
{
samplerName = $"{samplerName}[{GetSourceExpr(context, texOp.GetSource(srcIndex++), AggregateType.S32)}]";
}
name = $"{texOp.Type.ToGlslSamplerType()}({name}, {samplerName})";
}
return name; return name;
} }

View File

@ -160,6 +160,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{ {
int setIndex = context.TargetApi == TargetApi.Vulkan ? sampler.Set : 0; int setIndex = context.TargetApi == TargetApi.Vulkan ? sampler.Set : 0;
SpvInstruction imageType;
SpvInstruction sampledImageType;
if (sampler.Type != SamplerType.None)
{
var dim = (sampler.Type & SamplerType.Mask) switch var dim = (sampler.Type & SamplerType.Mask) switch
{ {
SamplerType.Texture1D => Dim.Dim1D, SamplerType.Texture1D => Dim.Dim1D,
@ -170,7 +175,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
_ => throw new InvalidOperationException($"Invalid sampler type \"{sampler.Type & SamplerType.Mask}\"."), _ => throw new InvalidOperationException($"Invalid sampler type \"{sampler.Type & SamplerType.Mask}\"."),
}; };
var imageType = context.TypeImage( imageType = context.TypeImage(
context.TypeFP32(), context.TypeFP32(),
dim, dim,
sampler.Type.HasFlag(SamplerType.Shadow), sampler.Type.HasFlag(SamplerType.Shadow),
@ -179,18 +184,25 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
1, 1,
ImageFormat.Unknown); ImageFormat.Unknown);
var sampledImageType = context.TypeSampledImage(imageType); sampledImageType = context.TypeSampledImage(imageType);
var sampledImagePointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageType); }
else
{
imageType = sampledImageType = context.TypeSampler();
}
var sampledOrSeparateImageType = sampler.Separate ? imageType : sampledImageType;
var sampledImagePointerType = context.TypePointer(StorageClass.UniformConstant, sampledOrSeparateImageType);
var sampledImageArrayPointerType = sampledImagePointerType; var sampledImageArrayPointerType = sampledImagePointerType;
if (sampler.ArrayLength == 0) if (sampler.ArrayLength == 0)
{ {
var sampledImageArrayType = context.TypeRuntimeArray(sampledImageType); var sampledImageArrayType = context.TypeRuntimeArray(sampledOrSeparateImageType);
sampledImageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageArrayType); sampledImageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageArrayType);
} }
else if (sampler.ArrayLength != 1) else if (sampler.ArrayLength != 1)
{ {
var sampledImageArrayType = context.TypeArray(sampledImageType, context.Constant(context.TypeU32(), sampler.ArrayLength)); var sampledImageArrayType = context.TypeArray(sampledOrSeparateImageType, context.Constant(context.TypeU32(), sampler.ArrayLength));
sampledImageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageArrayType); sampledImageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageArrayType);
} }

View File

@ -838,16 +838,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
} }
SamplerDeclaration declaration = context.Samplers[texOp.Binding]; SamplerDeclaration declaration = context.Samplers[texOp.Binding];
SpvInstruction image = declaration.Image; SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
if (declaration.IsIndexed)
{
SpvInstruction textureIndex = Src(AggregateType.S32);
image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex);
}
image = context.Load(declaration.SampledImageType, image);
int pCount = texOp.Type.GetDimensions(); int pCount = texOp.Type.GetDimensions();
@ -1171,16 +1162,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
} }
SamplerDeclaration declaration = context.Samplers[texOp.Binding]; SamplerDeclaration declaration = context.Samplers[texOp.Binding];
SpvInstruction image = declaration.Image; SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
if (declaration.IsIndexed)
{
SpvInstruction textureIndex = Src(AggregateType.S32);
image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex);
}
image = context.Load(declaration.SampledImageType, image);
int coordsCount = texOp.Type.GetDimensions(); int coordsCount = texOp.Type.GetDimensions();
@ -1449,17 +1431,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{ {
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
int srcIndex = 0;
SamplerDeclaration declaration = context.Samplers[texOp.Binding]; SamplerDeclaration declaration = context.Samplers[texOp.Binding];
SpvInstruction image = declaration.Image; SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
if (declaration.IsIndexed)
{
SpvInstruction textureIndex = context.GetS32(texOp.GetSource(0));
image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex);
}
image = context.Load(declaration.SampledImageType, image);
image = context.Image(declaration.ImageType, image); image = context.Image(declaration.ImageType, image);
SpvInstruction result = context.ImageQuerySamples(context.TypeS32(), image); SpvInstruction result = context.ImageQuerySamples(context.TypeS32(), image);
@ -1471,17 +1447,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{ {
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
int srcIndex = 0;
SamplerDeclaration declaration = context.Samplers[texOp.Binding]; SamplerDeclaration declaration = context.Samplers[texOp.Binding];
SpvInstruction image = declaration.Image; SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
if (declaration.IsIndexed)
{
SpvInstruction textureIndex = context.GetS32(texOp.GetSource(0));
image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex);
}
image = context.Load(declaration.SampledImageType, image);
image = context.Image(declaration.ImageType, image); image = context.Image(declaration.ImageType, image);
if (texOp.Index == 3) if (texOp.Index == 3)
@ -1506,8 +1476,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (hasLod) if (hasLod)
{ {
int lodSrcIndex = declaration.IsIndexed ? 1 : 0; var lod = context.GetS32(operation.GetSource(srcIndex));
var lod = context.GetS32(operation.GetSource(lodSrcIndex));
result = context.ImageQuerySizeLod(resultType, image, lod); result = context.ImageQuerySizeLod(resultType, image, lod);
} }
else else
@ -1905,6 +1874,43 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
} }
} }
private static SpvInstruction GenerateSampledImageLoad(CodeGenContext context, AstTextureOperation texOp, SamplerDeclaration declaration, ref int srcIndex)
{
SpvInstruction image = declaration.Image;
if (declaration.IsIndexed)
{
SpvInstruction textureIndex = context.Get(AggregateType.S32, texOp.GetSource(srcIndex++));
image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex);
}
if (texOp.IsSeparate)
{
image = context.Load(declaration.ImageType, image);
SamplerDeclaration samplerDeclaration = context.Samplers[texOp.SamplerBinding];
SpvInstruction sampler = samplerDeclaration.Image;
if (samplerDeclaration.IsIndexed)
{
SpvInstruction samplerIndex = context.Get(AggregateType.S32, texOp.GetSource(srcIndex++));
sampler = context.AccessChain(samplerDeclaration.SampledImagePointerType, sampler, samplerIndex);
}
sampler = context.Load(samplerDeclaration.ImageType, sampler);
image = context.SampledImage(declaration.SampledImageType, image, sampler);
}
else
{
image = context.Load(declaration.SampledImageType, image);
}
return image;
}
private static OperationResult GenerateUnary( private static OperationResult GenerateUnary(
CodeGenContext context, CodeGenContext context,
AstOperation operation, AstOperation operation,

View File

@ -26,13 +26,6 @@ namespace Ryujinx.Graphics.Shader
/// <returns>Span of the memory location</returns> /// <returns>Span of the memory location</returns>
ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize); ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize);
/// <summary>
/// Gets the size in bytes of a bound constant buffer for the current shader stage.
/// </summary>
/// <param name="slot">The number of the constant buffer to get the size from</param>
/// <returns>Size in bytes</returns>
int QueryTextureArrayLengthFromBuffer(int slot);
/// <summary> /// <summary>
/// Queries the binding number of a constant buffer. /// Queries the binding number of a constant buffer.
/// </summary> /// </summary>
@ -298,6 +291,15 @@ namespace Ryujinx.Graphics.Shader
return true; return true;
} }
/// <summary>
/// Queries host API support for separate textures and samplers.
/// </summary>
/// <returns>True if the API supports samplers and textures to be combined on the shader, false otherwise</returns>
bool QueryHostSupportsSeparateSampler()
{
return true;
}
/// <summary> /// <summary>
/// Queries host GPU shader ballot support. /// Queries host GPU shader ballot support.
/// </summary> /// </summary>
@ -388,6 +390,12 @@ namespace Ryujinx.Graphics.Shader
return true; return true;
} }
/// <summary>
/// Gets the maximum number of samplers that the bound texture pool may have.
/// </summary>
/// <returns>Maximum amount of samplers that the pool may have</returns>
int QuerySamplerArrayLengthFromPool();
/// <summary> /// <summary>
/// Queries sampler type information. /// Queries sampler type information.
/// </summary> /// </summary>
@ -399,6 +407,19 @@ namespace Ryujinx.Graphics.Shader
return SamplerType.Texture2D; return SamplerType.Texture2D;
} }
/// <summary>
/// Gets the size in bytes of a bound constant buffer for the current shader stage.
/// </summary>
/// <param name="slot">The number of the constant buffer to get the size from</param>
/// <returns>Size in bytes</returns>
int QueryTextureArrayLengthFromBuffer(int slot);
/// <summary>
/// Gets the maximum number of textures that the bound texture pool may have.
/// </summary>
/// <returns>Maximum amount of textures that the pool may have</returns>
int QueryTextureArrayLengthFromPool();
/// <summary> /// <summary>
/// Queries texture coordinate normalization information. /// Queries texture coordinate normalization information.
/// </summary> /// </summary>

View File

@ -216,6 +216,11 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
newSources[index] = source; newSources[index] = source;
if (source != null && source.Type == OperandType.LocalVariable)
{
source.UseOps.Add(this);
}
_sources = newSources; _sources = newSources;
} }

View File

@ -9,6 +9,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
public TextureFlags Flags { get; private set; } public TextureFlags Flags { get; private set; }
public int Binding { get; private set; } public int Binding { get; private set; }
public int SamplerBinding { get; private set; }
public TextureOperation( public TextureOperation(
Instruction inst, Instruction inst,
@ -24,6 +25,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
Format = format; Format = format;
Flags = flags; Flags = flags;
Binding = binding; Binding = binding;
SamplerBinding = -1;
} }
public void TurnIntoArray(int binding) public void TurnIntoArray(int binding)
@ -32,6 +34,13 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
Binding = binding; Binding = binding;
} }
public void TurnIntoArray(int textureBinding, int samplerBinding)
{
TurnIntoArray(textureBinding);
SamplerBinding = samplerBinding;
}
public void SetBinding(int binding) public void SetBinding(int binding)
{ {
if ((Flags & TextureFlags.Bindless) != 0) if ((Flags & TextureFlags.Bindless) != 0)

View File

@ -69,6 +69,7 @@ namespace Ryujinx.Graphics.Shader
{ {
string typeName = (type & SamplerType.Mask) switch string typeName = (type & SamplerType.Mask) switch
{ {
SamplerType.None => "sampler",
SamplerType.Texture1D => "sampler1D", SamplerType.Texture1D => "sampler1D",
SamplerType.TextureBuffer => "samplerBuffer", SamplerType.TextureBuffer => "samplerBuffer",
SamplerType.Texture2D => "sampler2D", SamplerType.Texture2D => "sampler2D",
@ -95,6 +96,31 @@ namespace Ryujinx.Graphics.Shader
return typeName; return typeName;
} }
public static string ToGlslTextureType(this SamplerType type)
{
string typeName = (type & SamplerType.Mask) switch
{
SamplerType.Texture1D => "texture1D",
SamplerType.TextureBuffer => "textureBuffer",
SamplerType.Texture2D => "texture2D",
SamplerType.Texture3D => "texture3D",
SamplerType.TextureCube => "textureCube",
_ => throw new ArgumentException($"Invalid texture type \"{type}\"."),
};
if ((type & SamplerType.Multisample) != 0)
{
typeName += "MS";
}
if ((type & SamplerType.Array) != 0)
{
typeName += "Array";
}
return typeName;
}
public static string ToGlslImageType(this SamplerType type, AggregateType componentType) public static string ToGlslImageType(this SamplerType type, AggregateType componentType)
{ {
string typeName = (type & SamplerType.Mask) switch string typeName = (type & SamplerType.Mask) switch

View File

@ -9,6 +9,9 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
public TextureFlags Flags { get; } public TextureFlags Flags { get; }
public int Binding { get; } public int Binding { get; }
public int SamplerBinding { get; }
public bool IsSeparate => SamplerBinding >= 0;
public AstTextureOperation( public AstTextureOperation(
Instruction inst, Instruction inst,
@ -16,6 +19,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
TextureFormat format, TextureFormat format,
TextureFlags flags, TextureFlags flags,
int binding, int binding,
int samplerBinding,
int index, int index,
params IAstNode[] sources) : base(inst, StorageKind.None, false, index, sources, sources.Length) params IAstNode[] sources) : base(inst, StorageKind.None, false, index, sources, sources.Length)
{ {
@ -23,6 +27,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
Format = format; Format = format;
Flags = flags; Flags = flags;
Binding = binding; Binding = binding;
SamplerBinding = samplerBinding;
} }
} }
} }

View File

@ -169,7 +169,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
AstTextureOperation GetAstTextureOperation(TextureOperation texOp) AstTextureOperation GetAstTextureOperation(TextureOperation texOp)
{ {
return new AstTextureOperation(inst, texOp.Type, texOp.Format, texOp.Flags, texOp.Binding, texOp.Index, sources); return new AstTextureOperation(inst, texOp.Type, texOp.Format, texOp.Flags, texOp.Binding, texOp.SamplerBinding, texOp.Index, sources);
} }
int componentsCount = BitOperations.PopCount((uint)operation.Index); int componentsCount = BitOperations.PopCount((uint)operation.Index);

View File

@ -5,25 +5,39 @@ namespace Ryujinx.Graphics.Shader
public int Set { get; } public int Set { get; }
public int Binding { get; } public int Binding { get; }
public int ArrayLength { get; } public int ArrayLength { get; }
public bool Separate { get; }
public string Name { get; } public string Name { get; }
public SamplerType Type { get; } public SamplerType Type { get; }
public TextureFormat Format { get; } public TextureFormat Format { get; }
public TextureUsageFlags Flags { get; } public TextureUsageFlags Flags { get; }
public TextureDefinition(int set, int binding, int arrayLength, string name, SamplerType type, TextureFormat format, TextureUsageFlags flags) public TextureDefinition(
int set,
int binding,
int arrayLength,
bool separate,
string name,
SamplerType type,
TextureFormat format,
TextureUsageFlags flags)
{ {
Set = set; Set = set;
Binding = binding; Binding = binding;
ArrayLength = arrayLength; ArrayLength = arrayLength;
Separate = separate;
Name = name; Name = name;
Type = type; Type = type;
Format = format; Format = format;
Flags = flags; Flags = flags;
} }
public TextureDefinition(int set, int binding, string name, SamplerType type) : this(set, binding, 1, false, name, type, TextureFormat.Unknown, TextureUsageFlags.None)
{
}
public TextureDefinition SetFlag(TextureUsageFlags flag) public TextureDefinition SetFlag(TextureUsageFlags flag)
{ {
return new TextureDefinition(Set, Binding, ArrayLength, Name, Type, Format, Flags | flag); return new TextureDefinition(Set, Binding, ArrayLength, Separate, Name, Type, Format, Flags | flag);
} }
} }
} }

View File

@ -13,6 +13,8 @@ namespace Ryujinx.Graphics.Shader
public readonly int HandleIndex; public readonly int HandleIndex;
public readonly int ArrayLength; public readonly int ArrayLength;
public readonly bool Separate;
public readonly TextureUsageFlags Flags; public readonly TextureUsageFlags Flags;
public TextureDescriptor( public TextureDescriptor(
@ -22,6 +24,7 @@ namespace Ryujinx.Graphics.Shader
int cbufSlot, int cbufSlot,
int handleIndex, int handleIndex,
int arrayLength, int arrayLength,
bool separate,
TextureUsageFlags flags) TextureUsageFlags flags)
{ {
Binding = binding; Binding = binding;
@ -30,6 +33,7 @@ namespace Ryujinx.Graphics.Shader
CbufSlot = cbufSlot; CbufSlot = cbufSlot;
HandleIndex = handleIndex; HandleIndex = handleIndex;
ArrayLength = arrayLength; ArrayLength = arrayLength;
Separate = separate;
Flags = flags; Flags = flags;
} }
} }

View File

@ -9,6 +9,7 @@ namespace Ryujinx.Graphics.Shader
SeparateSamplerHandle = 1, SeparateSamplerHandle = 1,
SeparateSamplerId = 2, SeparateSamplerId = 2,
SeparateConstantSamplerHandle = 3, SeparateConstantSamplerHandle = 3,
Direct = 4,
} }
public static class TextureHandle public static class TextureHandle

View File

@ -1,6 +1,7 @@
using Ryujinx.Graphics.Shader.Instructions; using Ryujinx.Graphics.Shader.Instructions;
using Ryujinx.Graphics.Shader.IntermediateRepresentation; using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr; using Ryujinx.Graphics.Shader.StructuredIr;
using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.Translation.Optimizations namespace Ryujinx.Graphics.Shader.Translation.Optimizations
@ -31,7 +32,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
continue; continue;
} }
if (!TryConvertBindless(block, resourceManager, gpuAccessor, texOp)) if (!TryConvertBindless(block, resourceManager, gpuAccessor, texOp) &&
!GenerateBindlessAccess(block, resourceManager, gpuAccessor, texOp, node))
{ {
// If we can't do bindless elimination, remove the texture operation. // If we can't do bindless elimination, remove the texture operation.
// Set any destination variables to zero. // Set any destination variables to zero.
@ -46,6 +48,88 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
} }
} }
private static bool GenerateBindlessAccess(
BasicBlock block,
ResourceManager resourceManager,
IGpuAccessor gpuAccessor,
TextureOperation texOp,
LinkedListNode<INode> node)
{
if (!gpuAccessor.QueryHostSupportsSeparateSampler())
{
// We depend on combining samplers and textures in the shader being supported for this.
return false;
}
Operand nvHandle = texOp.GetSource(0);
if (nvHandle.AsgOp is not Operation handleOp ||
handleOp.Inst != Instruction.Load ||
handleOp.StorageKind != StorageKind.Input)
{
// Right now, we only allow bindless access when the handle comes from a shader input.
// This is an artificial limitation to prevent it from being used in cases where it
// would have a large performance impact of loading all textures in the pool.
// It might be removed in the future, if we can mitigate the performance impact.
return false;
}
Operand textureHandle = OperandHelper.Local();
Operand samplerHandle = OperandHelper.Local();
Operand textureIndex = OperandHelper.Local();
block.Operations.AddBefore(node, new Operation(Instruction.BitwiseAnd, textureHandle, nvHandle, OperandHelper.Const(0xfffff)));
block.Operations.AddBefore(node, new Operation(Instruction.ShiftRightU32, samplerHandle, nvHandle, OperandHelper.Const(20)));
int texturePoolLength = Math.Max(BindlessToArray.MinimumArrayLength, gpuAccessor.QueryTextureArrayLengthFromPool());
block.Operations.AddBefore(node, new Operation(Instruction.MinimumU32, textureIndex, textureHandle, OperandHelper.Const(texturePoolLength - 1)));
texOp.SetSource(0, textureIndex);
bool hasSampler = !texOp.Inst.IsImage();
int textureBinding = resourceManager.GetTextureOrImageBinding(
texOp.Inst,
texOp.Type,
texOp.Format,
texOp.Flags & ~TextureFlags.Bindless,
0,
TextureHandle.PackOffsets(0, 0, TextureHandleType.Direct),
texturePoolLength,
hasSampler);
if (hasSampler)
{
Operand samplerIndex = OperandHelper.Local();
int samplerPoolLength = Math.Max(BindlessToArray.MinimumArrayLength, gpuAccessor.QuerySamplerArrayLengthFromPool());
block.Operations.AddBefore(node, new Operation(Instruction.MinimumU32, samplerIndex, samplerHandle, OperandHelper.Const(samplerPoolLength - 1)));
texOp.InsertSource(1, samplerIndex);
int samplerBinding = resourceManager.GetTextureOrImageBinding(
texOp.Inst,
SamplerType.None,
texOp.Format,
TextureFlags.None,
0,
TextureHandle.PackOffsets(0, 0, TextureHandleType.Direct),
samplerPoolLength);
texOp.TurnIntoArray(textureBinding, samplerBinding);
}
else
{
texOp.TurnIntoArray(textureBinding);
}
return true;
}
private static bool TryConvertBindless(BasicBlock block, ResourceManager resourceManager, IGpuAccessor gpuAccessor, TextureOperation texOp) private static bool TryConvertBindless(BasicBlock block, ResourceManager resourceManager, IGpuAccessor gpuAccessor, TextureOperation texOp)
{ {
if (texOp.Inst == Instruction.TextureSample || texOp.Inst.IsTextureQuery()) if (texOp.Inst == Instruction.TextureSample || texOp.Inst.IsTextureQuery())

View File

@ -11,7 +11,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
private const int HardcodedArrayLengthOgl = 4; private const int HardcodedArrayLengthOgl = 4;
// 1 and 0 elements are not considered arrays anymore. // 1 and 0 elements are not considered arrays anymore.
private const int MinimumArrayLength = 2; public const int MinimumArrayLength = 2;
public static void RunPassOgl(BasicBlock block, ResourceManager resourceManager) public static void RunPassOgl(BasicBlock block, ResourceManager resourceManager)
{ {

View File

@ -29,7 +29,7 @@ namespace Ryujinx.Graphics.Shader.Translation
private readonly HashSet<int> _usedConstantBufferBindings; private readonly HashSet<int> _usedConstantBufferBindings;
private readonly record struct TextureInfo(int CbufSlot, int Handle, int ArrayLength, SamplerType Type, TextureFormat Format); private readonly record struct TextureInfo(int CbufSlot, int Handle, int ArrayLength, bool Separate, SamplerType Type, TextureFormat Format);
private struct TextureMeta private struct TextureMeta
{ {
@ -225,7 +225,8 @@ namespace Ryujinx.Graphics.Shader.Translation
TextureFlags flags, TextureFlags flags,
int cbufSlot, int cbufSlot,
int handle, int handle,
int arrayLength = 1) int arrayLength = 1,
bool separate = false)
{ {
inst &= Instruction.Mask; inst &= Instruction.Mask;
bool isImage = inst.IsImage(); bool isImage = inst.IsImage();
@ -239,7 +240,18 @@ namespace Ryujinx.Graphics.Shader.Translation
format = TextureFormat.Unknown; format = TextureFormat.Unknown;
} }
int binding = GetTextureOrImageBinding(cbufSlot, handle, arrayLength, type, format, isImage, intCoords, isWrite, accurateType, coherent); int binding = GetTextureOrImageBinding(
cbufSlot,
handle,
arrayLength,
type,
format,
isImage,
intCoords,
isWrite,
accurateType,
coherent,
separate);
_gpuAccessor.RegisterTexture(handle, cbufSlot); _gpuAccessor.RegisterTexture(handle, cbufSlot);
@ -256,9 +268,10 @@ namespace Ryujinx.Graphics.Shader.Translation
bool intCoords, bool intCoords,
bool write, bool write,
bool accurateType, bool accurateType,
bool coherent) bool coherent,
bool separate)
{ {
var dimensions = type.GetDimensions(); var dimensions = type == SamplerType.None ? 0 : type.GetDimensions();
var dict = isImage ? _usedImages : _usedTextures; var dict = isImage ? _usedImages : _usedTextures;
var usageFlags = TextureUsageFlags.None; var usageFlags = TextureUsageFlags.None;
@ -290,7 +303,7 @@ namespace Ryujinx.Graphics.Shader.Translation
// For array textures, we also want to use type as key, // For array textures, we also want to use type as key,
// since we may have texture handles stores in the same buffer, but for textures with different types. // since we may have texture handles stores in the same buffer, but for textures with different types.
var keyType = arrayLength > 1 ? type : SamplerType.None; var keyType = arrayLength > 1 ? type : SamplerType.None;
var info = new TextureInfo(cbufSlot, handle, arrayLength, keyType, format); var info = new TextureInfo(cbufSlot, handle, arrayLength, separate, keyType, format);
var meta = new TextureMeta() var meta = new TextureMeta()
{ {
AccurateType = accurateType, AccurateType = accurateType,
@ -332,6 +345,10 @@ namespace Ryujinx.Graphics.Shader.Translation
? $"{prefix}_tcb_{handle:X}_{format.ToGlslFormat()}" ? $"{prefix}_tcb_{handle:X}_{format.ToGlslFormat()}"
: $"{prefix}_cb{cbufSlot}_{handle:X}_{format.ToGlslFormat()}"; : $"{prefix}_cb{cbufSlot}_{handle:X}_{format.ToGlslFormat()}";
} }
else if (type == SamplerType.None)
{
nameSuffix = cbufSlot < 0 ? $"s_tcb_{handle:X}" : $"s_cb{cbufSlot}_{handle:X}";
}
else else
{ {
nameSuffix = cbufSlot < 0 ? $"{prefix}_tcb_{handle:X}" : $"{prefix}_cb{cbufSlot}_{handle:X}"; nameSuffix = cbufSlot < 0 ? $"{prefix}_tcb_{handle:X}" : $"{prefix}_cb{cbufSlot}_{handle:X}";
@ -341,6 +358,7 @@ namespace Ryujinx.Graphics.Shader.Translation
isImage ? 3 : 2, isImage ? 3 : 2,
binding, binding,
arrayLength, arrayLength,
separate,
$"{_stagePrefix}_{nameSuffix}", $"{_stagePrefix}_{nameSuffix}",
meta.Type, meta.Type,
info.Format, info.Format,
@ -495,6 +513,7 @@ namespace Ryujinx.Graphics.Shader.Translation
info.CbufSlot, info.CbufSlot,
info.Handle, info.Handle,
info.ArrayLength, info.ArrayLength,
info.Separate,
meta.UsageFlags)); meta.UsageFlags));
} }
@ -514,6 +533,7 @@ namespace Ryujinx.Graphics.Shader.Translation
info.CbufSlot, info.CbufSlot,
info.Handle, info.Handle,
info.ArrayLength, info.ArrayLength,
info.Separate,
meta.UsageFlags)); meta.UsageFlags));
} }
} }

View File

@ -413,7 +413,7 @@ namespace Ryujinx.Graphics.Shader.Translation
if (Stage == ShaderStage.Vertex) if (Stage == ShaderStage.Vertex)
{ {
int ibBinding = resourceManager.Reservations.IndexBufferTextureBinding; int ibBinding = resourceManager.Reservations.IndexBufferTextureBinding;
TextureDefinition indexBuffer = new(2, ibBinding, 1, "ib_data", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None); TextureDefinition indexBuffer = new(2, ibBinding, "ib_data", SamplerType.TextureBuffer);
resourceManager.Properties.AddOrUpdateTexture(indexBuffer); resourceManager.Properties.AddOrUpdateTexture(indexBuffer);
int inputMap = _program.AttributeUsage.UsedInputAttributes; int inputMap = _program.AttributeUsage.UsedInputAttributes;
@ -422,7 +422,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{ {
int location = BitOperations.TrailingZeroCount(inputMap); int location = BitOperations.TrailingZeroCount(inputMap);
int binding = resourceManager.Reservations.GetVertexBufferTextureBinding(location); int binding = resourceManager.Reservations.GetVertexBufferTextureBinding(location);
TextureDefinition vaBuffer = new(2, binding, 1, $"vb_data{location}", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None); TextureDefinition vaBuffer = new(2, binding, $"vb_data{location}", SamplerType.TextureBuffer);
resourceManager.Properties.AddOrUpdateTexture(vaBuffer); resourceManager.Properties.AddOrUpdateTexture(vaBuffer);
inputMap &= ~(1 << location); inputMap &= ~(1 << location);
@ -431,7 +431,7 @@ namespace Ryujinx.Graphics.Shader.Translation
else if (Stage == ShaderStage.Geometry) else if (Stage == ShaderStage.Geometry)
{ {
int trbBinding = resourceManager.Reservations.TopologyRemapBufferTextureBinding; int trbBinding = resourceManager.Reservations.TopologyRemapBufferTextureBinding;
TextureDefinition remapBuffer = new(2, trbBinding, 1, "trb_data", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None); TextureDefinition remapBuffer = new(2, trbBinding, "trb_data", SamplerType.TextureBuffer);
resourceManager.Properties.AddOrUpdateTexture(remapBuffer); resourceManager.Properties.AddOrUpdateTexture(remapBuffer);
int geometryVbOutputSbBinding = resourceManager.Reservations.GeometryVertexOutputStorageBufferBinding; int geometryVbOutputSbBinding = resourceManager.Reservations.GeometryVertexOutputStorageBufferBinding;

View File

@ -43,11 +43,11 @@ namespace Ryujinx.Graphics.Vulkan
int binding = segment.Binding; int binding = segment.Binding;
int count = segment.Count; int count = segment.Count;
if (setIndex == PipelineBase.UniformSetIndex) if (IsBufferType(segment.Type))
{ {
entries[seg] = new DescriptorUpdateTemplateEntry() entries[seg] = new DescriptorUpdateTemplateEntry()
{ {
DescriptorType = DescriptorType.UniformBuffer, DescriptorType = segment.Type.Convert(),
DstBinding = (uint)binding, DstBinding = (uint)binding,
DescriptorCount = (uint)count, DescriptorCount = (uint)count,
Offset = structureOffset, Offset = structureOffset,
@ -56,39 +56,11 @@ namespace Ryujinx.Graphics.Vulkan
structureOffset += (nuint)(Unsafe.SizeOf<DescriptorBufferInfo>() * count); structureOffset += (nuint)(Unsafe.SizeOf<DescriptorBufferInfo>() * count);
} }
else if (setIndex == PipelineBase.StorageSetIndex) else if (IsBufferTextureType(segment.Type))
{ {
entries[seg] = new DescriptorUpdateTemplateEntry() entries[seg] = new DescriptorUpdateTemplateEntry()
{ {
DescriptorType = DescriptorType.StorageBuffer, DescriptorType = segment.Type.Convert(),
DstBinding = (uint)binding,
DescriptorCount = (uint)count,
Offset = structureOffset,
Stride = (nuint)Unsafe.SizeOf<DescriptorBufferInfo>()
};
structureOffset += (nuint)(Unsafe.SizeOf<DescriptorBufferInfo>() * count);
}
else if (setIndex == PipelineBase.TextureSetIndex)
{
if (segment.Type != ResourceType.BufferTexture)
{
entries[seg] = new DescriptorUpdateTemplateEntry()
{
DescriptorType = DescriptorType.CombinedImageSampler,
DstBinding = (uint)binding,
DescriptorCount = (uint)count,
Offset = structureOffset,
Stride = (nuint)Unsafe.SizeOf<DescriptorImageInfo>()
};
structureOffset += (nuint)(Unsafe.SizeOf<DescriptorImageInfo>() * count);
}
else
{
entries[seg] = new DescriptorUpdateTemplateEntry()
{
DescriptorType = DescriptorType.UniformTexelBuffer,
DstBinding = (uint)binding, DstBinding = (uint)binding,
DescriptorCount = (uint)count, DescriptorCount = (uint)count,
Offset = structureOffset, Offset = structureOffset,
@ -97,14 +69,11 @@ namespace Ryujinx.Graphics.Vulkan
structureOffset += (nuint)(Unsafe.SizeOf<BufferView>() * count); structureOffset += (nuint)(Unsafe.SizeOf<BufferView>() * count);
} }
} else
else if (setIndex == PipelineBase.ImageSetIndex)
{
if (segment.Type != ResourceType.BufferImage)
{ {
entries[seg] = new DescriptorUpdateTemplateEntry() entries[seg] = new DescriptorUpdateTemplateEntry()
{ {
DescriptorType = DescriptorType.StorageImage, DescriptorType = segment.Type.Convert(),
DstBinding = (uint)binding, DstBinding = (uint)binding,
DescriptorCount = (uint)count, DescriptorCount = (uint)count,
Offset = structureOffset, Offset = structureOffset,
@ -113,20 +82,6 @@ namespace Ryujinx.Graphics.Vulkan
structureOffset += (nuint)(Unsafe.SizeOf<DescriptorImageInfo>() * count); structureOffset += (nuint)(Unsafe.SizeOf<DescriptorImageInfo>() * count);
} }
else
{
entries[seg] = new DescriptorUpdateTemplateEntry()
{
DescriptorType = DescriptorType.StorageTexelBuffer,
DstBinding = (uint)binding,
DescriptorCount = (uint)count,
Offset = structureOffset,
Stride = (nuint)Unsafe.SizeOf<BufferView>()
};
structureOffset += (nuint)(Unsafe.SizeOf<BufferView>() * count);
}
}
} }
Size = (int)structureOffset; Size = (int)structureOffset;
@ -237,6 +192,16 @@ namespace Ryujinx.Graphics.Vulkan
Template = result; Template = result;
} }
private static bool IsBufferType(ResourceType type)
{
return type == ResourceType.UniformBuffer || type == ResourceType.StorageBuffer;
}
private static bool IsBufferTextureType(ResourceType type)
{
return type == ResourceType.BufferTexture || type == ResourceType.BufferImage;
}
public unsafe void Dispose() public unsafe void Dispose()
{ {
_gd.Api.DestroyDescriptorUpdateTemplate(_device, Template, null); _gd.Api.DestroyDescriptorUpdateTemplate(_device, Template, null);

View File

@ -706,6 +706,7 @@ namespace Ryujinx.Graphics.Vulkan
supportsCubemapView: !IsAmdGcn, supportsCubemapView: !IsAmdGcn,
supportsNonConstantTextureOffset: false, supportsNonConstantTextureOffset: false,
supportsScaledVertexFormats: FormatCapabilities.SupportsScaledVertexFormats(), supportsScaledVertexFormats: FormatCapabilities.SupportsScaledVertexFormats(),
supportsSeparateSampler: true,
supportsShaderBallot: false, supportsShaderBallot: false,
supportsShaderBarrierDivergence: Vendor != Vendor.Intel, supportsShaderBarrierDivergence: Vendor != Vendor.Intel,
supportsShaderFloat64: Capabilities.SupportsShaderFloat64, supportsShaderFloat64: Capabilities.SupportsShaderFloat64,

View File

@ -58,10 +58,20 @@ namespace Ryujinx.ShaderTools
return MemoryMarshal.Cast<byte, ulong>(new ReadOnlySpan<byte>(_data)[(int)address..]); return MemoryMarshal.Cast<byte, ulong>(new ReadOnlySpan<byte>(_data)[(int)address..]);
} }
public int QuerySamplerArrayLengthFromPool()
{
return DefaultArrayLength;
}
public int QueryTextureArrayLengthFromBuffer(int slot) public int QueryTextureArrayLengthFromBuffer(int slot)
{ {
return DefaultArrayLength; return DefaultArrayLength;
} }
public int QueryTextureArrayLengthFromPool()
{
return DefaultArrayLength;
}
} }
private class Options private class Options