Archived
1
0
Fork 0
forked from Mirror/Ryujinx

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 SupportsCubemapView;
public readonly bool SupportsNonConstantTextureOffset;
public readonly bool SupportsSeparateSampler;
public readonly bool SupportsShaderBallot;
public readonly bool SupportsShaderBarrierDivergence;
public readonly bool SupportsShaderFloat64;
@ -92,6 +93,7 @@ namespace Ryujinx.Graphics.GAL
bool supportsMismatchingViewFormat,
bool supportsCubemapView,
bool supportsNonConstantTextureOffset,
bool supportsSeparateSampler,
bool supportsShaderBallot,
bool supportsShaderBarrierDivergence,
bool supportsShaderFloat64,
@ -144,6 +146,7 @@ namespace Ryujinx.Graphics.GAL
SupportsMismatchingViewFormat = supportsMismatchingViewFormat;
SupportsCubemapView = supportsCubemapView;
SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset;
SupportsSeparateSampler = supportsSeparateSampler;
SupportsShaderBallot = supportsShaderBallot;
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
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 texturePoolGpuVa = ((ulong)_state.State.SetTexHeaderPoolAOffsetUpper << 32) | _state.State.SetTexHeaderPoolB;
int samplerPoolMaximumId = _state.State.SetTexSamplerPoolCMaximumIndex;
GpuChannelPoolState poolState = new(
texturePoolGpuVa,
_state.State.SetTexHeaderPoolCMaximumIndex,
@ -139,7 +141,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
sharedMemorySize,
_channel.BufferManager.HasUnalignedStorageBuffers);
CachedShaderProgram cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, poolState, computeState, shaderGpuVa);
CachedShaderProgram cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, samplerPoolMaximumId, poolState, computeState, shaderGpuVa);
_context.Renderer.Pipeline.SetProgram(cs.HostProgram);
@ -184,7 +186,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
sharedMemorySize,
_channel.BufferManager.HasUnalignedStorageBuffers);
cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, poolState, computeState, shaderGpuVa);
cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, samplerPoolMaximumId, poolState, computeState, shaderGpuVa);
_context.Renderer.Pipeline.SetProgram(cs.HostProgram);
}

View file

@ -1429,7 +1429,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
addressesSpan[index] = baseAddress + shader.Offset;
}
CachedShaderProgram gs = shaderCache.GetGraphicsShader(ref _state.State, ref _pipeline, _channel, ref _currentSpecState.GetPoolState(), ref _currentSpecState.GetGraphicsState(), addresses);
int samplerPoolMaximumId = _state.State.SamplerIndex == SamplerIndex.ViaHeaderIndex
? _state.State.TexturePoolState.MaximumId
: _state.State.SamplerPoolState.MaximumId;
CachedShaderProgram gs = shaderCache.GetGraphicsShader(
ref _state.State,
ref _pipeline,
_channel,
samplerPoolMaximumId,
ref _currentSpecState.GetPoolState(),
ref _currentSpecState.GetGraphicsState(),
addresses);
// Consume the modified flag for spec state so that it isn't checked again.
_currentSpecState.SetShader(gs);

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="address">Start address of the texture pool</param>
/// <param name="maximumId">Maximum ID of the texture pool</param>
/// <param name="bindingsArrayCache">Cache of texture array bindings</param>
/// <returns>The found or newly created texture pool</returns>
public T FindOrCreate(GpuChannel channel, ulong address, int maximumId)
public T FindOrCreate(GpuChannel channel, ulong address, int maximumId, TextureBindingsArrayCache bindingsArrayCache)
{
// Remove old entries from the cache, if possible.
while (_pools.Count > MaxCapacity && (_currentTimestamp - _pools.First.Value.CacheTimestamp) >= MinDeltaForRemoval)
@ -73,6 +74,7 @@ namespace Ryujinx.Graphics.Gpu.Image
_pools.RemoveFirst();
oldestPool.Dispose();
oldestPool.CacheNode = null;
bindingsArrayCache.RemoveAllWithPool(oldestPool);
}
T pool;
@ -87,8 +89,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (pool.CacheNode != _pools.Last)
{
_pools.Remove(pool.CacheNode);
pool.CacheNode = _pools.AddLast(pool);
_pools.AddLast(pool.CacheNode);
}
pool.CacheTimestamp = _currentTimestamp;

View file

@ -44,6 +44,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
public TextureUsageFlags Flags { get; }
/// <summary>
/// Indicates that the binding is for a sampler.
/// </summary>
public bool IsSamplerOnly { get; }
/// <summary>
/// Constructs the texture binding information structure.
/// </summary>
@ -74,8 +79,17 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="cbufSlot">Constant buffer slot where the texture handle is located</param>
/// <param name="handle">The shader texture handle (read index into the texture constant buffer)</param>
/// <param name="flags">The texture's usage flags, indicating how it is used in the shader</param>
public TextureBindingInfo(Target target, int binding, int arrayLength, int cbufSlot, int handle, TextureUsageFlags flags) : this(target, (Format)0, binding, arrayLength, cbufSlot, handle, flags)
/// <param name="isSamplerOnly">Indicates that the binding is for a sampler</param>
public TextureBindingInfo(
Target target,
int binding,
int arrayLength,
int cbufSlot,
int handle,
TextureUsageFlags flags,
bool isSamplerOnly) : this(target, 0, binding, arrayLength, cbufSlot, handle, flags)
{
IsSamplerOnly = isSamplerOnly;
}
}
}

View file

@ -21,12 +21,98 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly GpuContext _context;
private readonly GpuChannel _channel;
private readonly bool _isCompute;
/// <summary>
/// Array cache entry key.
/// </summary>
private readonly struct CacheEntryKey : IEquatable<CacheEntryKey>
private readonly struct CacheEntryFromPoolKey : IEquatable<CacheEntryFromPoolKey>
{
/// <summary>
/// Whether the entry is for an image.
/// </summary>
public readonly bool IsImage;
/// <summary>
/// Whether the entry is for a sampler.
/// </summary>
public readonly bool IsSampler;
/// <summary>
/// Texture or image target type.
/// </summary>
public readonly Target Target;
/// <summary>
/// Number of entries of the array.
/// </summary>
public readonly int ArrayLength;
private readonly TexturePool _texturePool;
private readonly SamplerPool _samplerPool;
/// <summary>
/// Creates a new array cache entry.
/// </summary>
/// <param name="isImage">Whether the entry is for an image</param>
/// <param name="bindingInfo">Binding information for the array</param>
/// <param name="texturePool">Texture pool where the array textures are located</param>
/// <param name="samplerPool">Sampler pool where the array samplers are located</param>
public CacheEntryFromPoolKey(bool isImage, TextureBindingInfo bindingInfo, TexturePool texturePool, SamplerPool samplerPool)
{
IsImage = isImage;
IsSampler = bindingInfo.IsSamplerOnly;
Target = bindingInfo.Target;
ArrayLength = bindingInfo.ArrayLength;
_texturePool = texturePool;
_samplerPool = samplerPool;
}
/// <summary>
/// Checks if the pool matches the cached pool.
/// </summary>
/// <param name="texturePool">Texture or sampler pool instance</param>
/// <returns>True if the pool matches, false otherwise</returns>
public bool MatchesPool<T>(IPool<T> pool)
{
return _texturePool == pool || _samplerPool == pool;
}
/// <summary>
/// Checks if the texture and sampler pools matches the cached pools.
/// </summary>
/// <param name="texturePool">Texture pool instance</param>
/// <param name="samplerPool">Sampler pool instance</param>
/// <returns>True if the pools match, false otherwise</returns>
private bool MatchesPools(TexturePool texturePool, SamplerPool samplerPool)
{
return _texturePool == texturePool && _samplerPool == samplerPool;
}
public bool Equals(CacheEntryFromPoolKey other)
{
return IsImage == other.IsImage &&
IsSampler == other.IsSampler &&
Target == other.Target &&
ArrayLength == other.ArrayLength &&
MatchesPools(other._texturePool, other._samplerPool);
}
public override bool Equals(object obj)
{
return obj is CacheEntryFromBufferKey other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine(_texturePool, _samplerPool, IsSampler);
}
}
/// <summary>
/// Array cache entry key.
/// </summary>
private readonly struct CacheEntryFromBufferKey : IEquatable<CacheEntryFromBufferKey>
{
/// <summary>
/// Whether the entry is for an image.
@ -61,7 +147,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="texturePool">Texture pool where the array textures are located</param>
/// <param name="samplerPool">Sampler pool where the array samplers are located</param>
/// <param name="textureBufferBounds">Constant buffer bounds with the texture handles</param>
public CacheEntryKey(
public CacheEntryFromBufferKey(
bool isImage,
TextureBindingInfo bindingInfo,
TexturePool texturePool,
@ -100,7 +186,7 @@ namespace Ryujinx.Graphics.Gpu.Image
return _textureBufferBounds.Equals(textureBufferBounds);
}
public bool Equals(CacheEntryKey other)
public bool Equals(CacheEntryFromBufferKey other)
{
return IsImage == other.IsImage &&
Target == other.Target &&
@ -112,7 +198,7 @@ namespace Ryujinx.Graphics.Gpu.Image
public override bool Equals(object obj)
{
return obj is CacheEntryKey other && Equals(other);
return obj is CacheEntryFromBufferKey other && Equals(other);
}
public override int GetHashCode()
@ -122,40 +208,15 @@ namespace Ryujinx.Graphics.Gpu.Image
}
/// <summary>
/// Array cache entry.
/// Array cache entry from pool.
/// </summary>
private class CacheEntry
{
/// <summary>
/// Key for this entry on the cache.
/// </summary>
public readonly CacheEntryKey Key;
/// <summary>
/// Linked list node used on the texture bindings array cache.
/// </summary>
public LinkedListNode<CacheEntry> CacheNode;
/// <summary>
/// Timestamp set on the last use of the array by the cache.
/// </summary>
public int CacheTimestamp;
/// <summary>
/// All cached textures, along with their invalidated sequence number as value.
/// </summary>
public readonly Dictionary<Texture, int> Textures;
/// <summary>
/// All pool texture IDs along with their textures.
/// </summary>
public readonly Dictionary<int, Texture> TextureIds;
/// <summary>
/// All pool sampler IDs along with their samplers.
/// </summary>
public readonly Dictionary<int, Sampler> SamplerIds;
/// <summary>
/// Backend texture array if the entry is for a texture, otherwise null.
/// </summary>
@ -166,44 +227,39 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
public readonly IImageArray ImageArray;
private readonly TexturePool _texturePool;
private readonly SamplerPool _samplerPool;
/// <summary>
/// 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 _samplerPoolSequence;
private int[] _cachedTextureBuffer;
private int[] _cachedSamplerBuffer;
private int _lastSequenceNumber;
/// <summary>
/// Creates a new array cache entry.
/// </summary>
/// <param name="key">Key for this entry on the cache</param>
/// <param name="texturePool">Texture pool where the array textures are located</param>
/// <param name="samplerPool">Sampler pool where the array samplers are located</param>
private CacheEntry(ref CacheEntryKey key, TexturePool texturePool, SamplerPool samplerPool)
private CacheEntry(TexturePool texturePool, SamplerPool samplerPool)
{
Key = key;
Textures = new Dictionary<Texture, int>();
TextureIds = new Dictionary<int, Texture>();
SamplerIds = new Dictionary<int, Sampler>();
_texturePool = texturePool;
_samplerPool = samplerPool;
_lastSequenceNumber = -1;
TexturePool = texturePool;
SamplerPool = samplerPool;
}
/// <summary>
/// Creates a new array cache entry.
/// </summary>
/// <param name="key">Key for this entry on the cache</param>
/// <param name="array">Backend texture array</param>
/// <param name="texturePool">Texture pool where the array textures are located</param>
/// <param name="samplerPool">Sampler pool where the array samplers are located</param>
public CacheEntry(ref CacheEntryKey key, ITextureArray array, TexturePool texturePool, SamplerPool samplerPool) : this(ref key, texturePool, samplerPool)
public CacheEntry(ITextureArray array, TexturePool texturePool, SamplerPool samplerPool) : this(texturePool, samplerPool)
{
TextureArray = array;
}
@ -211,11 +267,10 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <summary>
/// Creates a new array cache entry.
/// </summary>
/// <param name="key">Key for this entry on the cache</param>
/// <param name="array">Backend image array</param>
/// <param name="texturePool">Texture pool where the array textures are located</param>
/// <param name="samplerPool">Sampler pool where the array samplers are located</param>
public CacheEntry(ref CacheEntryKey key, IImageArray array, TexturePool texturePool, SamplerPool samplerPool) : this(ref key, texturePool, samplerPool)
public CacheEntry(IImageArray array, TexturePool texturePool, SamplerPool samplerPool) : this(texturePool, samplerPool)
{
ImageArray = array;
}
@ -248,23 +303,9 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <summary>
/// Clears all cached texture instances.
/// </summary>
public void Reset()
public virtual void Reset()
{
Textures.Clear();
TextureIds.Clear();
SamplerIds.Clear();
}
/// <summary>
/// Updates the cached constant buffer data.
/// </summary>
/// <param name="cachedTextureBuffer">Constant buffer data with the texture handles (and sampler handles, if they are combined)</param>
/// <param name="cachedSamplerBuffer">Constant buffer data with the sampler handles</param>
/// <param name="separateSamplerBuffer">Whether <paramref name="cachedTextureBuffer"/> and <paramref name="cachedSamplerBuffer"/> comes from different buffers</param>
public void UpdateData(ReadOnlySpan<int> cachedTextureBuffer, ReadOnlySpan<int> cachedSamplerBuffer, bool separateSamplerBuffer)
{
_cachedTextureBuffer = cachedTextureBuffer.ToArray();
_cachedSamplerBuffer = separateSamplerBuffer ? cachedSamplerBuffer.ToArray() : _cachedTextureBuffer;
}
/// <summary>
@ -287,39 +328,105 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <summary>
/// Checks if the cached texture or sampler pool has been modified since the last call to this method.
/// </summary>
/// <returns>True if any used entries of the pools might have been modified, false otherwise</returns>
public bool PoolsModified()
/// <returns>True if any used entries of the pool might have been modified, false otherwise</returns>
public bool TexturePoolModified()
{
bool texturePoolModified = _texturePool.WasModified(ref _texturePoolSequence);
bool samplerPoolModified = _samplerPool.WasModified(ref _samplerPoolSequence);
return TexturePool.WasModified(ref _texturePoolSequence);
}
// If both pools were not modified since the last check, we have nothing else to check.
if (!texturePoolModified && !samplerPoolModified)
{
return false;
}
/// <summary>
/// Checks if the cached texture or sampler pool has been modified since the last call to this method.
/// </summary>
/// <returns>True if any used entries of the pool might have been modified, false otherwise</returns>
public bool SamplerPoolModified()
{
return SamplerPool.WasModified(ref _samplerPoolSequence);
}
}
// If the pools were modified, let's check if any of the entries we care about changed.
/// <summary>
/// Array cache entry from constant buffer.
/// </summary>
private class CacheEntryFromBuffer : CacheEntry
{
/// <summary>
/// Key for this entry on the cache.
/// </summary>
public readonly CacheEntryFromBufferKey Key;
// Check if any of our cached textures changed on the pool.
foreach ((int textureId, Texture texture) in TextureIds)
{
if (_texturePool.GetCachedItem(textureId) != texture)
{
return true;
}
}
/// <summary>
/// Linked list node used on the texture bindings array cache.
/// </summary>
public LinkedListNode<CacheEntryFromBuffer> CacheNode;
// Check if any of our cached samplers changed on the pool.
foreach ((int samplerId, Sampler sampler) in SamplerIds)
{
if (_samplerPool.GetCachedItem(samplerId) != sampler)
{
return true;
}
}
/// <summary>
/// Timestamp set on the last use of the array by the cache.
/// </summary>
public int CacheTimestamp;
return false;
/// <summary>
/// All pool texture IDs along with their textures.
/// </summary>
public readonly Dictionary<int, (Texture, TextureDescriptor)> TextureIds;
/// <summary>
/// All pool sampler IDs along with their samplers.
/// </summary>
public readonly Dictionary<int, (Sampler, SamplerDescriptor)> SamplerIds;
private int[] _cachedTextureBuffer;
private int[] _cachedSamplerBuffer;
private int _lastSequenceNumber;
/// <summary>
/// Creates a new array cache entry.
/// </summary>
/// <param name="key">Key for this entry on the cache</param>
/// <param name="array">Backend texture array</param>
/// <param name="texturePool">Texture pool where the array textures are located</param>
/// <param name="samplerPool">Sampler pool where the array samplers are located</param>
public CacheEntryFromBuffer(ref CacheEntryFromBufferKey key, ITextureArray array, TexturePool texturePool, SamplerPool samplerPool) : base(array, texturePool, samplerPool)
{
Key = key;
_lastSequenceNumber = -1;
TextureIds = new Dictionary<int, (Texture, TextureDescriptor)>();
SamplerIds = new Dictionary<int, (Sampler, SamplerDescriptor)>();
}
/// <summary>
/// Creates a new array cache entry.
/// </summary>
/// <param name="key">Key for this entry on the cache</param>
/// <param name="array">Backend image array</param>
/// <param name="texturePool">Texture pool where the array textures are located</param>
/// <param name="samplerPool">Sampler pool where the array samplers are located</param>
public CacheEntryFromBuffer(ref CacheEntryFromBufferKey key, IImageArray array, TexturePool texturePool, SamplerPool samplerPool) : base(array, texturePool, samplerPool)
{
Key = key;
_lastSequenceNumber = -1;
TextureIds = new Dictionary<int, (Texture, TextureDescriptor)>();
SamplerIds = new Dictionary<int, (Sampler, SamplerDescriptor)>();
}
/// <inheritdoc/>
public override void Reset()
{
base.Reset();
TextureIds.Clear();
SamplerIds.Clear();
}
/// <summary>
/// Updates the cached constant buffer data.
/// </summary>
/// <param name="cachedTextureBuffer">Constant buffer data with the texture handles (and sampler handles, if they are combined)</param>
/// <param name="cachedSamplerBuffer">Constant buffer data with the sampler handles</param>
/// <param name="separateSamplerBuffer">Whether <paramref name="cachedTextureBuffer"/> and <paramref name="cachedSamplerBuffer"/> comes from different buffers</param>
public void UpdateData(ReadOnlySpan<int> cachedTextureBuffer, ReadOnlySpan<int> cachedSamplerBuffer, bool separateSamplerBuffer)
{
_cachedTextureBuffer = cachedTextureBuffer.ToArray();
_cachedSamplerBuffer = separateSamplerBuffer ? cachedSamplerBuffer.ToArray() : _cachedTextureBuffer;
}
/// <summary>
@ -380,10 +487,51 @@ namespace Ryujinx.Graphics.Gpu.Image
return true;
}
/// <summary>
/// Checks if the cached texture or sampler pool has been modified since the last call to this method.
/// </summary>
/// <returns>True if any used entries of the pools might have been modified, false otherwise</returns>
public bool PoolsModified()
{
bool texturePoolModified = TexturePoolModified();
bool samplerPoolModified = SamplerPoolModified();
// If both pools were not modified since the last check, we have nothing else to check.
if (!texturePoolModified && !samplerPoolModified)
{
return false;
}
// If the pools were modified, let's check if any of the entries we care about changed.
// Check if any of our cached textures changed on the pool.
foreach ((int textureId, (Texture texture, TextureDescriptor descriptor)) in TextureIds)
{
if (TexturePool.GetCachedItem(textureId) != texture ||
(texture == null && TexturePool.IsValidId(textureId) && !TexturePool.GetDescriptorRef(textureId).Equals(descriptor)))
{
return true;
}
}
// Check if any of our cached samplers changed on the pool.
foreach ((int samplerId, (Sampler sampler, SamplerDescriptor descriptor)) in SamplerIds)
{
if (SamplerPool.GetCachedItem(samplerId) != sampler ||
(sampler == null && SamplerPool.IsValidId(samplerId) && !SamplerPool.GetDescriptorRef(samplerId).Equals(descriptor)))
{
return true;
}
}
return false;
}
}
private readonly Dictionary<CacheEntryKey, CacheEntry> _cache;
private readonly LinkedList<CacheEntry> _lruCache;
private readonly Dictionary<CacheEntryFromBufferKey, CacheEntryFromBuffer> _cacheFromBuffer;
private readonly Dictionary<CacheEntryFromPoolKey, CacheEntry> _cacheFromPool;
private readonly LinkedList<CacheEntryFromBuffer> _lruCache;
private int _currentTimestamp;
@ -392,14 +540,13 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
/// <param name="context">GPU context</param>
/// <param name="channel">GPU channel</param>
/// <param name="isCompute">Whether the bindings will be used for compute or graphics pipelines</param>
public TextureBindingsArrayCache(GpuContext context, GpuChannel channel, bool isCompute)
public TextureBindingsArrayCache(GpuContext context, GpuChannel channel)
{
_context = context;
_channel = channel;
_isCompute = isCompute;
_cache = new Dictionary<CacheEntryKey, CacheEntry>();
_lruCache = new LinkedList<CacheEntry>();
_cacheFromBuffer = new Dictionary<CacheEntryFromBufferKey, CacheEntryFromBuffer>();
_cacheFromPool = new Dictionary<CacheEntryFromPoolKey, CacheEntry>();
_lruCache = new LinkedList<CacheEntryFromBuffer>();
}
/// <summary>
@ -457,15 +604,180 @@ namespace Ryujinx.Graphics.Gpu.Image
bool isImage,
SamplerIndex samplerIndex,
TextureBindingInfo bindingInfo)
{
if (IsDirectHandleType(bindingInfo.Handle))
{
UpdateFromPool(texturePool, samplerPool, stage, isImage, bindingInfo);
}
else
{
UpdateFromBuffer(texturePool, samplerPool, stage, stageIndex, textureBufferIndex, isImage, samplerIndex, bindingInfo);
}
}
/// <summary>
/// Updates a texture or image array bindings and textures from a texture or sampler pool.
/// </summary>
/// <param name="texturePool">Texture pool</param>
/// <param name="samplerPool">Sampler pool</param>
/// <param name="stage">Shader stage where the array is used</param>
/// <param name="isImage">Whether the array is a image or texture array</param>
/// <param name="bindingInfo">Array binding information</param>
private void UpdateFromPool(TexturePool texturePool, SamplerPool samplerPool, ShaderStage stage, bool isImage, TextureBindingInfo bindingInfo)
{
CacheEntry entry = GetOrAddEntry(texturePool, samplerPool, bindingInfo, isImage, out bool isNewEntry);
bool isSampler = bindingInfo.IsSamplerOnly;
bool poolModified = isSampler ? entry.SamplerPoolModified() : entry.TexturePoolModified();
bool isStore = bindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
bool resScaleUnsupported = bindingInfo.Flags.HasFlag(TextureUsageFlags.ResScaleUnsupported);
if (!poolModified && !isNewEntry && entry.ValidateTextures())
{
entry.SynchronizeMemory(isStore, resScaleUnsupported);
if (isImage)
{
_context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray);
}
else
{
_context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray);
}
return;
}
if (!isNewEntry)
{
entry.Reset();
}
int length = (isSampler ? samplerPool.MaximumId : texturePool.MaximumId) + 1;
length = Math.Min(length, bindingInfo.ArrayLength);
Format[] formats = isImage ? new Format[bindingInfo.ArrayLength] : null;
ISampler[] samplers = isImage ? null : new ISampler[bindingInfo.ArrayLength];
ITexture[] textures = new ITexture[bindingInfo.ArrayLength];
for (int index = 0; index < length; index++)
{
Texture texture = null;
Sampler sampler = null;
if (isSampler)
{
sampler = samplerPool?.Get(index);
}
else
{
ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(index, out texture);
if (texture != null)
{
entry.Textures[texture] = texture.InvalidatedSequence;
if (isStore)
{
texture.SignalModified();
}
if (resScaleUnsupported && texture.ScaleMode != TextureScaleMode.Blacklisted)
{
// Scaling textures used on arrays is currently not supported.
texture.BlacklistScale();
}
}
}
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
ISampler hostSampler = sampler?.GetHostSampler(texture);
Format format = bindingInfo.Format;
if (hostTexture != null && texture.Target == Target.TextureBuffer)
{
// Ensure that the buffer texture is using the correct buffer as storage.
// Buffers are frequently re-created to accommodate larger data, so we need to re-bind
// to ensure we're not using a old buffer that was already deleted.
if (isImage)
{
if (format == 0 && texture != null)
{
format = texture.Format;
}
_channel.BufferManager.SetBufferTextureStorage(entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format);
}
else
{
_channel.BufferManager.SetBufferTextureStorage(entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format);
}
}
else if (isImage)
{
if (format == 0 && texture != null)
{
format = texture.Format;
}
formats[index] = format;
textures[index] = hostTexture;
}
else
{
samplers[index] = hostSampler;
textures[index] = hostTexture;
}
}
if (isImage)
{
entry.ImageArray.SetFormats(0, formats);
entry.ImageArray.SetImages(0, textures);
_context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray);
}
else
{
entry.TextureArray.SetSamplers(0, samplers);
entry.TextureArray.SetTextures(0, textures);
_context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray);
}
}
/// <summary>
/// Updates a texture or image array bindings and textures from constant buffer handles.
/// </summary>
/// <param name="texturePool">Texture pool</param>
/// <param name="samplerPool">Sampler pool</param>
/// <param name="stage">Shader stage where the array is used</param>
/// <param name="stageIndex">Shader stage index where the array is used</param>
/// <param name="textureBufferIndex">Texture constant buffer index</param>
/// <param name="isImage">Whether the array is a image or texture array</param>
/// <param name="samplerIndex">Sampler handles source</param>
/// <param name="bindingInfo">Array binding information</param>
private void UpdateFromBuffer(
TexturePool texturePool,
SamplerPool samplerPool,
ShaderStage stage,
int stageIndex,
int textureBufferIndex,
bool isImage,
SamplerIndex samplerIndex,
TextureBindingInfo bindingInfo)
{
(textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, textureBufferIndex);
bool separateSamplerBuffer = textureBufferIndex != samplerBufferIndex;
bool isCompute = stage == ShaderStage.Compute;
ref BufferBounds textureBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, textureBufferIndex);
ref BufferBounds samplerBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, samplerBufferIndex);
ref BufferBounds textureBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, textureBufferIndex);
ref BufferBounds samplerBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, samplerBufferIndex);
CacheEntry entry = GetOrAddEntry(
CacheEntryFromBuffer entry = GetOrAddEntry(
texturePool,
samplerPool,
bindingInfo,
@ -589,8 +901,8 @@ namespace Ryujinx.Graphics.Gpu.Image
Sampler sampler = samplerPool?.Get(samplerId);
entry.TextureIds[textureId] = texture;
entry.SamplerIds[samplerId] = sampler;
entry.TextureIds[textureId] = (texture, descriptor);
entry.SamplerIds[samplerId] = (sampler, samplerPool?.GetDescriptorRef(samplerId) ?? default);
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
ISampler hostSampler = sampler?.GetHostSampler(texture);
@ -650,13 +962,12 @@ namespace Ryujinx.Graphics.Gpu.Image
}
/// <summary>
/// Gets a cached texture entry, or creates a new one if not found.
/// Gets a cached texture entry from pool, or creates a new one if not found.
/// </summary>
/// <param name="texturePool">Texture pool</param>
/// <param name="samplerPool">Sampler pool</param>
/// <param name="bindingInfo">Array binding information</param>
/// <param name="isImage">Whether the array is a image or texture array</param>
/// <param name="textureBufferBounds">Constant buffer bounds with the texture handles</param>
/// <param name="isNew">Whether a new entry was created, or an existing one was returned</param>
/// <returns>Cache entry</returns>
private CacheEntry GetOrAddEntry(
@ -664,17 +975,11 @@ namespace Ryujinx.Graphics.Gpu.Image
SamplerPool samplerPool,
TextureBindingInfo bindingInfo,
bool isImage,
ref BufferBounds textureBufferBounds,
out bool isNew)
{
CacheEntryKey key = new CacheEntryKey(
isImage,
bindingInfo,
texturePool,
samplerPool,
ref textureBufferBounds);
CacheEntryFromPoolKey key = new CacheEntryFromPoolKey(isImage, bindingInfo, texturePool, samplerPool);
isNew = !_cache.TryGetValue(key, out CacheEntry entry);
isNew = !_cacheFromPool.TryGetValue(key, out CacheEntry entry);
if (isNew)
{
@ -684,13 +989,61 @@ namespace Ryujinx.Graphics.Gpu.Image
{
IImageArray array = _context.Renderer.CreateImageArray(arrayLength, bindingInfo.Target == Target.TextureBuffer);
_cache.Add(key, entry = new CacheEntry(ref key, array, texturePool, samplerPool));
_cacheFromPool.Add(key, entry = new CacheEntry(array, texturePool, samplerPool));
}
else
{
ITextureArray array = _context.Renderer.CreateTextureArray(arrayLength, bindingInfo.Target == Target.TextureBuffer);
_cache.Add(key, entry = new CacheEntry(ref key, array, texturePool, samplerPool));
_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>
private void RemoveLeastUsedEntries()
{
LinkedListNode<CacheEntry> nextNode = _lruCache.First;
LinkedListNode<CacheEntryFromBuffer> nextNode = _lruCache.First;
while (nextNode != null && _currentTimestamp - nextNode.Value.CacheTimestamp >= MinDeltaForRemoval)
{
LinkedListNode<CacheEntry> toRemove = nextNode;
LinkedListNode<CacheEntryFromBuffer> toRemove = nextNode;
nextNode = nextNode.Next;
_cache.Remove(toRemove.Value.Key);
_cacheFromBuffer.Remove(toRemove.Value.Key);
_lruCache.Remove(toRemove);
}
}
/// <summary>
/// Removes all cached texture arrays matching the specified texture pool.
/// </summary>
/// <param name="pool">Texture pool</param>
public void RemoveAllWithPool<T>(IPool<T> pool)
{
List<CacheEntryFromPoolKey> keysToRemove = null;
foreach (CacheEntryFromPoolKey key in _cacheFromPool.Keys)
{
if (key.MatchesPool(pool))
{
(keysToRemove ??= new()).Add(key);
}
}
if (keysToRemove != null)
{
foreach (CacheEntryFromPoolKey key in keysToRemove)
{
_cacheFromPool.Remove(key);
}
}
}
/// <summary>
/// Checks if a handle indicates the binding should have all its textures sourced directly from a pool.
/// </summary>
/// <param name="handle">Handle to check</param>
/// <returns>True if the handle represents direct pool access, false otherwise</returns>
private static bool IsDirectHandleType(int handle)
{
(_, _, TextureHandleType type) = TextureHandle.UnpackOffsets(handle);
return type == TextureHandleType.Direct;
}
}
}

View file

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

View file

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

View file

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

View file

@ -109,6 +109,13 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
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/>
public SamplerType QuerySamplerType(int handle, int cbufSlot)
{
@ -117,6 +124,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
}
/// <inheritdoc/>
/// <exception cref="DiskCacheLoadException">Constant buffer derived length is not available on the cache</exception>
public int QueryTextureArrayLengthFromBuffer(int slot)
{
if (!_oldSpecState.TextureArrayFromBufferRegistered(_stageIndex, 0, slot))
@ -130,6 +138,13 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return arrayLength;
}
/// <inheritdoc/>
/// <exception cref="DiskCacheLoadException">Pool length is not available on the cache</exception>
public int QueryTextureArrayLengthFromPool()
{
return QueryArrayLengthFromPool(isSampler: false);
}
/// <inheritdoc/>
public TextureFormat QueryTextureFormat(int handle, int cbufSlot)
{
@ -170,6 +185,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
}
/// <inheritdoc/>
/// <exception cref="DiskCacheLoadException">Texture information is not available on the cache</exception>
public void RegisterTexture(int handle, int cbufSlot)
{
if (!_oldSpecState.TextureRegistered(_stageIndex, handle, cbufSlot))
@ -182,5 +198,24 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
bool coordNormalized = _oldSpecState.GetCoordNormalized(_stageIndex, handle, cbufSlot);
_newSpecState.RegisterTexture(_stageIndex, handle, cbufSlot, format, formatSrgb, target, coordNormalized);
}
/// <summary>
/// Gets the cached texture or sampler pool capacity.
/// </summary>
/// <param name="isSampler">True to get sampler pool length, false for texture pool length</param>
/// <returns>Pool length</returns>
/// <exception cref="DiskCacheLoadException">Pool length is not available on the cache</exception>
private int QueryArrayLengthFromPool(bool isSampler)
{
if (!_oldSpecState.TextureArrayFromPoolRegistered(isSampler))
{
throw new DiskCacheLoadException(DiskCacheLoadResult.MissingTextureArrayLength);
}
int arrayLength = _oldSpecState.GetTextureArrayFromPoolLength(isSampler);
_newSpecState.RegisterTextureArrayLengthFromPool(isSampler, arrayLength);
return arrayLength;
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

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.
/// </remarks>
/// <param name="channel">GPU channel</param>
/// <param name="samplerPoolMaximumId">Maximum ID that an entry in the sampler pool may have</param>
/// <param name="poolState">Texture pool state</param>
/// <param name="computeState">Compute engine state</param>
/// <param name="gpuVa">GPU virtual address of the binary shader code</param>
/// <returns>Compiled compute shader code</returns>
public CachedShaderProgram GetComputeShader(
GpuChannel channel,
int samplerPoolMaximumId,
GpuChannelPoolState poolState,
GpuChannelComputeState computeState,
ulong gpuVa)
@ -214,7 +216,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
ShaderSpecializationState specState = new(ref computeState);
GpuAccessorState gpuAccessorState = new(poolState, computeState, default, specState);
GpuAccessorState gpuAccessorState = new(samplerPoolMaximumId, poolState, computeState, default, specState);
GpuAccessor gpuAccessor = new(_context, channel, gpuAccessorState);
gpuAccessor.InitializeReservedCounts(tfEnabled: false, vertexAsCompute: false);
@ -291,6 +293,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="state">GPU state</param>
/// <param name="pipeline">Pipeline state</param>
/// <param name="channel">GPU channel</param>
/// <param name="samplerPoolMaximumId">Maximum ID that an entry in the sampler pool may have</param>
/// <param name="poolState">Texture pool state</param>
/// <param name="graphicsState">3D engine state</param>
/// <param name="addresses">Addresses of the shaders for each stage</param>
@ -299,6 +302,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
ref ThreedClassState state,
ref ProgramPipelineState pipeline,
GpuChannel channel,
int samplerPoolMaximumId,
ref GpuChannelPoolState poolState,
ref GpuChannelGraphicsState graphicsState,
ShaderAddresses addresses)
@ -319,7 +323,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
UpdatePipelineInfo(ref state, ref pipeline, graphicsState, channel);
ShaderSpecializationState specState = new(ref graphicsState, ref pipeline, transformFeedbackDescriptors);
GpuAccessorState gpuAccessorState = new(poolState, default, graphicsState, specState, transformFeedbackDescriptors);
GpuAccessorState gpuAccessorState = new(samplerPoolMaximumId, poolState, default, graphicsState, specState, transformFeedbackDescriptors);
ReadOnlySpan<ulong> addressesSpan = addresses.AsSpan();

View file

@ -185,11 +185,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
if (texture.ArrayLength > 1)
{
bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer;
ResourceType type = isBuffer
? (isImage ? ResourceType.BufferImage : ResourceType.BufferTexture)
: (isImage ? ResourceType.Image : ResourceType.TextureAndSampler);
ResourceType type = GetTextureResourceType(texture, isImage);
_resourceDescriptors[setIndex].Add(new ResourceDescriptor(texture.Binding, texture.ArrayLength, type, stages));
}
@ -242,16 +238,38 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
foreach (TextureDescriptor texture in textures)
{
bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer;
ResourceType type = isBuffer
? (isImage ? ResourceType.BufferImage : ResourceType.BufferTexture)
: (isImage ? ResourceType.Image : ResourceType.TextureAndSampler);
ResourceType type = GetTextureResourceType(texture, isImage);
_resourceUsages[setIndex].Add(new ResourceUsage(texture.Binding, texture.ArrayLength, type, stages));
}
}
private static ResourceType GetTextureResourceType(TextureDescriptor texture, bool isImage)
{
bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer;
if (isBuffer)
{
return isImage ? ResourceType.BufferImage : ResourceType.BufferTexture;
}
else if (isImage)
{
return ResourceType.Image;
}
else if (texture.Type == SamplerType.None)
{
return ResourceType.Sampler;
}
else if (texture.Separate)
{
return ResourceType.Texture;
}
else
{
return ResourceType.TextureAndSampler;
}
}
/// <summary>
/// Creates a new shader information structure from the added information.
/// </summary>

View file

@ -31,6 +31,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
PrimitiveTopology = 1 << 1,
TransformFeedback = 1 << 3,
TextureArrayFromBuffer = 1 << 4,
TextureArrayFromPool = 1 << 5,
}
private QueriedStateFlags _queriedState;
@ -154,7 +155,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
private readonly Dictionary<TextureKey, Box<TextureSpecializationState>> _textureSpecialization;
private readonly Dictionary<TextureKey, int> _textureArraySpecialization;
private readonly Dictionary<TextureKey, int> _textureArrayFromBufferSpecialization;
private readonly Dictionary<bool, int> _textureArrayFromPoolSpecialization;
private KeyValuePair<TextureKey, Box<TextureSpecializationState>>[] _allTextures;
private Box<TextureSpecializationState>[][] _textureByBinding;
private Box<TextureSpecializationState>[][] _imageByBinding;
@ -165,7 +167,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
private ShaderSpecializationState()
{
_textureSpecialization = new Dictionary<TextureKey, Box<TextureSpecializationState>>();
_textureArraySpecialization = new Dictionary<TextureKey, int>();
_textureArrayFromBufferSpecialization = new Dictionary<TextureKey, int>();
_textureArrayFromPoolSpecialization = new Dictionary<bool, int>();
}
/// <summary>
@ -327,7 +330,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
/// <summary>
/// Indicates that the coordinate normalization state of a given texture was used during the shader translation process.
/// Registers the length of a texture array calculated from a constant buffer size.
/// </summary>
/// <param name="stageIndex">Shader stage where the texture is used</param>
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
@ -335,10 +338,21 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="length">Number of elements in the texture array</param>
public void RegisterTextureArrayLengthFromBuffer(int stageIndex, int handle, int cbufSlot, int length)
{
_textureArraySpecialization[new TextureKey(stageIndex, handle, cbufSlot)] = length;
_textureArrayFromBufferSpecialization[new TextureKey(stageIndex, handle, cbufSlot)] = length;
_queriedState |= QueriedStateFlags.TextureArrayFromBuffer;
}
/// <summary>
/// Registers the length of a texture array calculated from a texture or sampler pool capacity.
/// </summary>
/// <param name="isSampler">True for sampler pool, false for texture pool</param>
/// <param name="length">Number of elements in the texture array</param>
public void RegisterTextureArrayLengthFromPool(bool isSampler, int length)
{
_textureArrayFromPoolSpecialization[isSampler] = length;
_queriedState |= QueriedStateFlags.TextureArrayFromPool;
}
/// <summary>
/// Indicates that the format of a given texture was used during the shader translation process.
/// </summary>
@ -385,7 +399,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
/// <summary>
/// Checks if a given texture was registerd on this specialization state.
/// Checks if a given texture was registered on this specialization state.
/// </summary>
/// <param name="stageIndex">Shader stage where the texture is used</param>
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
@ -396,14 +410,25 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
/// <summary>
/// Checks if a given texture array (from constant buffer) was registerd on this specialization state.
/// Checks if a given texture array (from constant buffer) was registered on this specialization state.
/// </summary>
/// <param name="stageIndex">Shader stage where the texture is used</param>
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
/// <returns>True if the length for the given buffer and stage exists, false otherwise</returns>
public bool TextureArrayFromBufferRegistered(int stageIndex, int handle, int cbufSlot)
{
return _textureArraySpecialization.ContainsKey(new TextureKey(stageIndex, handle, cbufSlot));
return _textureArrayFromBufferSpecialization.ContainsKey(new TextureKey(stageIndex, handle, cbufSlot));
}
/// <summary>
/// Checks if a given texture array (from a sampler pool or texture pool) was registered on this specialization state.
/// </summary>
/// <param name="isSampler">True for sampler pool, false for texture pool</param>
/// <returns>True if the length for the given pool, false otherwise</returns>
public bool TextureArrayFromPoolRegistered(bool isSampler)
{
return _textureArrayFromPoolSpecialization.ContainsKey(isSampler);
}
/// <summary>
@ -412,6 +437,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="stageIndex">Shader stage where the texture is used</param>
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
/// <returns>Format and sRGB tuple</returns>
public (uint, bool) GetFormat(int stageIndex, int handle, int cbufSlot)
{
TextureSpecializationState state = GetTextureSpecState(stageIndex, handle, cbufSlot).Value;
@ -424,6 +450,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="stageIndex">Shader stage where the texture is used</param>
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
/// <returns>Texture target</returns>
public TextureTarget GetTextureTarget(int stageIndex, int handle, int cbufSlot)
{
return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.TextureTarget;
@ -435,6 +462,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="stageIndex">Shader stage where the texture is used</param>
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
/// <returns>True if coordinates are normalized, false otherwise</returns>
public bool GetCoordNormalized(int stageIndex, int handle, int cbufSlot)
{
return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.CoordNormalized;
@ -446,9 +474,20 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="stageIndex">Shader stage where the texture is used</param>
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
/// <returns>Texture array length</returns>
public int GetTextureArrayFromBufferLength(int stageIndex, int handle, int cbufSlot)
{
return _textureArraySpecialization[new TextureKey(stageIndex, handle, cbufSlot)];
return _textureArrayFromBufferSpecialization[new TextureKey(stageIndex, handle, cbufSlot)];
}
/// <summary>
/// Gets the recorded length of a given texture array (from a sampler or texture pool).
/// </summary>
/// <param name="isSampler">True to get the sampler pool length, false to get the texture pool length</param>
/// <returns>Texture array length</returns>
public int GetTextureArrayFromPoolLength(bool isSampler)
{
return _textureArrayFromPoolSpecialization[isSampler];
}
/// <summary>
@ -894,7 +933,23 @@ namespace Ryujinx.Graphics.Gpu.Shader
dataReader.ReadWithMagicAndSize(ref textureKey, TexkMagic);
dataReader.Read(ref length);
specState._textureArraySpecialization[textureKey] = length;
specState._textureArrayFromBufferSpecialization[textureKey] = length;
}
}
if (specState._queriedState.HasFlag(QueriedStateFlags.TextureArrayFromPool))
{
dataReader.Read(ref count);
for (int index = 0; index < count; index++)
{
bool textureKey = default;
int length = 0;
dataReader.ReadWithMagicAndSize(ref textureKey, TexkMagic);
dataReader.Read(ref length);
specState._textureArrayFromPoolSpecialization[textureKey] = length;
}
}
@ -965,10 +1020,25 @@ namespace Ryujinx.Graphics.Gpu.Shader
if (_queriedState.HasFlag(QueriedStateFlags.TextureArrayFromBuffer))
{
count = (ushort)_textureArraySpecialization.Count;
count = (ushort)_textureArrayFromBufferSpecialization.Count;
dataWriter.Write(ref count);
foreach (var kv in _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 length = kv.Value;

View file

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

View file

@ -6,7 +6,6 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Numerics;
namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{
@ -352,7 +351,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
arrayDecl = "[]";
}
string samplerTypeName = definition.Type.ToGlslSamplerType();
string samplerTypeName = definition.Separate ? definition.Type.ToGlslTextureType() : definition.Type.ToGlslSamplerType();
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)
{
TextureDefinition definition = context.Properties.Textures[texOp.Binding];
string name = definition.Name;
TextureDefinition textureDefinition = context.Properties.Textures[texOp.Binding];
string name = textureDefinition.Name;
if (definition.ArrayLength != 1)
if (textureDefinition.ArrayLength != 1)
{
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;
}

View file

@ -160,37 +160,49 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
int setIndex = context.TargetApi == TargetApi.Vulkan ? sampler.Set : 0;
var dim = (sampler.Type & SamplerType.Mask) switch
SpvInstruction imageType;
SpvInstruction sampledImageType;
if (sampler.Type != SamplerType.None)
{
SamplerType.Texture1D => Dim.Dim1D,
SamplerType.Texture2D => Dim.Dim2D,
SamplerType.Texture3D => Dim.Dim3D,
SamplerType.TextureCube => Dim.Cube,
SamplerType.TextureBuffer => Dim.Buffer,
_ => throw new InvalidOperationException($"Invalid sampler type \"{sampler.Type & SamplerType.Mask}\"."),
};
var dim = (sampler.Type & SamplerType.Mask) switch
{
SamplerType.Texture1D => Dim.Dim1D,
SamplerType.Texture2D => Dim.Dim2D,
SamplerType.Texture3D => Dim.Dim3D,
SamplerType.TextureCube => Dim.Cube,
SamplerType.TextureBuffer => Dim.Buffer,
_ => throw new InvalidOperationException($"Invalid sampler type \"{sampler.Type & SamplerType.Mask}\"."),
};
var imageType = context.TypeImage(
context.TypeFP32(),
dim,
sampler.Type.HasFlag(SamplerType.Shadow),
sampler.Type.HasFlag(SamplerType.Array),
sampler.Type.HasFlag(SamplerType.Multisample),
1,
ImageFormat.Unknown);
imageType = context.TypeImage(
context.TypeFP32(),
dim,
sampler.Type.HasFlag(SamplerType.Shadow),
sampler.Type.HasFlag(SamplerType.Array),
sampler.Type.HasFlag(SamplerType.Multisample),
1,
ImageFormat.Unknown);
var sampledImageType = context.TypeSampledImage(imageType);
var sampledImagePointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageType);
sampledImageType = context.TypeSampledImage(imageType);
}
else
{
imageType = sampledImageType = context.TypeSampler();
}
var sampledOrSeparateImageType = sampler.Separate ? imageType : sampledImageType;
var sampledImagePointerType = context.TypePointer(StorageClass.UniformConstant, sampledOrSeparateImageType);
var sampledImageArrayPointerType = sampledImagePointerType;
if (sampler.ArrayLength == 0)
{
var sampledImageArrayType = context.TypeRuntimeArray(sampledImageType);
var sampledImageArrayType = context.TypeRuntimeArray(sampledOrSeparateImageType);
sampledImageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageArrayType);
}
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);
}

View file

@ -838,16 +838,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
}
SamplerDeclaration declaration = context.Samplers[texOp.Binding];
SpvInstruction image = declaration.Image;
if (declaration.IsIndexed)
{
SpvInstruction textureIndex = Src(AggregateType.S32);
image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex);
}
image = context.Load(declaration.SampledImageType, image);
SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
int pCount = texOp.Type.GetDimensions();
@ -1171,16 +1162,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
}
SamplerDeclaration declaration = context.Samplers[texOp.Binding];
SpvInstruction image = declaration.Image;
if (declaration.IsIndexed)
{
SpvInstruction textureIndex = Src(AggregateType.S32);
image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex);
}
image = context.Load(declaration.SampledImageType, image);
SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
int coordsCount = texOp.Type.GetDimensions();
@ -1449,17 +1431,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
AstTextureOperation texOp = (AstTextureOperation)operation;
int srcIndex = 0;
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);
SpvInstruction result = context.ImageQuerySamples(context.TypeS32(), image);
@ -1471,17 +1447,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
AstTextureOperation texOp = (AstTextureOperation)operation;
int srcIndex = 0;
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);
if (texOp.Index == 3)
@ -1506,8 +1476,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (hasLod)
{
int lodSrcIndex = declaration.IsIndexed ? 1 : 0;
var lod = context.GetS32(operation.GetSource(lodSrcIndex));
var lod = context.GetS32(operation.GetSource(srcIndex));
result = context.ImageQuerySizeLod(resultType, image, lod);
}
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(
CodeGenContext context,
AstOperation operation,

View file

@ -26,13 +26,6 @@ namespace Ryujinx.Graphics.Shader
/// <returns>Span of the memory location</returns>
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>
/// Queries the binding number of a constant buffer.
/// </summary>
@ -298,6 +291,15 @@ namespace Ryujinx.Graphics.Shader
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>
/// Queries host GPU shader ballot support.
/// </summary>
@ -388,6 +390,12 @@ namespace Ryujinx.Graphics.Shader
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>
/// Queries sampler type information.
/// </summary>
@ -399,6 +407,19 @@ namespace Ryujinx.Graphics.Shader
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>
/// Queries texture coordinate normalization information.
/// </summary>

View file

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

View file

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

View file

@ -69,6 +69,7 @@ namespace Ryujinx.Graphics.Shader
{
string typeName = (type & SamplerType.Mask) switch
{
SamplerType.None => "sampler",
SamplerType.Texture1D => "sampler1D",
SamplerType.TextureBuffer => "samplerBuffer",
SamplerType.Texture2D => "sampler2D",
@ -95,6 +96,31 @@ namespace Ryujinx.Graphics.Shader
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)
{
string typeName = (type & SamplerType.Mask) switch

View file

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

View file

@ -169,7 +169,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
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);

View file

@ -5,25 +5,39 @@ namespace Ryujinx.Graphics.Shader
public int Set { get; }
public int Binding { get; }
public int ArrayLength { get; }
public bool Separate { get; }
public string Name { get; }
public SamplerType Type { get; }
public TextureFormat Format { 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;
Binding = binding;
ArrayLength = arrayLength;
Separate = separate;
Name = name;
Type = type;
Format = format;
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)
{
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 ArrayLength;
public readonly bool Separate;
public readonly TextureUsageFlags Flags;
public TextureDescriptor(
@ -22,6 +24,7 @@ namespace Ryujinx.Graphics.Shader
int cbufSlot,
int handleIndex,
int arrayLength,
bool separate,
TextureUsageFlags flags)
{
Binding = binding;
@ -30,6 +33,7 @@ namespace Ryujinx.Graphics.Shader
CbufSlot = cbufSlot;
HandleIndex = handleIndex;
ArrayLength = arrayLength;
Separate = separate;
Flags = flags;
}
}

View file

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

View file

@ -1,6 +1,7 @@
using Ryujinx.Graphics.Shader.Instructions;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.Translation.Optimizations
@ -31,7 +32,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
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.
// 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)
{
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;
// 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)
{

View file

@ -29,7 +29,7 @@ namespace Ryujinx.Graphics.Shader.Translation
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
{
@ -225,7 +225,8 @@ namespace Ryujinx.Graphics.Shader.Translation
TextureFlags flags,
int cbufSlot,
int handle,
int arrayLength = 1)
int arrayLength = 1,
bool separate = false)
{
inst &= Instruction.Mask;
bool isImage = inst.IsImage();
@ -239,7 +240,18 @@ namespace Ryujinx.Graphics.Shader.Translation
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);
@ -256,9 +268,10 @@ namespace Ryujinx.Graphics.Shader.Translation
bool intCoords,
bool write,
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 usageFlags = TextureUsageFlags.None;
@ -290,7 +303,7 @@ namespace Ryujinx.Graphics.Shader.Translation
// 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.
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()
{
AccurateType = accurateType,
@ -332,6 +345,10 @@ namespace Ryujinx.Graphics.Shader.Translation
? $"{prefix}_tcb_{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
{
nameSuffix = cbufSlot < 0 ? $"{prefix}_tcb_{handle:X}" : $"{prefix}_cb{cbufSlot}_{handle:X}";
@ -341,6 +358,7 @@ namespace Ryujinx.Graphics.Shader.Translation
isImage ? 3 : 2,
binding,
arrayLength,
separate,
$"{_stagePrefix}_{nameSuffix}",
meta.Type,
info.Format,
@ -495,6 +513,7 @@ namespace Ryujinx.Graphics.Shader.Translation
info.CbufSlot,
info.Handle,
info.ArrayLength,
info.Separate,
meta.UsageFlags));
}
@ -514,6 +533,7 @@ namespace Ryujinx.Graphics.Shader.Translation
info.CbufSlot,
info.Handle,
info.ArrayLength,
info.Separate,
meta.UsageFlags));
}
}

View file

@ -413,7 +413,7 @@ namespace Ryujinx.Graphics.Shader.Translation
if (Stage == ShaderStage.Vertex)
{
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);
int inputMap = _program.AttributeUsage.UsedInputAttributes;
@ -422,7 +422,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{
int location = BitOperations.TrailingZeroCount(inputMap);
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);
inputMap &= ~(1 << location);
@ -431,7 +431,7 @@ namespace Ryujinx.Graphics.Shader.Translation
else if (Stage == ShaderStage.Geometry)
{
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);
int geometryVbOutputSbBinding = resourceManager.Reservations.GeometryVertexOutputStorageBufferBinding;

View file

@ -43,11 +43,11 @@ namespace Ryujinx.Graphics.Vulkan
int binding = segment.Binding;
int count = segment.Count;
if (setIndex == PipelineBase.UniformSetIndex)
if (IsBufferType(segment.Type))
{
entries[seg] = new DescriptorUpdateTemplateEntry()
{
DescriptorType = DescriptorType.UniformBuffer,
DescriptorType = segment.Type.Convert(),
DstBinding = (uint)binding,
DescriptorCount = (uint)count,
Offset = structureOffset,
@ -56,76 +56,31 @@ namespace Ryujinx.Graphics.Vulkan
structureOffset += (nuint)(Unsafe.SizeOf<DescriptorBufferInfo>() * count);
}
else if (setIndex == PipelineBase.StorageSetIndex)
else if (IsBufferTextureType(segment.Type))
{
entries[seg] = new DescriptorUpdateTemplateEntry()
{
DescriptorType = DescriptorType.StorageBuffer,
DescriptorType = segment.Type.Convert(),
DstBinding = (uint)binding,
DescriptorCount = (uint)count,
Offset = structureOffset,
Stride = (nuint)Unsafe.SizeOf<DescriptorBufferInfo>()
Stride = (nuint)Unsafe.SizeOf<BufferView>()
};
structureOffset += (nuint)(Unsafe.SizeOf<DescriptorBufferInfo>() * count);
structureOffset += (nuint)(Unsafe.SizeOf<BufferView>() * count);
}
else if (setIndex == PipelineBase.TextureSetIndex)
else
{
if (segment.Type != ResourceType.BufferTexture)
entries[seg] = new DescriptorUpdateTemplateEntry()
{
entries[seg] = new DescriptorUpdateTemplateEntry()
{
DescriptorType = DescriptorType.CombinedImageSampler,
DstBinding = (uint)binding,
DescriptorCount = (uint)count,
Offset = structureOffset,
Stride = (nuint)Unsafe.SizeOf<DescriptorImageInfo>()
};
DescriptorType = segment.Type.Convert(),
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,
DescriptorCount = (uint)count,
Offset = structureOffset,
Stride = (nuint)Unsafe.SizeOf<BufferView>()
};
structureOffset += (nuint)(Unsafe.SizeOf<BufferView>() * count);
}
}
else if (setIndex == PipelineBase.ImageSetIndex)
{
if (segment.Type != ResourceType.BufferImage)
{
entries[seg] = new DescriptorUpdateTemplateEntry()
{
DescriptorType = DescriptorType.StorageImage,
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.StorageTexelBuffer,
DstBinding = (uint)binding,
DescriptorCount = (uint)count,
Offset = structureOffset,
Stride = (nuint)Unsafe.SizeOf<BufferView>()
};
structureOffset += (nuint)(Unsafe.SizeOf<BufferView>() * count);
}
structureOffset += (nuint)(Unsafe.SizeOf<DescriptorImageInfo>() * count);
}
}
@ -237,6 +192,16 @@ namespace Ryujinx.Graphics.Vulkan
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()
{
_gd.Api.DestroyDescriptorUpdateTemplate(_device, Template, null);

View file

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

View file

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