diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs index d0c3bc5ae..9f5258e24 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)) + { + Logger.Error?.Print(LogClass.Gpu, "Oh my"); + UpdateShaderState(); + } + _channel.BufferManager.CommitGraphicsBindings(); } @@ -1148,6 +1153,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed return; } + int maxTextureBinding = 0; + int maxImageBinding = 0; + Span textureBindings = _channel.TextureManager.RentGraphicsTextureBindings(stage, info.Textures.Count); if (info.UsesRtLayer) @@ -1167,6 +1175,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); @@ -1185,8 +1198,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 f54ce1d70..ee931c199 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 { @@ -41,6 +42,8 @@ namespace Ryujinx.Graphics.Gpu.Image private readonly CpuMultiRegionHandle _memoryTracking; private readonly Action _modifiedDelegate; + private bool _modified; + /// /// Creates a new instance of the GPU resource pool. /// @@ -79,6 +82,16 @@ namespace Ryujinx.Graphics.Gpu.Image return PhysicalMemory.Read(Address + (ulong)id * DescriptorSize); } + /// + /// Gets a reference to the descriptor for a given ID. + /// + /// ID of the descriptor. This is effectively a zero-based index + /// A reference to the descriptor + public ref readonly T2 GetDescriptorRef(int id) + { + return ref MemoryMarshal.Cast(PhysicalMemory.GetSpan(Address + (ulong)id * DescriptorSize, DescriptorSize))[0]; + } + /// /// Gets the GPU resource with the given ID. /// @@ -91,9 +104,11 @@ namespace Ryujinx.Graphics.Gpu.Image /// This causes invalidation of pool entries, /// if a modification of entries by the CPU is detected. /// - public void SynchronizeMemory() + public bool SynchronizeMemory() { + _modified = false; _memoryTracking.QueryModified(_modifiedDelegate); + return _modified; } /// @@ -103,6 +118,8 @@ namespace Ryujinx.Graphics.Gpu.Image /// Size of the modified region private void RegionModified(ulong mAddress, ulong mSize) { + _modified = true; + if (mAddress < Address) { mAddress = Address; @@ -129,6 +146,7 @@ namespace Ryujinx.Graphics.Gpu.Image { if (write && Context.SequenceNumber == SequenceNumber) { + //Common.Logging.Logger.Error?.Print(Common.Logging.LogClass.Gpu, "Precise action..."); SequenceNumber--; } diff --git a/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs b/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs index e205ec487..daa2a0def 100644 --- a/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs +++ b/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs @@ -8,6 +8,7 @@ namespace Ryujinx.Graphics.Gpu.Image class SamplerPool : Pool { private float _forcedAnisotropy; + private int _modifiedSequenceNumber; /// /// Constructs a new instance of the sampler pool. @@ -52,7 +53,10 @@ namespace Ryujinx.Graphics.Gpu.Image SequenceNumber = Context.SequenceNumber; - SynchronizeMemory(); + if (SynchronizeMemory()) + { + _modifiedSequenceNumber = SequenceNumber; + } } Sampler sampler = Items[id]; @@ -71,6 +75,42 @@ namespace Ryujinx.Graphics.Gpu.Image return sampler; } + /// + /// Checks if the pool was modified, and returns the last sequence number where a modification was detected. + /// + /// The last sequence number where a modification was detected + 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; + } + } + + _modifiedSequenceNumber = SequenceNumber; + } + + if (SynchronizeMemory()) + { + _modifiedSequenceNumber = SequenceNumber; + } + } + + return _modifiedSequenceNumber; + } + /// /// Implementation of the sampler pool range invalidation. /// diff --git a/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs index 7ac4e12e2..d02985bfa 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 { @@ -11,7 +15,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// class TextureBindingsManager : IDisposable { - private const int InitialTextureStateSize = 32; + private const int InitialTextureStateSize = 32; // Should match binding range for host. private const int InitialImageStateSize = 8; private readonly GpuContext _context; @@ -35,18 +39,22 @@ 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 bool _rebind; - private readonly float[] _scales; private bool _scaleChanged; private int _lastFragmentTotal; @@ -72,8 +80,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 +90,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 +104,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,15 +122,6 @@ 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]; - - for (int i = 0; i < toClear; i++) - { - state[i] = new TextureStatePerStage(); } _imageBindingsCount[stage] = count; @@ -142,6 +129,24 @@ namespace Ryujinx.Graphics.Gpu.Image return _imageBindings[stage]; } + /// + /// Sets the max binding indexes for textures and images. + /// + /// The maximum texture binding + /// The maximum image binding + 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); + } + } + /// /// Sets the textures constant buffer index. /// The constant buffer specified holds the texture handles. @@ -319,11 +324,16 @@ namespace Ryujinx.Graphics.Gpu.Image } } + private int _texturePoolSequence; + private int _samplerPoolSequence; + /// /// Ensures that the bindings are visible to the host GPU. /// Note: this actually performs the binding using the host graphics API. /// - public void CommitBindings() + /// Specialization state for the bound shader + /// True if all bound textures match the current shader specialiation state, false otherwise + 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; + } + + /// + /// Fetch the constant buffers used for a texture to cache. + /// + /// Stage index of the constant buffer + /// The currently cached texture buffer index + /// The currently cached sampler buffer index + /// The currently cached texture buffer data + /// The currently cached sampler buffer data + /// The new texture buffer index + /// The new sampler buffer index + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void UpdateCachedBuffer( + int stageIndex, + ref int cachedTextureBufferIndex, + ref int cachedSamplerBufferIndex, + ref ReadOnlySpan cachedTextureBuffer, + ref ReadOnlySpan cachedSamplerBuffer, + int textureBufferIndex, + int samplerBufferIndex) + { + if (textureBufferIndex != cachedTextureBufferIndex) + { + ref BufferBounds bounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, textureBufferIndex); + + cachedTextureBuffer = MemoryMarshal.Cast(_channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size)); + cachedTextureBufferIndex = textureBufferIndex; + + if (samplerBufferIndex == textureBufferIndex) + { + cachedSamplerBuffer = cachedTextureBuffer; + cachedSamplerBufferIndex = samplerBufferIndex; + } + } + + if (cachedSamplerBufferIndex != samplerBufferIndex) + { + ref BufferBounds bounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, samplerBufferIndex); + + cachedSamplerBuffer = MemoryMarshal.Cast(_channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size)); + cachedSamplerBufferIndex = samplerBufferIndex; + } } /// @@ -358,13 +439,16 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// The current texture pool /// The shader stage using the textures to be bound - /// The stage number of the specified shader stage - private void CommitTextureBindings(TexturePool pool, ShaderStage stage, int stageIndex) + /// The stage number of the specified shader stageTrue if either the texture or sampler pool was modified, false otherwise + /// Specialization state for the bound shader + /// True if all bound textures match the current shader specialiation state, false otherwise + 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 cachedTextureBuffer = Span.Empty; + ReadOnlySpan cachedSamplerBuffer = Span.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) @@ -394,7 +487,27 @@ namespace Ryujinx.Graphics.Gpu.Image samplerId = 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; } /// @@ -440,38 +559,72 @@ namespace Ryujinx.Graphics.Gpu.Image /// The current texture pool /// The shader stage using the textures to be bound /// The stage number of the specified shader stage - private void CommitImageBindings(TexturePool pool, ShaderStage stage, int stageIndex) + /// True if either the texture or sampler pool was modified, false otherwise + /// Specialization state for the bound shader + /// True if all bound images match the current shader specialiation state, false otherwise + 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 cachedTextureBuffer = Span.Empty; + ReadOnlySpan cachedSamplerBuffer = Span.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; } /// @@ -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,13 +713,14 @@ namespace Ryujinx.Graphics.Gpu.Image /// Index of the constant buffer holding the texture handles /// Index of the constant buffer holding the sampler handles /// The packed texture and sampler ID (the real texture handle) + [MethodImpl(MethodImplOptions.AggressiveInlining)] private int ReadPackedId(int stageIndex, int wordOffset, int textureBufferIndex, int samplerBufferIndex) { (int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = TextureHandle.UnpackOffsets(wordOffset); ulong textureBufferAddress = _isCompute - ? _channel.BufferManager.GetComputeUniformBufferAddress(textureBufferIndex) - : _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, textureBufferIndex); + ? _channel.BufferManager.GetComputeUniformBufferAddress(textureBufferIndex) + : _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, textureBufferIndex); int handle = _channel.MemoryManager.Physical.Read(textureBufferAddress + (uint)textureWordOffset * 4); @@ -574,8 +733,8 @@ namespace Ryujinx.Graphics.Gpu.Image if (handleType != TextureHandleType.CombinedSampler) { ulong samplerBufferAddress = _isCompute - ? _channel.BufferManager.GetComputeUniformBufferAddress(samplerBufferIndex) - : _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, samplerBufferIndex); + ? _channel.BufferManager.GetComputeUniformBufferAddress(samplerBufferIndex) + : _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, samplerBufferIndex); int samplerHandle = _channel.MemoryManager.Physical.Read(samplerBufferAddress + (uint)samplerWordOffset * 4); @@ -590,16 +749,6 @@ namespace Ryujinx.Graphics.Gpu.Image return handle; } - /// - /// Unpacks the texture ID from the real texture handle. - /// - /// The real texture handle - /// The texture ID - private static int UnpackTextureId(int packedId) - { - return (packedId >> 0) & 0xfffff; - } - /// /// Unpacks the sampler ID from the real texture handle. /// @@ -615,7 +764,8 @@ namespace Ryujinx.Graphics.Gpu.Image /// public void Rebind() { - _rebind = true; + Array.Clear(_textureState); + Array.Clear(_imageState); } /// diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs index a1c292912..dea0c9688 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]; @@ -99,6 +104,16 @@ namespace Ryujinx.Graphics.Gpu.Image _cpBindingsManager.SetTextureBufferIndex(index); } + /// + /// Sets the max binding indexes on the compute pipeline. + /// + /// The maximum texture binding + /// The maximum image binding + public void SetComputeMaxBindings(int maxTextureBinding, int maxImageBinding) + { + _cpBindingsManager.SetMaxBindings(maxTextureBinding, maxImageBinding); + } + /// /// Sets the texture constant buffer index on the graphics pipeline. /// @@ -108,6 +123,16 @@ namespace Ryujinx.Graphics.Gpu.Image _gpBindingsManager.SetTextureBufferIndex(index); } + /// + /// Sets the max binding indexes on the graphics pipeline. + /// + /// The maximum texture binding + /// The maximum image binding + public void SetGraphicsMaxBindings(int maxTextureBinding, int maxImageBinding) + { + _gpBindingsManager.SetMaxBindings(maxTextureBinding, maxImageBinding); + } + /// /// Sets the current sampler pool on the compute pipeline. /// @@ -335,25 +360,48 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// Commits bindings on the compute pipeline. /// - public void CommitComputeBindings() + /// Specialization state for the bound shader + /// True if all bound textures match the current shader specialiation state, false otherwise + 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; } /// /// Commits bindings on the graphics pipeline. /// - public void CommitGraphicsBindings() + /// Specialization state for the bound shader + /// True if all bound textures match the current shader specialiation state, false otherwise + public bool CommitGraphicsBindings(ShaderSpecializationState specState) { - _gpBindingsManager.CommitBindings(); + bool result = _gpBindingsManager.CommitBindings(specState); UpdateRenderTargets(); + + return result; + } + + /// + /// Returns a texture pool from the cache, with the given address and maximum id. + /// + /// GPU virtual address of the texture pool + /// Maximum ID of the texture pool + /// The texture pool + public TexturePool GetTexturePool(ulong poolGpuVa, int maximumId) + { + ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa); + + TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId); + + return texturePool; } /// diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs index 10a6ff82a..5b3ca5cc8 100644 --- a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs +++ b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs @@ -14,6 +14,8 @@ namespace Ryujinx.Graphics.Gpu.Image { private readonly GpuChannel _channel; private readonly ConcurrentQueue _dereferenceQueue = new ConcurrentQueue(); + private int _modifiedSequenceNumber; + private TextureDescriptor _defaultDescriptor; /// /// Intrusive linked list node used on the texture pool cache. @@ -33,30 +35,19 @@ namespace Ryujinx.Graphics.Gpu.Image } /// - /// Gets the texture with the given ID. + /// Gets the texture descripor and texture with the given ID with no bounds check or synchronization. /// /// ID of the texture. This is effectively a zero-based index - /// The texture with the given ID - public override Texture Get(int id) + /// The texture with the given ID + /// The texture descriptor with the given ID + public ref readonly TextureDescriptor GetInternal(int id, out Texture texture) { - if ((uint)id >= Items.Length) - { - return null; - } + texture = Items[id]; - if (SequenceNumber != Context.SequenceNumber) - { - SequenceNumber = Context.SequenceNumber; - - SynchronizeMemory(); - } - - 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 +57,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 +73,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,9 +87,77 @@ namespace Ryujinx.Graphics.Gpu.Image texture.SynchronizeMemory(); } + return ref descriptor; + } + + /// + /// Gets the texture with the given ID. + /// + /// ID of the texture. This is effectively a zero-based index + /// The texture with the given ID + public override Texture Get(int id) + { + if ((uint)id >= Items.Length) + { + return null; + } + + if (SequenceNumber != Context.SequenceNumber) + { + SequenceNumber = Context.SequenceNumber; + + if (SynchronizeMemory()) + { + _modifiedSequenceNumber = SequenceNumber; + } + } + + GetInternal(id, out Texture texture); + return texture; } + /// + /// Gets the texture descripor and texture with the given ID. + /// + /// + /// This method assumes that the pool has been manually synchronized before doing binding. + /// + /// ID of the texture. This is effectively a zero-based index + /// The texture with the given ID + /// The texture descriptor with the given ID + 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); + } + + /// + /// Checks if the pool was modified, and returns the last sequence number where a modification was detected. + /// + /// The last sequence number where a modifiaction was detected + public int CheckModified() + { + if (SequenceNumber != Context.SequenceNumber) + { + SequenceNumber = Context.SequenceNumber; + + if (SynchronizeMemory()) + { + _modifiedSequenceNumber = SequenceNumber; + } + } + + return _modifiedSequenceNumber; + } + /// /// Forcibly remove a texture from this pool's items. /// If deferred, the dereference will be queued to occur on the render thread. @@ -175,7 +232,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// The texture descriptor /// Layer size for textures using a sub-range of mipmap levels, otherwise 0 /// The texture information - 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/Shader/ShaderSpecializationState.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs index 418c7b1a7..2adb6d44d 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> _textureSpecialization; + private KeyValuePair>[] _allTextures; + private Box[][] _textureByBinding; + private Box[][] _imageByBinding; /// /// Creates a new instance of the shader specialization state. @@ -194,6 +202,42 @@ namespace Ryujinx.Graphics.Gpu.Shader } } + public void Prepare(CachedShaderStage[] stages) + { + _allTextures = _textureSpecialization.ToArray(); + + _textureByBinding = new Box[stages.Length][]; + _imageByBinding = new Box[stages.Length][]; + + for (int i = 0; i < stages.Length; i++) + { + CachedShaderStage stage = stages[i]; + if (stage != null) + { + var textures = stage.Info.Textures; + var images = stage.Info.Images; + + var texBindings = new Box[textures.Count]; + var imageBindings = new Box[images.Count]; + + for (int j = 0; j < textures.Count; j++) + { + var texture = textures[j]; + texBindings[j] = GetTextureSpecState(i, texture.HandleIndex, texture.CbufSlot); + } + + for (int j = 0; j < images.Count; j++) + { + var image = images[j]; + imageBindings[j] = GetTextureSpecState(i, image.HandleIndex, image.CbufSlot); + } + + _textureByBinding[i] = texBindings; + _imageByBinding[i] = imageBindings; + } + } + } + /// /// Indicates that the shader accesses the early Z force state. /// @@ -396,15 +440,16 @@ namespace Ryujinx.Graphics.Gpu.Shader /// GPU channel /// Texture pool state /// Graphics state + /// Indicates whether texture descriptors should be checked /// True if the state matches, false otherwise - 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); } /// @@ -412,10 +457,64 @@ namespace Ryujinx.Graphics.Gpu.Shader /// /// GPU channel /// Texture pool state + /// Indicates whether texture descriptors should be checked /// True if the state matches, false otherwise - 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); + } + + /// + /// Fetch the constant buffers used for a texture to cache. + /// + /// GPU channel + /// Indicates whenever the check is requested by the 3D or compute engine + /// Stage index of the constant buffer + /// The currently cached texture buffer index + /// The currently cached sampler buffer index + /// The currently cached texture buffer data + /// The currently cached sampler buffer data + /// The currently cached stage + /// The new texture buffer index + /// The new sampler buffer index + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void UpdateCachedBuffer( + GpuChannel channel, + bool isCompute, + ref int cachedTextureBufferIndex, + ref int cachedSamplerBufferIndex, + ref ReadOnlySpan cachedTextureBuffer, + ref ReadOnlySpan 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(channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size)); + cachedTextureBufferIndex = textureBufferIndex; + + if (samplerBufferIndex == textureBufferIndex) + { + cachedSamplerBuffer = cachedTextureBuffer; + cachedSamplerBufferIndex = samplerBufferIndex; + } + } + + if (stageChange || cachedSamplerBufferIndex != samplerBufferIndex) + { + ref BufferBounds bounds = ref channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, samplerBufferIndex); + + cachedSamplerBuffer = MemoryMarshal.Cast(channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size)); + cachedSamplerBufferIndex = samplerBufferIndex; + } + + cachedStageIndex = stageIndex; } /// @@ -423,9 +522,10 @@ namespace Ryujinx.Graphics.Gpu.Shader /// /// GPU channel /// Texture pool state + /// Indicates whether texture descriptors should be checked /// Indicates whenever the check is requested by the 3D or compute engine /// True if the state matches, false otherwise - 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 +545,62 @@ namespace Ryujinx.Graphics.Gpu.Shader constantBufferUsePerStageMask &= ~(1 << index); } - foreach (var kv in _textureSpecialization) + if (checkTextures) { - TextureKey textureKey = kv.Key; + TexturePool pool = channel.TextureManager.GetTexturePool(poolState.TexturePoolGpuVa, poolState.TexturePoolMaximumId); - (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(textureKey.CbufSlot, poolState.TextureBufferIndex); + int cachedTextureBufferIndex = -1; + int cachedSamplerBufferIndex = -1; + int cachedStageIndex = -1; + ReadOnlySpan cachedTextureBuffer = Span.Empty; + ReadOnlySpan cachedSamplerBuffer = Span.Empty; - ulong textureCbAddress; - ulong samplerCbAddress; - - 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); + + UpdateCachedBuffer(channel, + isCompute, + ref cachedTextureBufferIndex, + ref cachedSamplerBufferIndex, + ref cachedTextureBuffer, + ref cachedSamplerBuffer, + ref cachedStageIndex, + textureBufferIndex, + samplerBufferIndex, + textureKey.StageIndex); + + int packedId = TextureHandle.ReadPackedId(textureKey.Handle, cachedTextureBuffer, cachedSamplerBuffer); + + int textureId = TextureHandle.UnpackTextureId(packedId); + + ref readonly Image.TextureDescriptor descriptor = ref pool.GetDescriptorRef(textureId); + + Box specializationState = kv.Value; + + if (specializationState.Value.QueriedFlags.HasFlag(QueriedTextureStateFlags.CoordNormalized) && + specializationState.Value.CoordNormalized != descriptor.UnpackTextureCoordNormalized()) + { + return false; + } } + } - Image.TextureDescriptor descriptor; - - 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); - } - - Box specializationState = kv.Value; + return true; + } + /// + /// Checks if the recorded texture state matches the given texture descriptor. + /// + /// Texture specialization state + /// Texture descriptor + /// True if the state matches, false otherwise + private bool MatchesTexture(Box specializationState, in Image.TextureDescriptor descriptor) + { + if (specializationState != null) + { if (specializationState.Value.QueriedFlags.HasFlag(QueriedTextureStateFlags.CoordNormalized) && specializationState.Value.CoordNormalized != descriptor.UnpackTextureCoordNormalized()) { @@ -504,6 +611,34 @@ namespace Ryujinx.Graphics.Gpu.Shader return true; } + /// + /// Checks if the recorded texture state for a given texture binding matches a texture descriptor. + /// + /// The shader stage + /// The texture index + /// Texture descriptor + /// True if the state matches, false otherwise + public bool MatchesTexture(ShaderStage stage, int index, in Image.TextureDescriptor descriptor) + { + Box specializationState = _textureByBinding[(int)stage][index]; + + return MatchesTexture(specializationState, descriptor); + } + + /// + /// Checks if the recorded texture state for a given image binding matches a texture descriptor. + /// + /// The shader stage + /// The texture index + /// Texture descriptor + /// True if the state matches, false otherwise + public bool MatchesImage(ShaderStage stage, int index, in Image.TextureDescriptor descriptor) + { + Box specializationState = _imageByBinding[(int)stage][index]; + + return MatchesTexture(specializationState, descriptor); + } + /// /// Reads shader specialization state that has been serialized. ///