diff options
Diffstat (limited to 'Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs')
-rw-r--r-- | Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs | 290 |
1 files changed, 215 insertions, 75 deletions
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs index 7ac4e12e..f15f8885 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs @@ -1,8 +1,12 @@ using Ryujinx.Common.Logging; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Engine.Types; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Gpu.Shader; using Ryujinx.Graphics.Shader; using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace Ryujinx.Graphics.Gpu.Image { @@ -35,17 +39,24 @@ namespace Ryujinx.Graphics.Gpu.Image { public ITexture Texture; public ISampler Sampler; + + public int TextureHandle; + public int SamplerHandle; + public int InvalidatedSequence; + public Texture CachedTexture; + public Sampler CachedSampler; } - private readonly TextureStatePerStage[][] _textureState; - private readonly TextureStatePerStage[][] _imageState; + private TextureStatePerStage[] _textureState; + private TextureStatePerStage[] _imageState; private int[] _textureBindingsCount; private int[] _imageBindingsCount; - private int _textureBufferIndex; + private int _texturePoolSequence; + private int _samplerPoolSequence; - private bool _rebind; + private int _textureBufferIndex; private readonly float[] _scales; private bool _scaleChanged; @@ -72,8 +83,8 @@ namespace Ryujinx.Graphics.Gpu.Image _textureBindings = new TextureBindingInfo[stages][]; _imageBindings = new TextureBindingInfo[stages][]; - _textureState = new TextureStatePerStage[stages][]; - _imageState = new TextureStatePerStage[stages][]; + _textureState = new TextureStatePerStage[InitialTextureStateSize]; + _imageState = new TextureStatePerStage[InitialImageStateSize]; _textureBindingsCount = new int[stages]; _imageBindingsCount = new int[stages]; @@ -82,9 +93,6 @@ namespace Ryujinx.Graphics.Gpu.Image { _textureBindings[stage] = new TextureBindingInfo[InitialTextureStateSize]; _imageBindings[stage] = new TextureBindingInfo[InitialImageStateSize]; - - _textureState[stage] = new TextureStatePerStage[InitialTextureStateSize]; - _imageState[stage] = new TextureStatePerStage[InitialImageStateSize]; } } @@ -99,15 +107,6 @@ namespace Ryujinx.Graphics.Gpu.Image if (count > _textureBindings[stage].Length) { Array.Resize(ref _textureBindings[stage], count); - Array.Resize(ref _textureState[stage], count); - } - - int toClear = Math.Max(_textureBindingsCount[stage], count); - TextureStatePerStage[] state = _textureState[stage]; - - for (int i = 0; i < toClear; i++) - { - state[i] = new TextureStatePerStage(); } _textureBindingsCount[stage] = count; @@ -126,20 +125,29 @@ namespace Ryujinx.Graphics.Gpu.Image if (count > _imageBindings[stage].Length) { Array.Resize(ref _imageBindings[stage], count); - Array.Resize(ref _imageState[stage], count); } - int toClear = Math.Max(_imageBindingsCount[stage], count); - TextureStatePerStage[] state = _imageState[stage]; + _imageBindingsCount[stage] = count; + + return _imageBindings[stage]; + } - for (int i = 0; i < toClear; i++) + /// <summary> + /// Sets the max binding indexes for textures and images. + /// </summary> + /// <param name="maxTextureBinding">The maximum texture binding</param> + /// <param name="maxImageBinding">The maximum image binding</param> + public void SetMaxBindings(int maxTextureBinding, int maxImageBinding) + { + if (maxTextureBinding >= _textureState.Length) { - state[i] = new TextureStatePerStage(); + Array.Resize(ref _textureState, maxTextureBinding + 1); } - _imageBindingsCount[stage] = count; - - return _imageBindings[stage]; + if (maxImageBinding >= _imageState.Length) + { + Array.Resize(ref _imageState, maxImageBinding + 1); + } } /// <summary> @@ -323,7 +331,9 @@ namespace Ryujinx.Graphics.Gpu.Image /// Ensures that the bindings are visible to the host GPU. /// Note: this actually performs the binding using the host graphics API. /// </summary> - public void CommitBindings() + /// <param name="specState">Specialization state for the bound shader</param> + /// <returns>True if all bound textures match the current shader specialiation state, false otherwise</returns> + public bool CommitBindings(ShaderSpecializationState specState) { ulong texturePoolAddress = _texturePoolAddress; @@ -331,10 +341,38 @@ namespace Ryujinx.Graphics.Gpu.Image ? _texturePoolCache.FindOrCreate(_channel, texturePoolAddress, _texturePoolMaximumId) : null; + // Check if the texture pool has been modified since bindings were last committed. + // If it wasn't, then it's possible to avoid looking up textures again when the handle remains the same. + bool poolModified = false; + + if (texturePool != null) + { + int texturePoolSequence = texturePool.CheckModified(); + + if (_texturePoolSequence != texturePoolSequence) + { + poolModified = true; + _texturePoolSequence = texturePoolSequence; + } + } + + if (_samplerPool != null) + { + int samplerPoolSequence = _samplerPool.CheckModified(); + + if (_samplerPoolSequence != samplerPoolSequence) + { + poolModified = true; + _samplerPoolSequence = samplerPoolSequence; + } + } + + bool specStateMatches = true; + if (_isCompute) { - CommitTextureBindings(texturePool, ShaderStage.Compute, 0); - CommitImageBindings (texturePool, ShaderStage.Compute, 0); + specStateMatches &= CommitTextureBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState); + specStateMatches &= CommitImageBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState); } else { @@ -342,14 +380,57 @@ namespace Ryujinx.Graphics.Gpu.Image { int stageIndex = (int)stage - 1; - CommitTextureBindings(texturePool, stage, stageIndex); - CommitImageBindings (texturePool, stage, stageIndex); + specStateMatches &= CommitTextureBindings(texturePool, stage, stageIndex, poolModified, specState); + specStateMatches &= CommitImageBindings(texturePool, stage, stageIndex, poolModified, specState); } } CommitRenderScale(); - _rebind = false; + return specStateMatches; + } + + /// <summary> + /// Fetch the constant buffers used for a texture to cache. + /// </summary> + /// <param name="stageIndex">Stage index of the constant buffer</param> + /// <param name="cachedTextureBufferIndex">The currently cached texture buffer index</param> + /// <param name="cachedSamplerBufferIndex">The currently cached sampler buffer index</param> + /// <param name="cachedTextureBuffer">The currently cached texture buffer data</param> + /// <param name="cachedSamplerBuffer">The currently cached sampler buffer data</param> + /// <param name="textureBufferIndex">The new texture buffer index</param> + /// <param name="samplerBufferIndex">The new sampler buffer index</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void UpdateCachedBuffer( + int stageIndex, + ref int cachedTextureBufferIndex, + ref int cachedSamplerBufferIndex, + ref ReadOnlySpan<int> cachedTextureBuffer, + ref ReadOnlySpan<int> cachedSamplerBuffer, + int textureBufferIndex, + int samplerBufferIndex) + { + if (textureBufferIndex != cachedTextureBufferIndex) + { + ref BufferBounds bounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, textureBufferIndex); + + cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size)); + cachedTextureBufferIndex = textureBufferIndex; + + if (samplerBufferIndex == textureBufferIndex) + { + cachedSamplerBuffer = cachedTextureBuffer; + cachedSamplerBufferIndex = samplerBufferIndex; + } + } + + if (samplerBufferIndex != cachedSamplerBufferIndex) + { + ref BufferBounds bounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, samplerBufferIndex); + + cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size)); + cachedSamplerBufferIndex = samplerBufferIndex; + } } /// <summary> @@ -358,13 +439,16 @@ namespace Ryujinx.Graphics.Gpu.Image /// </summary> /// <param name="pool">The current texture pool</param> /// <param name="stage">The shader stage using the textures to be bound</param> - /// <param name="stageIndex">The stage number of the specified shader stage</param> - private void CommitTextureBindings(TexturePool pool, ShaderStage stage, int stageIndex) + /// <param name="stageIndex">The stage number of the specified shader stage</param + /// <param name="poolModified">True if either the texture or sampler pool was modified, false otherwise</param> + /// <param name="specState">Specialization state for the bound shader</param> + /// <returns>True if all bound textures match the current shader specialiation state, false otherwise</returns> + private bool CommitTextureBindings(TexturePool pool, ShaderStage stage, int stageIndex, bool poolModified, ShaderSpecializationState specState) { int textureCount = _textureBindingsCount[stageIndex]; if (textureCount == 0) { - return; + return true; } var samplerPool = _samplerPool; @@ -372,17 +456,26 @@ namespace Ryujinx.Graphics.Gpu.Image if (pool == null) { Logger.Error?.Print(LogClass.Gpu, $"Shader stage \"{stage}\" uses textures, but texture pool was not set."); - return; + return true; } + bool specStateMatches = true; + + int cachedTextureBufferIndex = -1; + int cachedSamplerBufferIndex = -1; + ReadOnlySpan<int> cachedTextureBuffer = Span<int>.Empty; + ReadOnlySpan<int> cachedSamplerBuffer = Span<int>.Empty; + for (int index = 0; index < textureCount; index++) { TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index]; (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex); - int packedId = ReadPackedId(stageIndex, bindingInfo.Handle, textureBufferIndex, samplerBufferIndex); - int textureId = UnpackTextureId(packedId); + UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex); + + int packedId = TextureHandle.ReadPackedId(bindingInfo.Handle, cachedTextureBuffer, cachedSamplerBuffer); + int textureId = TextureHandle.UnpackTextureId(packedId); int samplerId; if (_samplerIndex == SamplerIndex.ViaHeaderIndex) @@ -391,10 +484,30 @@ namespace Ryujinx.Graphics.Gpu.Image } else { - samplerId = UnpackSamplerId(packedId); + samplerId = TextureHandle.UnpackSamplerId(packedId); } - Texture texture = pool.Get(textureId); + ref TextureStatePerStage state = ref _textureState[bindingInfo.Binding]; + + if (!poolModified && + state.TextureHandle == textureId && + state.SamplerHandle == samplerId && + state.CachedTexture != null && + state.CachedTexture.InvalidatedSequence == state.InvalidatedSequence && + state.CachedSampler?.IsDisposed != true) + { + // The texture is already bound. + state.CachedTexture.SynchronizeMemory(); + + continue; + } + + state.TextureHandle = textureId; + state.SamplerHandle = samplerId; + + ref readonly TextureDescriptor descriptor = ref pool.GetForBinding(textureId, out Texture texture); + + specStateMatches &= specState.MatchesTexture(stage, index, descriptor); ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target); @@ -407,30 +520,36 @@ namespace Ryujinx.Graphics.Gpu.Image } else { - if (_textureState[stageIndex][index].Texture != hostTexture || _rebind) + if (state.Texture != hostTexture) { if (UpdateScale(texture, bindingInfo, index, stage)) { hostTexture = texture?.GetTargetTexture(bindingInfo.Target); } - _textureState[stageIndex][index].Texture = hostTexture; + state.Texture = hostTexture; _context.Renderer.Pipeline.SetTexture(bindingInfo.Binding, hostTexture); } Sampler sampler = samplerPool?.Get(samplerId); + state.CachedSampler = sampler; ISampler hostSampler = sampler?.GetHostSampler(texture); - if (_textureState[stageIndex][index].Sampler != hostSampler || _rebind) + if (state.Sampler != hostSampler) { - _textureState[stageIndex][index].Sampler = hostSampler; + state.Sampler = hostSampler; _context.Renderer.Pipeline.SetSampler(bindingInfo.Binding, hostSampler); } + + state.CachedTexture = texture; + state.InvalidatedSequence = texture?.InvalidatedSequence ?? 0; } } + + return specStateMatches; } /// <summary> @@ -440,38 +559,72 @@ namespace Ryujinx.Graphics.Gpu.Image /// <param name="pool">The current texture pool</param> /// <param name="stage">The shader stage using the textures to be bound</param> /// <param name="stageIndex">The stage number of the specified shader stage</param> - private void CommitImageBindings(TexturePool pool, ShaderStage stage, int stageIndex) + /// <param name="poolModified">True if either the texture or sampler pool was modified, false otherwise</param> + /// <param name="specState">Specialization state for the bound shader</param> + /// <returns>True if all bound images match the current shader specialiation state, false otherwise</returns> + private bool CommitImageBindings(TexturePool pool, ShaderStage stage, int stageIndex, bool poolModified, ShaderSpecializationState specState) { int imageCount = _imageBindingsCount[stageIndex]; if (imageCount == 0) { - return; + return true; } if (pool == null) { Logger.Error?.Print(LogClass.Gpu, $"Shader stage \"{stage}\" uses images, but texture pool was not set."); - return; + return true; } // Scales for images appear after the texture ones. int baseScaleIndex = _textureBindingsCount[stageIndex]; + int cachedTextureBufferIndex = -1; + int cachedSamplerBufferIndex = -1; + ReadOnlySpan<int> cachedTextureBuffer = Span<int>.Empty; + ReadOnlySpan<int> cachedSamplerBuffer = Span<int>.Empty; + + bool specStateMatches = true; + for (int index = 0; index < imageCount; index++) { TextureBindingInfo bindingInfo = _imageBindings[stageIndex][index]; (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex); - int packedId = ReadPackedId(stageIndex, bindingInfo.Handle, textureBufferIndex, samplerBufferIndex); - int textureId = UnpackTextureId(packedId); + UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex); - Texture texture = pool.Get(textureId); + int packedId = TextureHandle.ReadPackedId(bindingInfo.Handle, cachedTextureBuffer, cachedSamplerBuffer); + int textureId = TextureHandle.UnpackTextureId(packedId); - ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target); + ref TextureStatePerStage state = ref _imageState[bindingInfo.Binding]; bool isStore = bindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore); + if (!poolModified && + state.TextureHandle == textureId && + state.CachedTexture != null && + state.CachedTexture.InvalidatedSequence == state.InvalidatedSequence) + { + // The texture is already bound. + state.CachedTexture.SynchronizeMemory(); + + if (isStore) + { + state.CachedTexture?.SignalModified(); + } + + continue; + } + + state.TextureHandle = textureId; + + ref readonly TextureDescriptor descriptor = ref pool.GetForBinding(textureId, out Texture texture); + + specStateMatches &= specState.MatchesImage(stage, index, descriptor); + + ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target); + if (hostTexture != null && texture.Target == Target.TextureBuffer) { // Ensure that the buffer texture is using the correct buffer as storage. @@ -494,14 +647,14 @@ namespace Ryujinx.Graphics.Gpu.Image texture?.SignalModified(); } - if (_imageState[stageIndex][index].Texture != hostTexture || _rebind) + if (state.Texture != hostTexture) { if (UpdateScale(texture, bindingInfo, baseScaleIndex + index, stage)) { hostTexture = texture?.GetTargetTexture(bindingInfo.Target); } - _imageState[stageIndex][index].Texture = hostTexture; + state.Texture = hostTexture; Format format = bindingInfo.Format; @@ -512,8 +665,13 @@ namespace Ryujinx.Graphics.Gpu.Image _context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTexture, format); } + + state.CachedTexture = texture; + state.InvalidatedSequence = texture?.InvalidatedSequence ?? 0; } } + + return specStateMatches; } /// <summary> @@ -537,7 +695,7 @@ namespace Ryujinx.Graphics.Gpu.Image (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(cbufSlot, bufferIndex); int packedId = ReadPackedId(stageIndex, handle, textureBufferIndex, samplerBufferIndex); - int textureId = UnpackTextureId(packedId); + int textureId = TextureHandle.UnpackTextureId(packedId); ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa); @@ -555,6 +713,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// <param name="textureBufferIndex">Index of the constant buffer holding the texture handles</param> /// <param name="samplerBufferIndex">Index of the constant buffer holding the sampler handles</param> /// <returns>The packed texture and sampler ID (the real texture handle)</returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] private int ReadPackedId(int stageIndex, int wordOffset, int textureBufferIndex, int samplerBufferIndex) { (int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = TextureHandle.UnpackOffsets(wordOffset); @@ -591,31 +750,12 @@ namespace Ryujinx.Graphics.Gpu.Image } /// <summary> - /// Unpacks the texture ID from the real texture handle. - /// </summary> - /// <param name="packedId">The real texture handle</param> - /// <returns>The texture ID</returns> - private static int UnpackTextureId(int packedId) - { - return (packedId >> 0) & 0xfffff; - } - - /// <summary> - /// Unpacks the sampler ID from the real texture handle. - /// </summary> - /// <param name="packedId">The real texture handle</param> - /// <returns>The sampler ID</returns> - private static int UnpackSamplerId(int packedId) - { - return (packedId >> 20) & 0xfff; - } - - /// <summary> /// Force all bound textures and images to be rebound the next time CommitBindings is called. /// </summary> public void Rebind() { - _rebind = true; + Array.Clear(_textureState); + Array.Clear(_imageState); } /// <summary> |