aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs19
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs24
-rw-r--r--Ryujinx.Graphics.Gpu/Image/Pool.cs42
-rw-r--r--Ryujinx.Graphics.Gpu/Image/Sampler.cs7
-rw-r--r--Ryujinx.Graphics.Gpu/Image/SamplerPool.cs35
-rw-r--r--Ryujinx.Graphics.Gpu/Image/Texture.cs8
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs290
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureManager.cs56
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TexturePool.cs92
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/BufferManager.cs19
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/CachedShaderProgram.cs2
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs4
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs4
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs227
-rw-r--r--Ryujinx.Graphics.Shader/TextureHandle.cs59
15 files changed, 737 insertions, 151 deletions
diff --git a/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs b/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs
index 87c14da8..a1a9b481 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs
@@ -188,6 +188,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
_channel.BufferManager.SetComputeStorageBufferBindings(info.SBuffers);
_channel.BufferManager.SetComputeUniformBufferBindings(info.CBuffers);
+ int maxTextureBinding = -1;
+ int maxImageBinding = -1;
+
TextureBindingInfo[] textureBindings = _channel.TextureManager.RentComputeTextureBindings(info.Textures.Count);
for (int index = 0; index < info.Textures.Count; index++)
@@ -202,6 +205,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
descriptor.CbufSlot,
descriptor.HandleIndex,
descriptor.Flags);
+
+ if (descriptor.Binding > maxTextureBinding)
+ {
+ maxTextureBinding = descriptor.Binding;
+ }
}
TextureBindingInfo[] imageBindings = _channel.TextureManager.RentComputeImageBindings(info.Images.Count);
@@ -220,9 +228,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
descriptor.CbufSlot,
descriptor.HandleIndex,
descriptor.Flags);
+
+ if (descriptor.Binding > maxImageBinding)
+ {
+ maxImageBinding = descriptor.Binding;
+ }
}
- _channel.TextureManager.CommitComputeBindings();
+ _channel.TextureManager.SetComputeMaxBindings(maxTextureBinding, maxImageBinding);
+
+ // Should never return false for mismatching spec state, since the shader was fetched above.
+ _channel.TextureManager.CommitComputeBindings(cs.SpecializationState);
+
_channel.BufferManager.CommitComputeBindings();
_context.Renderer.Pipeline.DispatchCompute(qmd.CtaRasterWidth, qmd.CtaRasterHeight, qmd.CtaRasterDepth);
diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
index f648479b..c64c760a 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
@@ -201,7 +201,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
// of the shader for the new state.
if (_shaderSpecState != null)
{
- if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState(), GetGraphicsState()))
+ if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState(), GetGraphicsState(), false))
{
ForceShaderUpdate();
}
@@ -275,7 +275,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
{
UpdateStorageBuffers();
- _channel.TextureManager.CommitGraphicsBindings();
+ if (!_channel.TextureManager.CommitGraphicsBindings(_shaderSpecState))
+ {
+ // Shader must be reloaded.
+ UpdateShaderState();
+ }
+
_channel.BufferManager.CommitGraphicsBindings();
}
@@ -1150,6 +1155,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
return;
}
+ int maxTextureBinding = -1;
+ int maxImageBinding = -1;
+
Span<TextureBindingInfo> textureBindings = _channel.TextureManager.RentGraphicsTextureBindings(stage, info.Textures.Count);
if (info.UsesRtLayer)
@@ -1169,6 +1177,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
descriptor.CbufSlot,
descriptor.HandleIndex,
descriptor.Flags);
+
+ if (descriptor.Binding > maxTextureBinding)
+ {
+ maxTextureBinding = descriptor.Binding;
+ }
}
TextureBindingInfo[] imageBindings = _channel.TextureManager.RentGraphicsImageBindings(stage, info.Images.Count);
@@ -1187,8 +1200,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
descriptor.CbufSlot,
descriptor.HandleIndex,
descriptor.Flags);
+
+ if (descriptor.Binding > maxImageBinding)
+ {
+ maxImageBinding = descriptor.Binding;
+ }
}
+ _channel.TextureManager.SetGraphicsMaxBindings(maxTextureBinding, maxImageBinding);
+
_channel.BufferManager.SetGraphicsStorageBufferBindings(stage, info.SBuffers);
_channel.BufferManager.SetGraphicsUniformBufferBindings(stage, info.CBuffers);
}
diff --git a/Ryujinx.Graphics.Gpu/Image/Pool.cs b/Ryujinx.Graphics.Gpu/Image/Pool.cs
index f54ce1d7..8e210513 100644
--- a/Ryujinx.Graphics.Gpu/Image/Pool.cs
+++ b/Ryujinx.Graphics.Gpu/Image/Pool.cs
@@ -1,6 +1,7 @@
using Ryujinx.Cpu.Tracking;
using Ryujinx.Graphics.Gpu.Memory;
using System;
+using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Image
{
@@ -16,6 +17,7 @@ namespace Ryujinx.Graphics.Gpu.Image
protected GpuContext Context;
protected PhysicalMemory PhysicalMemory;
protected int SequenceNumber;
+ protected int ModifiedSequenceNumber;
protected T1[] Items;
protected T2[] DescriptorCache;
@@ -41,6 +43,9 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly CpuMultiRegionHandle _memoryTracking;
private readonly Action<ulong, ulong> _modifiedDelegate;
+ private int _modifiedSequenceOffset;
+ private bool _modified;
+
/// <summary>
/// Creates a new instance of the GPU resource pool.
/// </summary>
@@ -80,6 +85,16 @@ namespace Ryujinx.Graphics.Gpu.Image
}
/// <summary>
+ /// Gets a reference to the descriptor for a given ID.
+ /// </summary>
+ /// <param name="id">ID of the descriptor. This is effectively a zero-based index</param>
+ /// <returns>A reference to the descriptor</returns>
+ public ref readonly T2 GetDescriptorRef(int id)
+ {
+ return ref MemoryMarshal.Cast<byte, T2>(PhysicalMemory.GetSpan(Address + (ulong)id * DescriptorSize, DescriptorSize))[0];
+ }
+
+ /// <summary>
/// Gets the GPU resource with the given ID.
/// </summary>
/// <param name="id">ID of the resource. This is effectively a zero-based index</param>
@@ -93,7 +108,13 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
public void SynchronizeMemory()
{
+ _modified = false;
_memoryTracking.QueryModified(_modifiedDelegate);
+
+ if (_modified)
+ {
+ UpdateModifiedSequence();
+ }
}
/// <summary>
@@ -103,6 +124,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="mSize">Size of the modified region</param>
private void RegionModified(ulong mAddress, ulong mSize)
{
+ _modified = true;
+
if (mAddress < Address)
{
mAddress = Address;
@@ -119,6 +142,15 @@ namespace Ryujinx.Graphics.Gpu.Image
}
/// <summary>
+ /// Updates the modified sequence number using the current sequence number and offset,
+ /// indicating that it has been modified.
+ /// </summary>
+ protected void UpdateModifiedSequence()
+ {
+ ModifiedSequenceNumber = SequenceNumber + _modifiedSequenceOffset;
+ }
+
+ /// <summary>
/// An action to be performed when a precise memory access occurs to this resource.
/// Makes sure that the dirty flags are checked.
/// </summary>
@@ -129,6 +161,16 @@ namespace Ryujinx.Graphics.Gpu.Image
{
if (write && Context.SequenceNumber == SequenceNumber)
{
+ if (ModifiedSequenceNumber == SequenceNumber + _modifiedSequenceOffset)
+ {
+ // The modified sequence number is offset when PreciseActions occur so that
+ // users checking it will see an increment and know the pool has changed since
+ // their last look, even though the main SequenceNumber has not been changed.
+
+ _modifiedSequenceOffset++;
+ }
+
+ // Force the pool to be checked again the next time it is used.
SequenceNumber--;
}
diff --git a/Ryujinx.Graphics.Gpu/Image/Sampler.cs b/Ryujinx.Graphics.Gpu/Image/Sampler.cs
index f8923d34..b70ac9eb 100644
--- a/Ryujinx.Graphics.Gpu/Image/Sampler.cs
+++ b/Ryujinx.Graphics.Gpu/Image/Sampler.cs
@@ -9,6 +9,11 @@ namespace Ryujinx.Graphics.Gpu.Image
class Sampler : IDisposable
{
/// <summary>
+ /// True if the sampler is disposed, false otherwise.
+ /// </summary>
+ public bool IsDisposed { get; private set; }
+
+ /// <summary>
/// Host sampler object.
/// </summary>
private readonly ISampler _hostSampler;
@@ -101,6 +106,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
public void Dispose()
{
+ IsDisposed = true;
+
_hostSampler.Dispose();
_anisoSampler?.Dispose();
}
diff --git a/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs b/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs
index e205ec48..e95800ad 100644
--- a/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs
+++ b/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs
@@ -48,6 +48,8 @@ namespace Ryujinx.Graphics.Gpu.Image
Items[i] = null;
}
}
+
+ UpdateModifiedSequence();
}
SequenceNumber = Context.SequenceNumber;
@@ -72,6 +74,39 @@ namespace Ryujinx.Graphics.Gpu.Image
}
/// <summary>
+ /// Checks if the pool was modified, and returns the last sequence number where a modification was detected.
+ /// </summary>
+ /// <returns>A number that increments each time a modification is detected</returns>
+ public int CheckModified()
+ {
+ if (SequenceNumber != Context.SequenceNumber)
+ {
+ SequenceNumber = Context.SequenceNumber;
+
+ if (_forcedAnisotropy != GraphicsConfig.MaxAnisotropy)
+ {
+ _forcedAnisotropy = GraphicsConfig.MaxAnisotropy;
+
+ for (int i = 0; i < Items.Length; i++)
+ {
+ if (Items[i] != null)
+ {
+ Items[i].Dispose();
+
+ Items[i] = null;
+ }
+ }
+
+ UpdateModifiedSequence();
+ }
+
+ SynchronizeMemory();
+ }
+
+ return ModifiedSequenceNumber;
+ }
+
+ /// <summary>
/// Implementation of the sampler pool range invalidation.
/// </summary>
/// <param name="address">Start address of the range of the sampler pool</param>
diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs
index aadb4260..cb10f456 100644
--- a/Ryujinx.Graphics.Gpu/Image/Texture.cs
+++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs
@@ -100,6 +100,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
public bool AlwaysFlushOnOverlap { get; private set; }
+ /// <summary>
+ /// Increments when the host texture is swapped, or when the texture is removed from all pools.
+ /// </summary>
+ public int InvalidatedSequence { get; private set; }
+
private int _depth;
private int _layers;
public int FirstLayer { get; private set; }
@@ -1407,6 +1412,7 @@ namespace Ryujinx.Graphics.Gpu.Image
DisposeTextures();
HostTexture = hostTexture;
+ InvalidatedSequence++;
}
/// <summary>
@@ -1535,6 +1541,8 @@ namespace Ryujinx.Graphics.Gpu.Image
_poolOwners.Clear();
}
+
+ InvalidatedSequence++;
}
/// <summary>
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
index 7ac4e12e..f15f8885 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
@@ -1,8 +1,12 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Types;
+using Ryujinx.Graphics.Gpu.Memory;
+using Ryujinx.Graphics.Gpu.Shader;
using Ryujinx.Graphics.Shader;
using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Image
{
@@ -35,17 +39,24 @@ namespace Ryujinx.Graphics.Gpu.Image
{
public ITexture Texture;
public ISampler Sampler;
+
+ public int TextureHandle;
+ public int SamplerHandle;
+ public int InvalidatedSequence;
+ public Texture CachedTexture;
+ public Sampler CachedSampler;
}
- private readonly TextureStatePerStage[][] _textureState;
- private readonly TextureStatePerStage[][] _imageState;
+ private TextureStatePerStage[] _textureState;
+ private TextureStatePerStage[] _imageState;
private int[] _textureBindingsCount;
private int[] _imageBindingsCount;
- private int _textureBufferIndex;
+ private int _texturePoolSequence;
+ private int _samplerPoolSequence;
- private bool _rebind;
+ private int _textureBufferIndex;
private readonly float[] _scales;
private bool _scaleChanged;
@@ -72,8 +83,8 @@ namespace Ryujinx.Graphics.Gpu.Image
_textureBindings = new TextureBindingInfo[stages][];
_imageBindings = new TextureBindingInfo[stages][];
- _textureState = new TextureStatePerStage[stages][];
- _imageState = new TextureStatePerStage[stages][];
+ _textureState = new TextureStatePerStage[InitialTextureStateSize];
+ _imageState = new TextureStatePerStage[InitialImageStateSize];
_textureBindingsCount = new int[stages];
_imageBindingsCount = new int[stages];
@@ -82,9 +93,6 @@ namespace Ryujinx.Graphics.Gpu.Image
{
_textureBindings[stage] = new TextureBindingInfo[InitialTextureStateSize];
_imageBindings[stage] = new TextureBindingInfo[InitialImageStateSize];
-
- _textureState[stage] = new TextureStatePerStage[InitialTextureStateSize];
- _imageState[stage] = new TextureStatePerStage[InitialImageStateSize];
}
}
@@ -99,15 +107,6 @@ namespace Ryujinx.Graphics.Gpu.Image
if (count > _textureBindings[stage].Length)
{
Array.Resize(ref _textureBindings[stage], count);
- Array.Resize(ref _textureState[stage], count);
- }
-
- int toClear = Math.Max(_textureBindingsCount[stage], count);
- TextureStatePerStage[] state = _textureState[stage];
-
- for (int i = 0; i < toClear; i++)
- {
- state[i] = new TextureStatePerStage();
}
_textureBindingsCount[stage] = count;
@@ -126,20 +125,29 @@ namespace Ryujinx.Graphics.Gpu.Image
if (count > _imageBindings[stage].Length)
{
Array.Resize(ref _imageBindings[stage], count);
- Array.Resize(ref _imageState[stage], count);
}
- int toClear = Math.Max(_imageBindingsCount[stage], count);
- TextureStatePerStage[] state = _imageState[stage];
+ _imageBindingsCount[stage] = count;
+
+ return _imageBindings[stage];
+ }
- for (int i = 0; i < toClear; i++)
+ /// <summary>
+ /// Sets the max binding indexes for textures and images.
+ /// </summary>
+ /// <param name="maxTextureBinding">The maximum texture binding</param>
+ /// <param name="maxImageBinding">The maximum image binding</param>
+ public void SetMaxBindings(int maxTextureBinding, int maxImageBinding)
+ {
+ if (maxTextureBinding >= _textureState.Length)
{
- state[i] = new TextureStatePerStage();
+ Array.Resize(ref _textureState, maxTextureBinding + 1);
}
- _imageBindingsCount[stage] = count;
-
- return _imageBindings[stage];
+ if (maxImageBinding >= _imageState.Length)
+ {
+ Array.Resize(ref _imageState, maxImageBinding + 1);
+ }
}
/// <summary>
@@ -323,7 +331,9 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Ensures that the bindings are visible to the host GPU.
/// Note: this actually performs the binding using the host graphics API.
/// </summary>
- public void CommitBindings()
+ /// <param name="specState">Specialization state for the bound shader</param>
+ /// <returns>True if all bound textures match the current shader specialiation state, false otherwise</returns>
+ public bool CommitBindings(ShaderSpecializationState specState)
{
ulong texturePoolAddress = _texturePoolAddress;
@@ -331,10 +341,38 @@ namespace Ryujinx.Graphics.Gpu.Image
? _texturePoolCache.FindOrCreate(_channel, texturePoolAddress, _texturePoolMaximumId)
: null;
+ // Check if the texture pool has been modified since bindings were last committed.
+ // If it wasn't, then it's possible to avoid looking up textures again when the handle remains the same.
+ bool poolModified = false;
+
+ if (texturePool != null)
+ {
+ int texturePoolSequence = texturePool.CheckModified();
+
+ if (_texturePoolSequence != texturePoolSequence)
+ {
+ poolModified = true;
+ _texturePoolSequence = texturePoolSequence;
+ }
+ }
+
+ if (_samplerPool != null)
+ {
+ int samplerPoolSequence = _samplerPool.CheckModified();
+
+ if (_samplerPoolSequence != samplerPoolSequence)
+ {
+ poolModified = true;
+ _samplerPoolSequence = samplerPoolSequence;
+ }
+ }
+
+ bool specStateMatches = true;
+
if (_isCompute)
{
- CommitTextureBindings(texturePool, ShaderStage.Compute, 0);
- CommitImageBindings (texturePool, ShaderStage.Compute, 0);
+ specStateMatches &= CommitTextureBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState);
+ specStateMatches &= CommitImageBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState);
}
else
{
@@ -342,14 +380,57 @@ namespace Ryujinx.Graphics.Gpu.Image
{
int stageIndex = (int)stage - 1;
- CommitTextureBindings(texturePool, stage, stageIndex);
- CommitImageBindings (texturePool, stage, stageIndex);
+ specStateMatches &= CommitTextureBindings(texturePool, stage, stageIndex, poolModified, specState);
+ specStateMatches &= CommitImageBindings(texturePool, stage, stageIndex, poolModified, specState);
}
}
CommitRenderScale();
- _rebind = false;
+ return specStateMatches;
+ }
+
+ /// <summary>
+ /// Fetch the constant buffers used for a texture to cache.
+ /// </summary>
+ /// <param name="stageIndex">Stage index of the constant buffer</param>
+ /// <param name="cachedTextureBufferIndex">The currently cached texture buffer index</param>
+ /// <param name="cachedSamplerBufferIndex">The currently cached sampler buffer index</param>
+ /// <param name="cachedTextureBuffer">The currently cached texture buffer data</param>
+ /// <param name="cachedSamplerBuffer">The currently cached sampler buffer data</param>
+ /// <param name="textureBufferIndex">The new texture buffer index</param>
+ /// <param name="samplerBufferIndex">The new sampler buffer index</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void UpdateCachedBuffer(
+ int stageIndex,
+ ref int cachedTextureBufferIndex,
+ ref int cachedSamplerBufferIndex,
+ ref ReadOnlySpan<int> cachedTextureBuffer,
+ ref ReadOnlySpan<int> cachedSamplerBuffer,
+ int textureBufferIndex,
+ int samplerBufferIndex)
+ {
+ if (textureBufferIndex != cachedTextureBufferIndex)
+ {
+ ref BufferBounds bounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, textureBufferIndex);
+
+ cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size));
+ cachedTextureBufferIndex = textureBufferIndex;
+
+ if (samplerBufferIndex == textureBufferIndex)
+ {
+ cachedSamplerBuffer = cachedTextureBuffer;
+ cachedSamplerBufferIndex = samplerBufferIndex;
+ }
+ }
+
+ if (samplerBufferIndex != cachedSamplerBufferIndex)
+ {
+ ref BufferBounds bounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, samplerBufferIndex);
+
+ cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size));
+ cachedSamplerBufferIndex = samplerBufferIndex;
+ }
}
/// <summary>
@@ -358,13 +439,16 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
/// <param name="pool">The current texture pool</param>
/// <param name="stage">The shader stage using the textures to be bound</param>
- /// <param name="stageIndex">The stage number of the specified shader stage</param>
- private void CommitTextureBindings(TexturePool pool, ShaderStage stage, int stageIndex)
+ /// <param name="stageIndex">The stage number of the specified shader stage</param
+ /// <param name="poolModified">True if either the texture or sampler pool was modified, false otherwise</param>
+ /// <param name="specState">Specialization state for the bound shader</param>
+ /// <returns>True if all bound textures match the current shader specialiation state, false otherwise</returns>
+ private bool CommitTextureBindings(TexturePool pool, ShaderStage stage, int stageIndex, bool poolModified, ShaderSpecializationState specState)
{
int textureCount = _textureBindingsCount[stageIndex];
if (textureCount == 0)
{
- return;
+ return true;
}
var samplerPool = _samplerPool;
@@ -372,17 +456,26 @@ namespace Ryujinx.Graphics.Gpu.Image
if (pool == null)
{
Logger.Error?.Print(LogClass.Gpu, $"Shader stage \"{stage}\" uses textures, but texture pool was not set.");
- return;
+ return true;
}
+ bool specStateMatches = true;
+
+ int cachedTextureBufferIndex = -1;
+ int cachedSamplerBufferIndex = -1;
+ ReadOnlySpan<int> cachedTextureBuffer = Span<int>.Empty;
+ ReadOnlySpan<int> cachedSamplerBuffer = Span<int>.Empty;
+
for (int index = 0; index < textureCount; index++)
{
TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index];
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
- int packedId = ReadPackedId(stageIndex, bindingInfo.Handle, textureBufferIndex, samplerBufferIndex);
- int textureId = UnpackTextureId(packedId);
+ UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex);
+
+ int packedId = TextureHandle.ReadPackedId(bindingInfo.Handle, cachedTextureBuffer, cachedSamplerBuffer);
+ int textureId = TextureHandle.UnpackTextureId(packedId);
int samplerId;
if (_samplerIndex == SamplerIndex.ViaHeaderIndex)
@@ -391,10 +484,30 @@ namespace Ryujinx.Graphics.Gpu.Image
}
else
{
- samplerId = UnpackSamplerId(packedId);
+ samplerId = TextureHandle.UnpackSamplerId(packedId);
}
- Texture texture = pool.Get(textureId);
+ ref TextureStatePerStage state = ref _textureState[bindingInfo.Binding];
+
+ if (!poolModified &&
+ state.TextureHandle == textureId &&
+ state.SamplerHandle == samplerId &&
+ state.CachedTexture != null &&
+ state.CachedTexture.InvalidatedSequence == state.InvalidatedSequence &&
+ state.CachedSampler?.IsDisposed != true)
+ {
+ // The texture is already bound.
+ state.CachedTexture.SynchronizeMemory();
+
+ continue;
+ }
+
+ state.TextureHandle = textureId;
+ state.SamplerHandle = samplerId;
+
+ ref readonly TextureDescriptor descriptor = ref pool.GetForBinding(textureId, out Texture texture);
+
+ specStateMatches &= specState.MatchesTexture(stage, index, descriptor);
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
@@ -407,30 +520,36 @@ namespace Ryujinx.Graphics.Gpu.Image
}
else
{
- if (_textureState[stageIndex][index].Texture != hostTexture || _rebind)
+ if (state.Texture != hostTexture)
{
if (UpdateScale(texture, bindingInfo, index, stage))
{
hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
}
- _textureState[stageIndex][index].Texture = hostTexture;
+ state.Texture = hostTexture;
_context.Renderer.Pipeline.SetTexture(bindingInfo.Binding, hostTexture);
}
Sampler sampler = samplerPool?.Get(samplerId);
+ state.CachedSampler = sampler;
ISampler hostSampler = sampler?.GetHostSampler(texture);
- if (_textureState[stageIndex][index].Sampler != hostSampler || _rebind)
+ if (state.Sampler != hostSampler)
{
- _textureState[stageIndex][index].Sampler = hostSampler;
+ state.Sampler = hostSampler;
_context.Renderer.Pipeline.SetSampler(bindingInfo.Binding, hostSampler);
}
+
+ state.CachedTexture = texture;
+ state.InvalidatedSequence = texture?.InvalidatedSequence ?? 0;
}
}
+
+ return specStateMatches;
}
/// <summary>
@@ -440,38 +559,72 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="pool">The current texture pool</param>
/// <param name="stage">The shader stage using the textures to be bound</param>
/// <param name="stageIndex">The stage number of the specified shader stage</param>
- private void CommitImageBindings(TexturePool pool, ShaderStage stage, int stageIndex)
+ /// <param name="poolModified">True if either the texture or sampler pool was modified, false otherwise</param>
+ /// <param name="specState">Specialization state for the bound shader</param>
+ /// <returns>True if all bound images match the current shader specialiation state, false otherwise</returns>
+ private bool CommitImageBindings(TexturePool pool, ShaderStage stage, int stageIndex, bool poolModified, ShaderSpecializationState specState)
{
int imageCount = _imageBindingsCount[stageIndex];
if (imageCount == 0)
{
- return;
+ return true;
}
if (pool == null)
{
Logger.Error?.Print(LogClass.Gpu, $"Shader stage \"{stage}\" uses images, but texture pool was not set.");
- return;
+ return true;
}
// Scales for images appear after the texture ones.
int baseScaleIndex = _textureBindingsCount[stageIndex];
+ int cachedTextureBufferIndex = -1;
+ int cachedSamplerBufferIndex = -1;
+ ReadOnlySpan<int> cachedTextureBuffer = Span<int>.Empty;
+ ReadOnlySpan<int> cachedSamplerBuffer = Span<int>.Empty;
+
+ bool specStateMatches = true;
+
for (int index = 0; index < imageCount; index++)
{
TextureBindingInfo bindingInfo = _imageBindings[stageIndex][index];
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
- int packedId = ReadPackedId(stageIndex, bindingInfo.Handle, textureBufferIndex, samplerBufferIndex);
- int textureId = UnpackTextureId(packedId);
+ UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex);
- Texture texture = pool.Get(textureId);
+ int packedId = TextureHandle.ReadPackedId(bindingInfo.Handle, cachedTextureBuffer, cachedSamplerBuffer);
+ int textureId = TextureHandle.UnpackTextureId(packedId);
- ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
+ ref TextureStatePerStage state = ref _imageState[bindingInfo.Binding];
bool isStore = bindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
+ if (!poolModified &&
+ state.TextureHandle == textureId &&
+ state.CachedTexture != null &&
+ state.CachedTexture.InvalidatedSequence == state.InvalidatedSequence)
+ {
+ // The texture is already bound.
+ state.CachedTexture.SynchronizeMemory();
+
+ if (isStore)
+ {
+ state.CachedTexture?.SignalModified();
+ }
+
+ continue;
+ }
+
+ state.TextureHandle = textureId;
+
+ ref readonly TextureDescriptor descriptor = ref pool.GetForBinding(textureId, out Texture texture);
+
+ specStateMatches &= specState.MatchesImage(stage, index, descriptor);
+
+ ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
+
if (hostTexture != null && texture.Target == Target.TextureBuffer)
{
// Ensure that the buffer texture is using the correct buffer as storage.
@@ -494,14 +647,14 @@ namespace Ryujinx.Graphics.Gpu.Image
texture?.SignalModified();
}
- if (_imageState[stageIndex][index].Texture != hostTexture || _rebind)
+ if (state.Texture != hostTexture)
{
if (UpdateScale(texture, bindingInfo, baseScaleIndex + index, stage))
{
hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
}
- _imageState[stageIndex][index].Texture = hostTexture;
+ state.Texture = hostTexture;
Format format = bindingInfo.Format;
@@ -512,8 +665,13 @@ namespace Ryujinx.Graphics.Gpu.Image
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTexture, format);
}
+
+ state.CachedTexture = texture;
+ state.InvalidatedSequence = texture?.InvalidatedSequence ?? 0;
}
}
+
+ return specStateMatches;
}
/// <summary>
@@ -537,7 +695,7 @@ namespace Ryujinx.Graphics.Gpu.Image
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(cbufSlot, bufferIndex);
int packedId = ReadPackedId(stageIndex, handle, textureBufferIndex, samplerBufferIndex);
- int textureId = UnpackTextureId(packedId);
+ int textureId = TextureHandle.UnpackTextureId(packedId);
ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa);
@@ -555,6 +713,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="textureBufferIndex">Index of the constant buffer holding the texture handles</param>
/// <param name="samplerBufferIndex">Index of the constant buffer holding the sampler handles</param>
/// <returns>The packed texture and sampler ID (the real texture handle)</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
private int ReadPackedId(int stageIndex, int wordOffset, int textureBufferIndex, int samplerBufferIndex)
{
(int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = TextureHandle.UnpackOffsets(wordOffset);
@@ -591,31 +750,12 @@ namespace Ryujinx.Graphics.Gpu.Image
}
/// <summary>
- /// Unpacks the texture ID from the real texture handle.
- /// </summary>
- /// <param name="packedId">The real texture handle</param>
- /// <returns>The texture ID</returns>
- private static int UnpackTextureId(int packedId)
- {
- return (packedId >> 0) & 0xfffff;
- }
-
- /// <summary>
- /// Unpacks the sampler ID from the real texture handle.
- /// </summary>
- /// <param name="packedId">The real texture handle</param>
- /// <returns>The sampler ID</returns>
- private static int UnpackSamplerId(int packedId)
- {
- return (packedId >> 20) & 0xfff;
- }
-
- /// <summary>
/// Force all bound textures and images to be rebound the next time CommitBindings is called.
/// </summary>
public void Rebind()
{
- _rebind = true;
+ Array.Clear(_textureState);
+ Array.Clear(_imageState);
}
/// <summary>
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
index a1c29291..628c3159 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
@@ -1,5 +1,6 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Types;
+using Ryujinx.Graphics.Gpu.Shader;
using System;
namespace Ryujinx.Graphics.Gpu.Image
@@ -10,9 +11,11 @@ namespace Ryujinx.Graphics.Gpu.Image
class TextureManager : IDisposable
{
private readonly GpuContext _context;
+ private readonly GpuChannel _channel;
private readonly TextureBindingsManager _cpBindingsManager;
private readonly TextureBindingsManager _gpBindingsManager;
+ private readonly TexturePoolCache _texturePoolCache;
private readonly Texture[] _rtColors;
private readonly ITexture[] _rtHostColors;
@@ -35,6 +38,7 @@ namespace Ryujinx.Graphics.Gpu.Image
public TextureManager(GpuContext context, GpuChannel channel)
{
_context = context;
+ _channel = channel;
TexturePoolCache texturePoolCache = new TexturePoolCache(context);
@@ -43,6 +47,7 @@ namespace Ryujinx.Graphics.Gpu.Image
_cpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, scales, isCompute: true);
_gpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, scales, isCompute: false);
+ _texturePoolCache = texturePoolCache;
_rtColors = new Texture[Constants.TotalRenderTargets];
_rtHostColors = new ITexture[Constants.TotalRenderTargets];
@@ -100,6 +105,16 @@ namespace Ryujinx.Graphics.Gpu.Image
}
/// <summary>
+ /// Sets the max binding indexes on the compute pipeline.
+ /// </summary>
+ /// <param name="maxTextureBinding">The maximum texture binding</param>
+ /// <param name="maxImageBinding">The maximum image binding</param>
+ public void SetComputeMaxBindings(int maxTextureBinding, int maxImageBinding)
+ {
+ _cpBindingsManager.SetMaxBindings(maxTextureBinding, maxImageBinding);
+ }
+
+ /// <summary>
/// Sets the texture constant buffer index on the graphics pipeline.
/// </summary>
/// <param name="index">The texture constant buffer index</param>
@@ -109,6 +124,16 @@ namespace Ryujinx.Graphics.Gpu.Image
}
/// <summary>
+ /// Sets the max binding indexes on the graphics pipeline.
+ /// </summary>
+ /// <param name="maxTextureBinding">The maximum texture binding</param>
+ /// <param name="maxImageBinding">The maximum image binding</param>
+ public void SetGraphicsMaxBindings(int maxTextureBinding, int maxImageBinding)
+ {
+ _gpBindingsManager.SetMaxBindings(maxTextureBinding, maxImageBinding);
+ }
+
+ /// <summary>
/// Sets the current sampler pool on the compute pipeline.
/// </summary>
/// <param name="gpuVa">The start GPU virtual address of the sampler pool</param>
@@ -335,25 +360,48 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <summary>
/// Commits bindings on the compute pipeline.
/// </summary>
- public void CommitComputeBindings()
+ /// <param name="specState">Specialization state for the bound shader</param>
+ /// <returns>True if all bound textures match the current shader specialization state, false otherwise</returns>
+ public bool CommitComputeBindings(ShaderSpecializationState specState)
{
// Every time we switch between graphics and compute work,
// we must rebind everything.
// Since compute work happens less often, we always do that
// before and after the compute dispatch.
_cpBindingsManager.Rebind();
- _cpBindingsManager.CommitBindings();
+ bool result = _cpBindingsManager.CommitBindings(specState);
_gpBindingsManager.Rebind();
+
+ return result;
}
/// <summary>
/// Commits bindings on the graphics pipeline.
/// </summary>
- public void CommitGraphicsBindings()
+ /// <param name="specState">Specialization state for the bound shader</param>
+ /// <returns>True if all bound textures match the current shader specialization state, false otherwise</returns>
+ public bool CommitGraphicsBindings(ShaderSpecializationState specState)
{
- _gpBindingsManager.CommitBindings();
+ bool result = _gpBindingsManager.CommitBindings(specState);
UpdateRenderTargets();
+
+ return result;
+ }
+
+ /// <summary>
+ /// Returns a texture pool from the cache, with the given address and maximum id.
+ /// </summary>
+ /// <param name="poolGpuVa">GPU virtual address of the texture pool</param>
+ /// <param name="maximumId">Maximum ID of the texture pool</param>
+ /// <returns>The texture pool</returns>
+ public TexturePool GetTexturePool(ulong poolGpuVa, int maximumId)
+ {
+ ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa);
+
+ TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId);
+
+ return texturePool;
}
/// <summary>
diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
index 10a6ff82..75974c43 100644
--- a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
@@ -14,6 +14,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
private readonly GpuChannel _channel;
private readonly ConcurrentQueue<Texture> _dereferenceQueue = new ConcurrentQueue<Texture>();
+ private TextureDescriptor _defaultDescriptor;
/// <summary>
/// Intrusive linked list node used on the texture pool cache.
@@ -33,30 +34,19 @@ namespace Ryujinx.Graphics.Gpu.Image
}
/// <summary>
- /// Gets the texture with the given ID.
+ /// Gets the texture descripor and texture with the given ID with no bounds check or synchronization.
/// </summary>
/// <param name="id">ID of the texture. This is effectively a zero-based index</param>
- /// <returns>The texture with the given ID</returns>
- public override Texture Get(int id)
+ /// <param name="texture">The texture with the given ID</param>
+ /// <returns>The texture descriptor with the given ID</returns>
+ private ref readonly TextureDescriptor GetInternal(int id, out Texture texture)
{
- if ((uint)id >= Items.Length)
- {
- return null;
- }
-
- if (SequenceNumber != Context.SequenceNumber)
- {
- SequenceNumber = Context.SequenceNumber;
-
- SynchronizeMemory();
- }
+ texture = Items[id];
- Texture texture = Items[id];
+ ref readonly TextureDescriptor descriptor = ref GetDescriptorRef(id);
if (texture == null)
{
- TextureDescriptor descriptor = GetDescriptor(id);
-
TextureInfo info = GetInfo(descriptor, out int layerSize);
ProcessDereferenceQueue();
@@ -66,7 +56,7 @@ namespace Ryujinx.Graphics.Gpu.Image
// If this happens, then the texture address is invalid, we can't add it to the cache.
if (texture == null)
{
- return null;
+ return ref descriptor;
}
texture.IncrementReferenceCount(this, id);
@@ -82,8 +72,6 @@ namespace Ryujinx.Graphics.Gpu.Image
// Texture changed size at one point - it may be a different size than the sampler expects.
// This can be triggered when the size is changed by a size hint on copy or draw, but the texture has been sampled before.
- TextureDescriptor descriptor = GetDescriptor(id);
-
int baseLevel = descriptor.UnpackBaseLevel();
int width = Math.Max(1, descriptor.UnpackWidth() >> baseLevel);
int height = Math.Max(1, descriptor.UnpackHeight() >> baseLevel);
@@ -98,10 +86,72 @@ namespace Ryujinx.Graphics.Gpu.Image
texture.SynchronizeMemory();
}
+ return ref descriptor;
+ }
+
+ /// <summary>
+ /// Gets the texture with the given ID.
+ /// </summary>
+ /// <param name="id">ID of the texture. This is effectively a zero-based index</param>
+ /// <returns>The texture with the given ID</returns>
+ public override Texture Get(int id)
+ {
+ if ((uint)id >= Items.Length)
+ {
+ return null;
+ }
+
+ if (SequenceNumber != Context.SequenceNumber)
+ {
+ SequenceNumber = Context.SequenceNumber;
+
+ SynchronizeMemory();
+ }
+
+ GetInternal(id, out Texture texture);
+
return texture;
}
/// <summary>
+ /// Gets the texture descriptor and texture with the given ID.
+ /// </summary>
+ /// <remarks>
+ /// This method assumes that the pool has been manually synchronized before doing binding.
+ /// </remarks>
+ /// <param name="id">ID of the texture. This is effectively a zero-based index</param>
+ /// <param name="texture">The texture with the given ID</param>
+ /// <returns>The texture descriptor with the given ID</returns>
+ public ref readonly TextureDescriptor GetForBinding(int id, out Texture texture)
+ {
+ if ((uint)id >= Items.Length)
+ {
+ texture = null;
+ return ref _defaultDescriptor;
+ }
+
+ // When getting for binding, assume the pool has already been synchronized.
+
+ return ref GetInternal(id, out texture);
+ }
+
+ /// <summary>
+ /// Checks if the pool was modified, and returns the last sequence number where a modification was detected.
+ /// </summary>
+ /// <returns>A number that increments each time a modification is detected</returns>
+ public int CheckModified()
+ {
+ if (SequenceNumber != Context.SequenceNumber)
+ {
+ SequenceNumber = Context.SequenceNumber;
+
+ SynchronizeMemory();
+ }
+
+ return ModifiedSequenceNumber;
+ }
+
+ /// <summary>
/// Forcibly remove a texture from this pool's items.
/// If deferred, the dereference will be queued to occur on the render thread.
/// </summary>
@@ -175,7 +225,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="descriptor">The texture descriptor</param>
/// <param name="layerSize">Layer size for textures using a sub-range of mipmap levels, otherwise 0</param>
/// <returns>The texture information</returns>
- private TextureInfo GetInfo(TextureDescriptor descriptor, out int layerSize)
+ private TextureInfo GetInfo(in TextureDescriptor descriptor, out int layerSize)
{
int depthOrLayers = descriptor.UnpackDepth();
int levels = descriptor.UnpackLevels();
diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
index 71f202ae..9f5f39a9 100644
--- a/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
+++ b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
@@ -379,6 +379,25 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
/// <summary>
+ /// Gets the bounds of the uniform buffer currently bound at the given index.
+ /// </summary>
+ /// <param name="isCompute">Indicates whenever the uniform is requested by the 3D or compute engine</param>
+ /// <param name="stage">Index of the shader stage, if the uniform is for the 3D engine</param>
+ /// <param name="index">Index of the uniform buffer binding</param>
+ /// <returns>The uniform buffer bounds, or an undefined value if the buffer is not currently bound</returns>
+ public ref BufferBounds GetUniformBufferBounds(bool isCompute, int stage, int index)
+ {
+ if (isCompute)
+ {
+ return ref _cpUniformBuffers.Buffers[index];
+ }
+ else
+ {
+ return ref _gpUniformBuffers[stage].Buffers[index];
+ }
+ }
+
+ /// <summary>
/// Ensures that the compute engine bindings are visible to the host GPU.
/// Note: this actually performs the binding using the host graphics API.
/// </summary>
diff --git a/Ryujinx.Graphics.Gpu/Shader/CachedShaderProgram.cs b/Ryujinx.Graphics.Gpu/Shader/CachedShaderProgram.cs
index 3b4c65f3..69fcb278 100644
--- a/Ryujinx.Graphics.Gpu/Shader/CachedShaderProgram.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/CachedShaderProgram.cs
@@ -35,6 +35,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
HostProgram = hostProgram;
SpecializationState = specializationState;
Shaders = shaders;
+
+ SpecializationState.Prepare(shaders);
}
/// <summary>
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
index df4b9d12..0779bf2c 100644
--- a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
@@ -418,7 +418,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
if (IsShaderEqual(channel.MemoryManager, cpShader.Shaders[0], gpuVa))
{
- return cpShader.SpecializationState.MatchesCompute(channel, poolState);
+ return cpShader.SpecializationState.MatchesCompute(channel, poolState, true);
}
return false;
@@ -454,7 +454,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
}
- return gpShaders.SpecializationState.MatchesGraphics(channel, poolState, graphicsState);
+ return gpShaders.SpecializationState.MatchesGraphics(channel, poolState, graphicsState, true);
}
/// <summary>
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs
index e3e57d74..43ccd892 100644
--- a/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs
@@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
foreach (var entry in _entries)
{
- if (entry.SpecializationState.MatchesGraphics(channel, poolState, graphicsState))
+ if (entry.SpecializationState.MatchesGraphics(channel, poolState, graphicsState, true))
{
program = entry;
return true;
@@ -57,7 +57,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
foreach (var entry in _entries)
{
- if (entry.SpecializationState.MatchesCompute(channel, poolState))
+ if (entry.SpecializationState.MatchesCompute(channel, poolState, true))
{
program = entry;
return true;
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
index 418c7b1a..44ffd687 100644
--- a/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
@@ -1,9 +1,14 @@
using Ryujinx.Common.Memory;
+using Ryujinx.Graphics.Gpu.Image;
+using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.Shader.DiskCache;
using Ryujinx.Graphics.Shader;
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Shader
{
@@ -158,6 +163,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
private readonly Dictionary<TextureKey, Box<TextureSpecializationState>> _textureSpecialization;
+ private KeyValuePair<TextureKey, Box<TextureSpecializationState>>[] _allTextures;
+ private Box<TextureSpecializationState>[][] _textureByBinding;
+ private Box<TextureSpecializationState>[][] _imageByBinding;
/// <summary>
/// Creates a new instance of the shader specialization state.
@@ -195,6 +203,48 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
/// <summary>
+ /// Prepare the shader specialization state for quick binding lookups.
+ /// </summary>
+ /// <param name="stages">The shader stages</param>
+ public void Prepare(CachedShaderStage[] stages)
+ {
+ _allTextures = _textureSpecialization.ToArray();
+
+ _textureByBinding = new Box<TextureSpecializationState>[stages.Length][];
+ _imageByBinding = new Box<TextureSpecializationState>[stages.Length][];
+
+ for (int i = 0; i < stages.Length; i++)
+ {
+ CachedShaderStage stage = stages[i];
+ if (stage?.Info != null)
+ {
+ var textures = stage.Info.Textures;
+ var images = stage.Info.Images;
+
+ var texBindings = new Box<TextureSpecializationState>[textures.Count];
+ var imageBindings = new Box<TextureSpecializationState>[images.Count];
+
+ int stageIndex = Math.Max(i - 1, 0); // Don't count VertexA for looking up spec state. No-Op for compute.
+
+ for (int j = 0; j < textures.Count; j++)
+ {
+ var texture = textures[j];
+ texBindings[j] = GetTextureSpecState(stageIndex, texture.HandleIndex, texture.CbufSlot);
+ }
+
+ for (int j = 0; j < images.Count; j++)
+ {
+ var image = images[j];
+ imageBindings[j] = GetTextureSpecState(stageIndex, image.HandleIndex, image.CbufSlot);
+ }
+
+ _textureByBinding[i] = texBindings;
+ _imageByBinding[i] = imageBindings;
+ }
+ }
+ }
+
+ /// <summary>
/// Indicates that the shader accesses the early Z force state.
/// </summary>
public void RecordEarlyZForce()
@@ -396,15 +446,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="channel">GPU channel</param>
/// <param name="poolState">Texture pool state</param>
/// <param name="graphicsState">Graphics state</param>
+ /// <param name="checkTextures">Indicates whether texture descriptors should be checked</param>
/// <returns>True if the state matches, false otherwise</returns>
- public bool MatchesGraphics(GpuChannel channel, GpuChannelPoolState poolState, GpuChannelGraphicsState graphicsState)
+ public bool MatchesGraphics(GpuChannel channel, GpuChannelPoolState poolState, GpuChannelGraphicsState graphicsState, bool checkTextures)
{
if (graphicsState.ViewportTransformDisable != GraphicsState.ViewportTransformDisable)
{
return false;
}
- return Matches(channel, poolState, isCompute: false);
+ return Matches(channel, poolState, checkTextures, isCompute: false);
}
/// <summary>
@@ -412,10 +463,64 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary>
/// <param name="channel">GPU channel</param>
/// <param name="poolState">Texture pool state</param>
+ /// <param name="checkTextures">Indicates whether texture descriptors should be checked</param>
/// <returns>True if the state matches, false otherwise</returns>
- public bool MatchesCompute(GpuChannel channel, GpuChannelPoolState poolState)
+ public bool MatchesCompute(GpuChannel channel, GpuChannelPoolState poolState, bool checkTextures)
{
- return Matches(channel, poolState, isCompute: true);
+ return Matches(channel, poolState, checkTextures, isCompute: true);
+ }
+
+ /// <summary>
+ /// Fetch the constant buffers used for a texture to cache.
+ /// </summary>
+ /// <param name="channel">GPU channel</param>
+ /// <param name="isCompute">Indicates whenever the check is requested by the 3D or compute engine</param>
+ /// <param name="cachedTextureBufferIndex">The currently cached texture buffer index</param>
+ /// <param name="cachedSamplerBufferIndex">The currently cached sampler buffer index</param>
+ /// <param name="cachedTextureBuffer">The currently cached texture buffer data</param>
+ /// <param name="cachedSamplerBuffer">The currently cached sampler buffer data</param>
+ /// <param name="cachedStageIndex">The currently cached stage</param>
+ /// <param name="textureBufferIndex">The new texture buffer index</param>
+ /// <param name="samplerBufferIndex">The new sampler buffer index</param>
+ /// <param name="stageIndex">Stage index of the constant buffer</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void UpdateCachedBuffer(
+ GpuChannel channel,
+ bool isCompute,
+ ref int cachedTextureBufferIndex,
+ ref int cachedSamplerBufferIndex,
+ ref ReadOnlySpan<int> cachedTextureBuffer,
+ ref ReadOnlySpan<int> cachedSamplerBuffer,
+ ref int cachedStageIndex,
+ int textureBufferIndex,
+ int samplerBufferIndex,
+ int stageIndex)
+ {
+ bool stageChange = stageIndex != cachedStageIndex;
+
+ if (stageChange || textureBufferIndex != cachedTextureBufferIndex)
+ {
+ ref BufferBounds bounds = ref channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, textureBufferIndex);
+
+ cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size));
+ cachedTextureBufferIndex = textureBufferIndex;
+
+ if (samplerBufferIndex == textureBufferIndex)
+ {
+ cachedSamplerBuffer = cachedTextureBuffer;
+ cachedSamplerBufferIndex = samplerBufferIndex;
+ }
+ }
+
+ if (stageChange || samplerBufferIndex != cachedSamplerBufferIndex)
+ {
+ ref BufferBounds bounds = ref channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, samplerBufferIndex);
+
+ cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size));
+ cachedSamplerBufferIndex = samplerBufferIndex;
+ }
+
+ cachedStageIndex = stageIndex;
}
/// <summary>
@@ -423,9 +528,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary>
/// <param name="channel">GPU channel</param>
/// <param name="poolState">Texture pool state</param>
+ /// <param name="checkTextures">Indicates whether texture descriptors should be checked</param>
/// <param name="isCompute">Indicates whenever the check is requested by the 3D or compute engine</param>
/// <returns>True if the state matches, false otherwise</returns>
- private bool Matches(GpuChannel channel, GpuChannelPoolState poolState, bool isCompute)
+ private bool Matches(GpuChannel channel, GpuChannelPoolState poolState, bool checkTextures, bool isCompute)
{
int constantBufferUsePerStageMask = _constantBufferUsePerStage;
@@ -445,55 +551,60 @@ namespace Ryujinx.Graphics.Gpu.Shader
constantBufferUsePerStageMask &= ~(1 << index);
}
- foreach (var kv in _textureSpecialization)
+ if (checkTextures)
{
- TextureKey textureKey = kv.Key;
-
- (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(textureKey.CbufSlot, poolState.TextureBufferIndex);
+ TexturePool pool = channel.TextureManager.GetTexturePool(poolState.TexturePoolGpuVa, poolState.TexturePoolMaximumId);
- ulong textureCbAddress;
- ulong samplerCbAddress;
+ int cachedTextureBufferIndex = -1;
+ int cachedSamplerBufferIndex = -1;
+ int cachedStageIndex = -1;
+ ReadOnlySpan<int> cachedTextureBuffer = Span<int>.Empty;
+ ReadOnlySpan<int> cachedSamplerBuffer = Span<int>.Empty;
- if (isCompute)
+ foreach (var kv in _allTextures)
{
- textureCbAddress = channel.BufferManager.GetComputeUniformBufferAddress(textureBufferIndex);
- samplerCbAddress = channel.BufferManager.GetComputeUniformBufferAddress(samplerBufferIndex);
- }
- else
- {
- textureCbAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(textureKey.StageIndex, textureBufferIndex);
- samplerCbAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(textureKey.StageIndex, samplerBufferIndex);
- }
+ TextureKey textureKey = kv.Key;
- if (!channel.MemoryManager.Physical.IsMapped(textureCbAddress) || !channel.MemoryManager.Physical.IsMapped(samplerCbAddress))
- {
- continue;
- }
+ (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(textureKey.CbufSlot, poolState.TextureBufferIndex);
- Image.TextureDescriptor descriptor;
+ UpdateCachedBuffer(channel,
+ isCompute,
+ ref cachedTextureBufferIndex,
+ ref cachedSamplerBufferIndex,
+ ref cachedTextureBuffer,
+ ref cachedSamplerBuffer,
+ ref cachedStageIndex,
+ textureBufferIndex,
+ samplerBufferIndex,
+ textureKey.StageIndex);
- if (isCompute)
- {
- descriptor = channel.TextureManager.GetComputeTextureDescriptor(
- poolState.TexturePoolGpuVa,
- poolState.TextureBufferIndex,
- poolState.TexturePoolMaximumId,
- textureKey.Handle,
- textureKey.CbufSlot);
- }
- else
- {
- descriptor = channel.TextureManager.GetGraphicsTextureDescriptor(
- poolState.TexturePoolGpuVa,
- poolState.TextureBufferIndex,
- poolState.TexturePoolMaximumId,
- textureKey.StageIndex,
- textureKey.Handle,
- textureKey.CbufSlot);
+ int packedId = TextureHandle.ReadPackedId(textureKey.Handle, cachedTextureBuffer, cachedSamplerBuffer);
+
+ int textureId = TextureHandle.UnpackTextureId(packedId);
+
+ ref readonly Image.TextureDescriptor descriptor = ref pool.GetDescriptorRef(textureId);
+
+ if (!MatchesTexture(kv.Value, descriptor))
+ {
+ return false;
+ }
}
+ }
- Box<TextureSpecializationState> specializationState = kv.Value;
+ return true;
+ }
+ /// <summary>
+ /// Checks if the recorded texture state matches the given texture descriptor.
+ /// </summary>
+ /// <param name="specializationState">Texture specialization state</param>
+ /// <param name="descriptor">Texture descriptor</param>
+ /// <returns>True if the state matches, false otherwise</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private bool MatchesTexture(Box<TextureSpecializationState> specializationState, in Image.TextureDescriptor descriptor)
+ {
+ if (specializationState != null)
+ {
if (specializationState.Value.QueriedFlags.HasFlag(QueriedTextureStateFlags.CoordNormalized) &&
specializationState.Value.CoordNormalized != descriptor.UnpackTextureCoordNormalized())
{
@@ -505,6 +616,34 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
/// <summary>
+ /// Checks if the recorded texture state for a given texture binding matches a texture descriptor.
+ /// </summary>
+ /// <param name="stage">The shader stage</param>
+ /// <param name="index">The texture index</param>
+ /// <param name="descriptor">Texture descriptor</param>
+ /// <returns>True if the state matches, false otherwise</returns>
+ public bool MatchesTexture(ShaderStage stage, int index, in Image.TextureDescriptor descriptor)
+ {
+ Box<TextureSpecializationState> specializationState = _textureByBinding[(int)stage][index];
+
+ return MatchesTexture(specializationState, descriptor);
+ }
+
+ /// <summary>
+ /// Checks if the recorded texture state for a given image binding matches a texture descriptor.
+ /// </summary>
+ /// <param name="stage">The shader stage</param>
+ /// <param name="index">The texture index</param>
+ /// <param name="descriptor">Texture descriptor</param>
+ /// <returns>True if the state matches, false otherwise</returns>
+ public bool MatchesImage(ShaderStage stage, int index, in Image.TextureDescriptor descriptor)
+ {
+ Box<TextureSpecializationState> specializationState = _imageByBinding[(int)stage][index];
+
+ return MatchesTexture(specializationState, descriptor);
+ }
+
+ /// <summary>
/// Reads shader specialization state that has been serialized.
/// </summary>
/// <param name="dataReader">Data reader</param>
diff --git a/Ryujinx.Graphics.Shader/TextureHandle.cs b/Ryujinx.Graphics.Shader/TextureHandle.cs
index b3712e6b..d468188b 100644
--- a/Ryujinx.Graphics.Shader/TextureHandle.cs
+++ b/Ryujinx.Graphics.Shader/TextureHandle.cs
@@ -1,3 +1,4 @@
+using System;
using System.Runtime.CompilerServices;
namespace Ryujinx.Graphics.Shader
@@ -50,5 +51,63 @@ namespace Ryujinx.Graphics.Shader
{
return (handle & 0x3fff, (handle >> 14) & 0x3fff, (TextureHandleType)((uint)handle >> 28));
}
+
+ /// <summary>
+ /// Unpacks the texture ID from the real texture handle.
+ /// </summary>
+ /// <param name="packedId">The real texture handle</param>
+ /// <returns>The texture ID</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int UnpackTextureId(int packedId)
+ {
+ return (packedId >> 0) & 0xfffff;
+ }
+
+ /// <summary>
+ /// Unpacks the sampler ID from the real texture handle.
+ /// </summary>
+ /// <param name="packedId">The real texture handle</param>
+ /// <returns>The sampler ID</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int UnpackSamplerId(int packedId)
+ {
+ return (packedId >> 20) & 0xfff;
+ }
+
+ /// <summary>
+ /// Reads a packed texture and sampler ID (basically, the real texture handle)
+ /// from a given texture/sampler constant buffer.
+ /// </summary>
+ /// <param name="wordOffset">A word offset of the handle on the buffer (the "fake" shader handle)</param>
+ /// <param name="cachedTextureBuffer">The constant buffer to fetch texture IDs from</param>
+ /// <param name="cachedSamplerBuffer">The constant buffer to fetch sampler IDs from</param>
+ /// <returns>The packed texture and sampler ID (the real texture handle)</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int ReadPackedId(int wordOffset, ReadOnlySpan<int> cachedTextureBuffer, ReadOnlySpan<int> cachedSamplerBuffer)
+ {
+ (int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = UnpackOffsets(wordOffset);
+
+ int handle = cachedTextureBuffer[textureWordOffset];
+
+ // The "wordOffset" (which is really the immediate value used on texture instructions on the shader)
+ // is a 13-bit value. However, in order to also support separate samplers and textures (which uses
+ // bindless textures on the shader), we extend it with another value on the higher 16 bits with
+ // another offset for the sampler.
+ // The shader translator has code to detect separate texture and sampler uses with a bindless texture,
+ // turn that into a regular texture access and produce those special handles with values on the higher 16 bits.
+ if (handleType != TextureHandleType.CombinedSampler)
+ {
+ int samplerHandle = cachedSamplerBuffer[samplerWordOffset];
+
+ if (handleType == TextureHandleType.SeparateSamplerId)
+ {
+ samplerHandle <<= 20;
+ }
+
+ handle |= samplerHandle;
+ }
+
+ return handle;
+ }
}
}