Optimize Texture Binding and Shader Specialization Checks (#3399)

* Changes 1

* Changes 2

* Better ModifiedSequence handling

This should handle PreciseEvents properly, and simplifies a few things.

* Minor changes, remove debug log

* Handle stage.Info being null

Hopefully fixes Catherine crash

* Fix shader specialization fast texture lookup

* Fix some things.

* Address Feedback Part 1

* Make method static.
This commit is contained in:
riperiperi 2022-06-17 17:09:14 +01:00 committed by GitHub
parent d987cacfb7
commit 99ffc061d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 766 additions and 180 deletions

View file

@ -188,6 +188,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
_channel.BufferManager.SetComputeStorageBufferBindings(info.SBuffers); _channel.BufferManager.SetComputeStorageBufferBindings(info.SBuffers);
_channel.BufferManager.SetComputeUniformBufferBindings(info.CBuffers); _channel.BufferManager.SetComputeUniformBufferBindings(info.CBuffers);
int maxTextureBinding = -1;
int maxImageBinding = -1;
TextureBindingInfo[] textureBindings = _channel.TextureManager.RentComputeTextureBindings(info.Textures.Count); TextureBindingInfo[] textureBindings = _channel.TextureManager.RentComputeTextureBindings(info.Textures.Count);
for (int index = 0; index < info.Textures.Count; index++) for (int index = 0; index < info.Textures.Count; index++)
@ -202,6 +205,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
descriptor.CbufSlot, descriptor.CbufSlot,
descriptor.HandleIndex, descriptor.HandleIndex,
descriptor.Flags); descriptor.Flags);
if (descriptor.Binding > maxTextureBinding)
{
maxTextureBinding = descriptor.Binding;
}
} }
TextureBindingInfo[] imageBindings = _channel.TextureManager.RentComputeImageBindings(info.Images.Count); TextureBindingInfo[] imageBindings = _channel.TextureManager.RentComputeImageBindings(info.Images.Count);
@ -220,9 +228,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
descriptor.CbufSlot, descriptor.CbufSlot,
descriptor.HandleIndex, descriptor.HandleIndex,
descriptor.Flags); 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(); _channel.BufferManager.CommitComputeBindings();
_context.Renderer.Pipeline.DispatchCompute(qmd.CtaRasterWidth, qmd.CtaRasterHeight, qmd.CtaRasterDepth); _context.Renderer.Pipeline.DispatchCompute(qmd.CtaRasterWidth, qmd.CtaRasterHeight, qmd.CtaRasterDepth);

View file

@ -201,7 +201,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
// of the shader for the new state. // of the shader for the new state.
if (_shaderSpecState != null) if (_shaderSpecState != null)
{ {
if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState(), GetGraphicsState())) if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState(), GetGraphicsState(), false))
{ {
ForceShaderUpdate(); ForceShaderUpdate();
} }
@ -275,7 +275,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
{ {
UpdateStorageBuffers(); UpdateStorageBuffers();
_channel.TextureManager.CommitGraphicsBindings(); if (!_channel.TextureManager.CommitGraphicsBindings(_shaderSpecState))
{
// Shader must be reloaded.
UpdateShaderState();
}
_channel.BufferManager.CommitGraphicsBindings(); _channel.BufferManager.CommitGraphicsBindings();
} }
@ -1150,6 +1155,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
return; return;
} }
int maxTextureBinding = -1;
int maxImageBinding = -1;
Span<TextureBindingInfo> textureBindings = _channel.TextureManager.RentGraphicsTextureBindings(stage, info.Textures.Count); Span<TextureBindingInfo> textureBindings = _channel.TextureManager.RentGraphicsTextureBindings(stage, info.Textures.Count);
if (info.UsesRtLayer) if (info.UsesRtLayer)
@ -1169,6 +1177,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
descriptor.CbufSlot, descriptor.CbufSlot,
descriptor.HandleIndex, descriptor.HandleIndex,
descriptor.Flags); descriptor.Flags);
if (descriptor.Binding > maxTextureBinding)
{
maxTextureBinding = descriptor.Binding;
}
} }
TextureBindingInfo[] imageBindings = _channel.TextureManager.RentGraphicsImageBindings(stage, info.Images.Count); TextureBindingInfo[] imageBindings = _channel.TextureManager.RentGraphicsImageBindings(stage, info.Images.Count);
@ -1187,7 +1200,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
descriptor.CbufSlot, descriptor.CbufSlot,
descriptor.HandleIndex, descriptor.HandleIndex,
descriptor.Flags); descriptor.Flags);
if (descriptor.Binding > maxImageBinding)
{
maxImageBinding = descriptor.Binding;
} }
}
_channel.TextureManager.SetGraphicsMaxBindings(maxTextureBinding, maxImageBinding);
_channel.BufferManager.SetGraphicsStorageBufferBindings(stage, info.SBuffers); _channel.BufferManager.SetGraphicsStorageBufferBindings(stage, info.SBuffers);
_channel.BufferManager.SetGraphicsUniformBufferBindings(stage, info.CBuffers); _channel.BufferManager.SetGraphicsUniformBufferBindings(stage, info.CBuffers);

View file

@ -1,6 +1,7 @@
using Ryujinx.Cpu.Tracking; using Ryujinx.Cpu.Tracking;
using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Gpu.Memory;
using System; using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Image namespace Ryujinx.Graphics.Gpu.Image
{ {
@ -16,6 +17,7 @@ namespace Ryujinx.Graphics.Gpu.Image
protected GpuContext Context; protected GpuContext Context;
protected PhysicalMemory PhysicalMemory; protected PhysicalMemory PhysicalMemory;
protected int SequenceNumber; protected int SequenceNumber;
protected int ModifiedSequenceNumber;
protected T1[] Items; protected T1[] Items;
protected T2[] DescriptorCache; protected T2[] DescriptorCache;
@ -41,6 +43,9 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly CpuMultiRegionHandle _memoryTracking; private readonly CpuMultiRegionHandle _memoryTracking;
private readonly Action<ulong, ulong> _modifiedDelegate; private readonly Action<ulong, ulong> _modifiedDelegate;
private int _modifiedSequenceOffset;
private bool _modified;
/// <summary> /// <summary>
/// Creates a new instance of the GPU resource pool. /// Creates a new instance of the GPU resource pool.
/// </summary> /// </summary>
@ -79,6 +84,16 @@ namespace Ryujinx.Graphics.Gpu.Image
return PhysicalMemory.Read<T2>(Address + (ulong)id * DescriptorSize); return PhysicalMemory.Read<T2>(Address + (ulong)id * DescriptorSize);
} }
/// <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> /// <summary>
/// Gets the GPU resource with the given ID. /// Gets the GPU resource with the given ID.
/// </summary> /// </summary>
@ -93,7 +108,13 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
public void SynchronizeMemory() public void SynchronizeMemory()
{ {
_modified = false;
_memoryTracking.QueryModified(_modifiedDelegate); _memoryTracking.QueryModified(_modifiedDelegate);
if (_modified)
{
UpdateModifiedSequence();
}
} }
/// <summary> /// <summary>
@ -103,6 +124,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="mSize">Size of the modified region</param> /// <param name="mSize">Size of the modified region</param>
private void RegionModified(ulong mAddress, ulong mSize) private void RegionModified(ulong mAddress, ulong mSize)
{ {
_modified = true;
if (mAddress < Address) if (mAddress < Address)
{ {
mAddress = Address; mAddress = Address;
@ -118,6 +141,15 @@ namespace Ryujinx.Graphics.Gpu.Image
InvalidateRangeImpl(mAddress, mSize); InvalidateRangeImpl(mAddress, mSize);
} }
/// <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> /// <summary>
/// An action to be performed when a precise memory access occurs to this resource. /// An action to be performed when a precise memory access occurs to this resource.
/// Makes sure that the dirty flags are checked. /// Makes sure that the dirty flags are checked.
@ -129,6 +161,16 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
if (write && Context.SequenceNumber == SequenceNumber) 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--; SequenceNumber--;
} }

View file

@ -8,6 +8,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
class Sampler : IDisposable class Sampler : IDisposable
{ {
/// <summary>
/// True if the sampler is disposed, false otherwise.
/// </summary>
public bool IsDisposed { get; private set; }
/// <summary> /// <summary>
/// Host sampler object. /// Host sampler object.
/// </summary> /// </summary>
@ -101,6 +106,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
IsDisposed = true;
_hostSampler.Dispose(); _hostSampler.Dispose();
_anisoSampler?.Dispose(); _anisoSampler?.Dispose();
} }

View file

@ -48,6 +48,8 @@ namespace Ryujinx.Graphics.Gpu.Image
Items[i] = null; Items[i] = null;
} }
} }
UpdateModifiedSequence();
} }
SequenceNumber = Context.SequenceNumber; SequenceNumber = Context.SequenceNumber;
@ -71,6 +73,39 @@ namespace Ryujinx.Graphics.Gpu.Image
return sampler; return sampler;
} }
/// <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> /// <summary>
/// Implementation of the sampler pool range invalidation. /// Implementation of the sampler pool range invalidation.
/// </summary> /// </summary>

View file

@ -100,6 +100,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
public bool AlwaysFlushOnOverlap { get; private set; } 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 _depth;
private int _layers; private int _layers;
public int FirstLayer { get; private set; } public int FirstLayer { get; private set; }
@ -1407,6 +1412,7 @@ namespace Ryujinx.Graphics.Gpu.Image
DisposeTextures(); DisposeTextures();
HostTexture = hostTexture; HostTexture = hostTexture;
InvalidatedSequence++;
} }
/// <summary> /// <summary>
@ -1535,6 +1541,8 @@ namespace Ryujinx.Graphics.Gpu.Image
_poolOwners.Clear(); _poolOwners.Clear();
} }
InvalidatedSequence++;
} }
/// <summary> /// <summary>

View file

@ -1,8 +1,12 @@
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Types; using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.Shader;
using Ryujinx.Graphics.Shader; using Ryujinx.Graphics.Shader;
using System; using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Image namespace Ryujinx.Graphics.Gpu.Image
{ {
@ -35,17 +39,24 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
public ITexture Texture; public ITexture Texture;
public ISampler Sampler; public ISampler Sampler;
public int TextureHandle;
public int SamplerHandle;
public int InvalidatedSequence;
public Texture CachedTexture;
public Sampler CachedSampler;
} }
private readonly TextureStatePerStage[][] _textureState; private TextureStatePerStage[] _textureState;
private readonly TextureStatePerStage[][] _imageState; private TextureStatePerStage[] _imageState;
private int[] _textureBindingsCount; private int[] _textureBindingsCount;
private int[] _imageBindingsCount; private int[] _imageBindingsCount;
private int _textureBufferIndex; private int _texturePoolSequence;
private int _samplerPoolSequence;
private bool _rebind; private int _textureBufferIndex;
private readonly float[] _scales; private readonly float[] _scales;
private bool _scaleChanged; private bool _scaleChanged;
@ -72,8 +83,8 @@ namespace Ryujinx.Graphics.Gpu.Image
_textureBindings = new TextureBindingInfo[stages][]; _textureBindings = new TextureBindingInfo[stages][];
_imageBindings = new TextureBindingInfo[stages][]; _imageBindings = new TextureBindingInfo[stages][];
_textureState = new TextureStatePerStage[stages][]; _textureState = new TextureStatePerStage[InitialTextureStateSize];
_imageState = new TextureStatePerStage[stages][]; _imageState = new TextureStatePerStage[InitialImageStateSize];
_textureBindingsCount = new int[stages]; _textureBindingsCount = new int[stages];
_imageBindingsCount = new int[stages]; _imageBindingsCount = new int[stages];
@ -82,9 +93,6 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
_textureBindings[stage] = new TextureBindingInfo[InitialTextureStateSize]; _textureBindings[stage] = new TextureBindingInfo[InitialTextureStateSize];
_imageBindings[stage] = new TextureBindingInfo[InitialImageStateSize]; _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) if (count > _textureBindings[stage].Length)
{ {
Array.Resize(ref _textureBindings[stage], count); 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; _textureBindingsCount[stage] = count;
@ -126,15 +125,6 @@ namespace Ryujinx.Graphics.Gpu.Image
if (count > _imageBindings[stage].Length) if (count > _imageBindings[stage].Length)
{ {
Array.Resize(ref _imageBindings[stage], count); Array.Resize(ref _imageBindings[stage], count);
Array.Resize(ref _imageState[stage], count);
}
int toClear = Math.Max(_imageBindingsCount[stage], count);
TextureStatePerStage[] state = _imageState[stage];
for (int i = 0; i < toClear; i++)
{
state[i] = new TextureStatePerStage();
} }
_imageBindingsCount[stage] = count; _imageBindingsCount[stage] = count;
@ -142,6 +132,24 @@ namespace Ryujinx.Graphics.Gpu.Image
return _imageBindings[stage]; return _imageBindings[stage];
} }
/// <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)
{
Array.Resize(ref _textureState, maxTextureBinding + 1);
}
if (maxImageBinding >= _imageState.Length)
{
Array.Resize(ref _imageState, maxImageBinding + 1);
}
}
/// <summary> /// <summary>
/// Sets the textures constant buffer index. /// Sets the textures constant buffer index.
/// The constant buffer specified holds the texture handles. /// The constant buffer specified holds the texture handles.
@ -323,7 +331,9 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Ensures that the bindings are visible to the host GPU. /// Ensures that the bindings are visible to the host GPU.
/// Note: this actually performs the binding using the host graphics API. /// Note: this actually performs the binding using the host graphics API.
/// </summary> /// </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; ulong texturePoolAddress = _texturePoolAddress;
@ -331,10 +341,38 @@ namespace Ryujinx.Graphics.Gpu.Image
? _texturePoolCache.FindOrCreate(_channel, texturePoolAddress, _texturePoolMaximumId) ? _texturePoolCache.FindOrCreate(_channel, texturePoolAddress, _texturePoolMaximumId)
: null; : 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) if (_isCompute)
{ {
CommitTextureBindings(texturePool, ShaderStage.Compute, 0); specStateMatches &= CommitTextureBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState);
CommitImageBindings (texturePool, ShaderStage.Compute, 0); specStateMatches &= CommitImageBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState);
} }
else else
{ {
@ -342,14 +380,57 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
int stageIndex = (int)stage - 1; int stageIndex = (int)stage - 1;
CommitTextureBindings(texturePool, stage, stageIndex); specStateMatches &= CommitTextureBindings(texturePool, stage, stageIndex, poolModified, specState);
CommitImageBindings (texturePool, stage, stageIndex); specStateMatches &= CommitImageBindings(texturePool, stage, stageIndex, poolModified, specState);
} }
} }
CommitRenderScale(); 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> /// <summary>
@ -358,13 +439,16 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
/// <param name="pool">The current texture pool</param> /// <param name="pool">The current texture pool</param>
/// <param name="stage">The shader stage using the textures to be bound</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> /// <param name="stageIndex">The stage number of the specified shader stage</param
private void CommitTextureBindings(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 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]; int textureCount = _textureBindingsCount[stageIndex];
if (textureCount == 0) if (textureCount == 0)
{ {
return; return true;
} }
var samplerPool = _samplerPool; var samplerPool = _samplerPool;
@ -372,17 +456,26 @@ namespace Ryujinx.Graphics.Gpu.Image
if (pool == null) if (pool == null)
{ {
Logger.Error?.Print(LogClass.Gpu, $"Shader stage \"{stage}\" uses textures, but texture pool was not set."); 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++) for (int index = 0; index < textureCount; index++)
{ {
TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index]; TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index];
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex); (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
int packedId = ReadPackedId(stageIndex, bindingInfo.Handle, textureBufferIndex, samplerBufferIndex); UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex);
int textureId = UnpackTextureId(packedId);
int packedId = TextureHandle.ReadPackedId(bindingInfo.Handle, cachedTextureBuffer, cachedSamplerBuffer);
int textureId = TextureHandle.UnpackTextureId(packedId);
int samplerId; int samplerId;
if (_samplerIndex == SamplerIndex.ViaHeaderIndex) if (_samplerIndex == SamplerIndex.ViaHeaderIndex)
@ -391,10 +484,30 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
else 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); ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
@ -407,30 +520,36 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
else else
{ {
if (_textureState[stageIndex][index].Texture != hostTexture || _rebind) if (state.Texture != hostTexture)
{ {
if (UpdateScale(texture, bindingInfo, index, stage)) if (UpdateScale(texture, bindingInfo, index, stage))
{ {
hostTexture = texture?.GetTargetTexture(bindingInfo.Target); hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
} }
_textureState[stageIndex][index].Texture = hostTexture; state.Texture = hostTexture;
_context.Renderer.Pipeline.SetTexture(bindingInfo.Binding, hostTexture); _context.Renderer.Pipeline.SetTexture(bindingInfo.Binding, hostTexture);
} }
Sampler sampler = samplerPool?.Get(samplerId); Sampler sampler = samplerPool?.Get(samplerId);
state.CachedSampler = sampler;
ISampler hostSampler = sampler?.GetHostSampler(texture); 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); _context.Renderer.Pipeline.SetSampler(bindingInfo.Binding, hostSampler);
} }
state.CachedTexture = texture;
state.InvalidatedSequence = texture?.InvalidatedSequence ?? 0;
} }
} }
return specStateMatches;
} }
/// <summary> /// <summary>
@ -440,38 +559,72 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="pool">The current texture pool</param> /// <param name="pool">The current texture pool</param>
/// <param name="stage">The shader stage using the textures to be bound</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> /// <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]; int imageCount = _imageBindingsCount[stageIndex];
if (imageCount == 0) if (imageCount == 0)
{ {
return; return true;
} }
if (pool == null) if (pool == null)
{ {
Logger.Error?.Print(LogClass.Gpu, $"Shader stage \"{stage}\" uses images, but texture pool was not set."); 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. // Scales for images appear after the texture ones.
int baseScaleIndex = _textureBindingsCount[stageIndex]; 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++) for (int index = 0; index < imageCount; index++)
{ {
TextureBindingInfo bindingInfo = _imageBindings[stageIndex][index]; TextureBindingInfo bindingInfo = _imageBindings[stageIndex][index];
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex); (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
int packedId = ReadPackedId(stageIndex, bindingInfo.Handle, textureBufferIndex, samplerBufferIndex); UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex);
int textureId = UnpackTextureId(packedId);
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); 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) if (hostTexture != null && texture.Target == Target.TextureBuffer)
{ {
// Ensure that the buffer texture is using the correct buffer as storage. // Ensure that the buffer texture is using the correct buffer as storage.
@ -494,14 +647,14 @@ namespace Ryujinx.Graphics.Gpu.Image
texture?.SignalModified(); texture?.SignalModified();
} }
if (_imageState[stageIndex][index].Texture != hostTexture || _rebind) if (state.Texture != hostTexture)
{ {
if (UpdateScale(texture, bindingInfo, baseScaleIndex + index, stage)) if (UpdateScale(texture, bindingInfo, baseScaleIndex + index, stage))
{ {
hostTexture = texture?.GetTargetTexture(bindingInfo.Target); hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
} }
_imageState[stageIndex][index].Texture = hostTexture; state.Texture = hostTexture;
Format format = bindingInfo.Format; Format format = bindingInfo.Format;
@ -512,8 +665,13 @@ namespace Ryujinx.Graphics.Gpu.Image
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTexture, format); _context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTexture, format);
} }
state.CachedTexture = texture;
state.InvalidatedSequence = texture?.InvalidatedSequence ?? 0;
} }
} }
return specStateMatches;
} }
/// <summary> /// <summary>
@ -537,7 +695,7 @@ namespace Ryujinx.Graphics.Gpu.Image
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(cbufSlot, bufferIndex); (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(cbufSlot, bufferIndex);
int packedId = ReadPackedId(stageIndex, handle, textureBufferIndex, samplerBufferIndex); int packedId = ReadPackedId(stageIndex, handle, textureBufferIndex, samplerBufferIndex);
int textureId = UnpackTextureId(packedId); int textureId = TextureHandle.UnpackTextureId(packedId);
ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa); 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="textureBufferIndex">Index of the constant buffer holding the texture handles</param>
/// <param name="samplerBufferIndex">Index of the constant buffer holding the sampler 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> /// <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) private int ReadPackedId(int stageIndex, int wordOffset, int textureBufferIndex, int samplerBufferIndex)
{ {
(int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = TextureHandle.UnpackOffsets(wordOffset); (int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = TextureHandle.UnpackOffsets(wordOffset);
@ -590,32 +749,13 @@ namespace Ryujinx.Graphics.Gpu.Image
return handle; return handle;
} }
/// <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> /// <summary>
/// Force all bound textures and images to be rebound the next time CommitBindings is called. /// Force all bound textures and images to be rebound the next time CommitBindings is called.
/// </summary> /// </summary>
public void Rebind() public void Rebind()
{ {
_rebind = true; Array.Clear(_textureState);
Array.Clear(_imageState);
} }
/// <summary> /// <summary>

View file

@ -1,5 +1,6 @@
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Types; using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Shader;
using System; using System;
namespace Ryujinx.Graphics.Gpu.Image namespace Ryujinx.Graphics.Gpu.Image
@ -10,9 +11,11 @@ namespace Ryujinx.Graphics.Gpu.Image
class TextureManager : IDisposable class TextureManager : IDisposable
{ {
private readonly GpuContext _context; private readonly GpuContext _context;
private readonly GpuChannel _channel;
private readonly TextureBindingsManager _cpBindingsManager; private readonly TextureBindingsManager _cpBindingsManager;
private readonly TextureBindingsManager _gpBindingsManager; private readonly TextureBindingsManager _gpBindingsManager;
private readonly TexturePoolCache _texturePoolCache;
private readonly Texture[] _rtColors; private readonly Texture[] _rtColors;
private readonly ITexture[] _rtHostColors; private readonly ITexture[] _rtHostColors;
@ -35,6 +38,7 @@ namespace Ryujinx.Graphics.Gpu.Image
public TextureManager(GpuContext context, GpuChannel channel) public TextureManager(GpuContext context, GpuChannel channel)
{ {
_context = context; _context = context;
_channel = channel;
TexturePoolCache texturePoolCache = new TexturePoolCache(context); TexturePoolCache texturePoolCache = new TexturePoolCache(context);
@ -43,6 +47,7 @@ namespace Ryujinx.Graphics.Gpu.Image
_cpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, scales, isCompute: true); _cpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, scales, isCompute: true);
_gpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, scales, isCompute: false); _gpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, scales, isCompute: false);
_texturePoolCache = texturePoolCache;
_rtColors = new Texture[Constants.TotalRenderTargets]; _rtColors = new Texture[Constants.TotalRenderTargets];
_rtHostColors = new ITexture[Constants.TotalRenderTargets]; _rtHostColors = new ITexture[Constants.TotalRenderTargets];
@ -99,6 +104,16 @@ namespace Ryujinx.Graphics.Gpu.Image
_cpBindingsManager.SetTextureBufferIndex(index); _cpBindingsManager.SetTextureBufferIndex(index);
} }
/// <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> /// <summary>
/// Sets the texture constant buffer index on the graphics pipeline. /// Sets the texture constant buffer index on the graphics pipeline.
/// </summary> /// </summary>
@ -108,6 +123,16 @@ namespace Ryujinx.Graphics.Gpu.Image
_gpBindingsManager.SetTextureBufferIndex(index); _gpBindingsManager.SetTextureBufferIndex(index);
} }
/// <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> /// <summary>
/// Sets the current sampler pool on the compute pipeline. /// Sets the current sampler pool on the compute pipeline.
/// </summary> /// </summary>
@ -335,25 +360,48 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <summary> /// <summary>
/// Commits bindings on the compute pipeline. /// Commits bindings on the compute pipeline.
/// </summary> /// </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, // Every time we switch between graphics and compute work,
// we must rebind everything. // we must rebind everything.
// Since compute work happens less often, we always do that // Since compute work happens less often, we always do that
// before and after the compute dispatch. // before and after the compute dispatch.
_cpBindingsManager.Rebind(); _cpBindingsManager.Rebind();
_cpBindingsManager.CommitBindings(); bool result = _cpBindingsManager.CommitBindings(specState);
_gpBindingsManager.Rebind(); _gpBindingsManager.Rebind();
return result;
} }
/// <summary> /// <summary>
/// Commits bindings on the graphics pipeline. /// Commits bindings on the graphics pipeline.
/// </summary> /// </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(); 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> /// <summary>

View file

@ -14,6 +14,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
private readonly GpuChannel _channel; private readonly GpuChannel _channel;
private readonly ConcurrentQueue<Texture> _dereferenceQueue = new ConcurrentQueue<Texture>(); private readonly ConcurrentQueue<Texture> _dereferenceQueue = new ConcurrentQueue<Texture>();
private TextureDescriptor _defaultDescriptor;
/// <summary> /// <summary>
/// Intrusive linked list node used on the texture pool cache. /// Intrusive linked list node used on the texture pool cache.
@ -32,6 +33,62 @@ namespace Ryujinx.Graphics.Gpu.Image
_channel = channel; _channel = channel;
} }
/// <summary>
/// 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>
/// <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)
{
texture = Items[id];
ref readonly TextureDescriptor descriptor = ref GetDescriptorRef(id);
if (texture == null)
{
TextureInfo info = GetInfo(descriptor, out int layerSize);
ProcessDereferenceQueue();
texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
// If this happens, then the texture address is invalid, we can't add it to the cache.
if (texture == null)
{
return ref descriptor;
}
texture.IncrementReferenceCount(this, id);
Items[id] = texture;
DescriptorCache[id] = descriptor;
}
else
{
if (texture.ChangedSize)
{
// 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.
int baseLevel = descriptor.UnpackBaseLevel();
int width = Math.Max(1, descriptor.UnpackWidth() >> baseLevel);
int height = Math.Max(1, descriptor.UnpackHeight() >> baseLevel);
if (texture.Info.Width != width || texture.Info.Height != height)
{
texture.ChangeSize(width, height, texture.Info.DepthOrLayers);
}
}
// Memory is automatically synchronized on texture creation.
texture.SynchronizeMemory();
}
return ref descriptor;
}
/// <summary> /// <summary>
/// Gets the texture with the given ID. /// Gets the texture with the given ID.
/// </summary> /// </summary>
@ -51,56 +108,49 @@ namespace Ryujinx.Graphics.Gpu.Image
SynchronizeMemory(); SynchronizeMemory();
} }
Texture texture = Items[id]; GetInternal(id, out Texture texture);
if (texture == null)
{
TextureDescriptor descriptor = GetDescriptor(id);
TextureInfo info = GetInfo(descriptor, out int layerSize);
ProcessDereferenceQueue();
texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
// If this happens, then the texture address is invalid, we can't add it to the cache.
if (texture == null)
{
return null;
}
texture.IncrementReferenceCount(this, id);
Items[id] = texture;
DescriptorCache[id] = descriptor;
}
else
{
if (texture.ChangedSize)
{
// 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);
if (texture.Info.Width != width || texture.Info.Height != height)
{
texture.ChangeSize(width, height, texture.Info.DepthOrLayers);
}
}
// Memory is automatically synchronized on texture creation.
texture.SynchronizeMemory();
}
return 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> /// <summary>
/// Forcibly remove a texture from this pool's items. /// Forcibly remove a texture from this pool's items.
/// If deferred, the dereference will be queued to occur on the render thread. /// If deferred, the dereference will be queued to occur on the render thread.
@ -175,7 +225,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="descriptor">The texture descriptor</param> /// <param name="descriptor">The texture descriptor</param>
/// <param name="layerSize">Layer size for textures using a sub-range of mipmap levels, otherwise 0</param> /// <param name="layerSize">Layer size for textures using a sub-range of mipmap levels, otherwise 0</param>
/// <returns>The texture information</returns> /// <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 depthOrLayers = descriptor.UnpackDepth();
int levels = descriptor.UnpackLevels(); int levels = descriptor.UnpackLevels();

View file

@ -378,6 +378,25 @@ namespace Ryujinx.Graphics.Gpu.Memory
return _gpUniformBuffers[stage].Buffers[index].Address; return _gpUniformBuffers[stage].Buffers[index].Address;
} }
/// <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> /// <summary>
/// Ensures that the compute engine bindings are visible to the host GPU. /// Ensures that the compute engine bindings are visible to the host GPU.
/// Note: this actually performs the binding using the host graphics API. /// Note: this actually performs the binding using the host graphics API.

View file

@ -35,6 +35,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
HostProgram = hostProgram; HostProgram = hostProgram;
SpecializationState = specializationState; SpecializationState = specializationState;
Shaders = shaders; Shaders = shaders;
SpecializationState.Prepare(shaders);
} }
/// <summary> /// <summary>

View file

@ -418,7 +418,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{ {
if (IsShaderEqual(channel.MemoryManager, cpShader.Shaders[0], gpuVa)) if (IsShaderEqual(channel.MemoryManager, cpShader.Shaders[0], gpuVa))
{ {
return cpShader.SpecializationState.MatchesCompute(channel, poolState); return cpShader.SpecializationState.MatchesCompute(channel, poolState, true);
} }
return false; 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> /// <summary>

View file

@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{ {
foreach (var entry in _entries) foreach (var entry in _entries)
{ {
if (entry.SpecializationState.MatchesGraphics(channel, poolState, graphicsState)) if (entry.SpecializationState.MatchesGraphics(channel, poolState, graphicsState, true))
{ {
program = entry; program = entry;
return true; return true;
@ -57,7 +57,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{ {
foreach (var entry in _entries) foreach (var entry in _entries)
{ {
if (entry.SpecializationState.MatchesCompute(channel, poolState)) if (entry.SpecializationState.MatchesCompute(channel, poolState, true))
{ {
program = entry; program = entry;
return true; return true;

View file

@ -1,9 +1,14 @@
using Ryujinx.Common.Memory; using Ryujinx.Common.Memory;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.Shader.DiskCache; using Ryujinx.Graphics.Gpu.Shader.DiskCache;
using Ryujinx.Graphics.Shader; using Ryujinx.Graphics.Shader;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Shader namespace Ryujinx.Graphics.Gpu.Shader
{ {
@ -158,6 +163,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
} }
private readonly Dictionary<TextureKey, Box<TextureSpecializationState>> _textureSpecialization; private readonly Dictionary<TextureKey, Box<TextureSpecializationState>> _textureSpecialization;
private KeyValuePair<TextureKey, Box<TextureSpecializationState>>[] _allTextures;
private Box<TextureSpecializationState>[][] _textureByBinding;
private Box<TextureSpecializationState>[][] _imageByBinding;
/// <summary> /// <summary>
/// Creates a new instance of the shader specialization state. /// Creates a new instance of the shader specialization state.
@ -194,6 +202,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> /// <summary>
/// Indicates that the shader accesses the early Z force state. /// Indicates that the shader accesses the early Z force state.
/// </summary> /// </summary>
@ -396,15 +446,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="channel">GPU channel</param> /// <param name="channel">GPU channel</param>
/// <param name="poolState">Texture pool state</param> /// <param name="poolState">Texture pool state</param>
/// <param name="graphicsState">Graphics 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> /// <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) if (graphicsState.ViewportTransformDisable != GraphicsState.ViewportTransformDisable)
{ {
return false; return false;
} }
return Matches(channel, poolState, isCompute: false); return Matches(channel, poolState, checkTextures, isCompute: false);
} }
/// <summary> /// <summary>
@ -412,10 +463,64 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary> /// </summary>
/// <param name="channel">GPU channel</param> /// <param name="channel">GPU channel</param>
/// <param name="poolState">Texture pool state</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> /// <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> /// <summary>
@ -423,9 +528,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary> /// </summary>
/// <param name="channel">GPU channel</param> /// <param name="channel">GPU channel</param>
/// <param name="poolState">Texture pool state</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> /// <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> /// <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; int constantBufferUsePerStageMask = _constantBufferUsePerStage;
@ -445,55 +551,60 @@ namespace Ryujinx.Graphics.Gpu.Shader
constantBufferUsePerStageMask &= ~(1 << index); constantBufferUsePerStageMask &= ~(1 << index);
} }
foreach (var kv in _textureSpecialization) if (checkTextures)
{
TexturePool pool = channel.TextureManager.GetTexturePool(poolState.TexturePoolGpuVa, poolState.TexturePoolMaximumId);
int cachedTextureBufferIndex = -1;
int cachedSamplerBufferIndex = -1;
int cachedStageIndex = -1;
ReadOnlySpan<int> cachedTextureBuffer = Span<int>.Empty;
ReadOnlySpan<int> cachedSamplerBuffer = Span<int>.Empty;
foreach (var kv in _allTextures)
{ {
TextureKey textureKey = kv.Key; TextureKey textureKey = kv.Key;
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(textureKey.CbufSlot, poolState.TextureBufferIndex); (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(textureKey.CbufSlot, poolState.TextureBufferIndex);
ulong textureCbAddress; UpdateCachedBuffer(channel,
ulong samplerCbAddress; isCompute,
ref cachedTextureBufferIndex,
ref cachedSamplerBufferIndex,
ref cachedTextureBuffer,
ref cachedSamplerBuffer,
ref cachedStageIndex,
textureBufferIndex,
samplerBufferIndex,
textureKey.StageIndex);
if (isCompute) 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))
{ {
textureCbAddress = channel.BufferManager.GetComputeUniformBufferAddress(textureBufferIndex); return false;
samplerCbAddress = channel.BufferManager.GetComputeUniformBufferAddress(samplerBufferIndex); }
} }
else
{
textureCbAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(textureKey.StageIndex, textureBufferIndex);
samplerCbAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(textureKey.StageIndex, samplerBufferIndex);
} }
if (!channel.MemoryManager.Physical.IsMapped(textureCbAddress) || !channel.MemoryManager.Physical.IsMapped(samplerCbAddress)) return true;
{
continue;
} }
Image.TextureDescriptor descriptor; /// <summary>
/// Checks if the recorded texture state matches the given texture descriptor.
if (isCompute) /// </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)
{ {
descriptor = channel.TextureManager.GetComputeTextureDescriptor( if (specializationState != null)
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);
}
Box<TextureSpecializationState> specializationState = kv.Value;
if (specializationState.Value.QueriedFlags.HasFlag(QueriedTextureStateFlags.CoordNormalized) && if (specializationState.Value.QueriedFlags.HasFlag(QueriedTextureStateFlags.CoordNormalized) &&
specializationState.Value.CoordNormalized != descriptor.UnpackTextureCoordNormalized()) specializationState.Value.CoordNormalized != descriptor.UnpackTextureCoordNormalized())
{ {
@ -504,6 +615,34 @@ namespace Ryujinx.Graphics.Gpu.Shader
return true; return true;
} }
/// <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> /// <summary>
/// Reads shader specialization state that has been serialized. /// Reads shader specialization state that has been serialized.
/// </summary> /// </summary>

View file

@ -1,3 +1,4 @@
using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace Ryujinx.Graphics.Shader namespace Ryujinx.Graphics.Shader
@ -50,5 +51,63 @@ namespace Ryujinx.Graphics.Shader
{ {
return (handle & 0x3fff, (handle >> 14) & 0x3fff, (TextureHandleType)((uint)handle >> 28)); 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;
}
} }
} }