diff --git a/src/Ryujinx.Graphics.Gpu/Constants.cs b/src/Ryujinx.Graphics.Gpu/Constants.cs index 6da27e8b7..5860cdaa5 100644 --- a/src/Ryujinx.Graphics.Gpu/Constants.cs +++ b/src/Ryujinx.Graphics.Gpu/Constants.cs @@ -85,6 +85,11 @@ namespace Ryujinx.Graphics.Gpu /// public const int DriverReservedUniformBuffer = 0; + /// + /// Number of the uniform buffer reserved by the driver to store texture binding handles. + /// + public const int DriverReserveTextureBindingsBuffer = 2; + /// /// Maximum size that an storage buffer is assumed to have when the correct size is unknown. /// diff --git a/src/Ryujinx.Graphics.Gpu/Image/BitMap.cs b/src/Ryujinx.Graphics.Gpu/Image/BitMap.cs index c83a8fe9b..d901610a8 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/BitMap.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/BitMap.cs @@ -87,14 +87,22 @@ namespace Ryujinx.Graphics.Gpu.Image /// Sets a bit to 0. /// /// Index of the bit - public void Clear(int bit) + /// True if the bit was set, false otherwise + public bool Clear(int bit) { int wordIndex = bit / IntSize; int wordBit = bit & IntMask; ulong wordMask = 1UL << wordBit; + if ((_masks[wordIndex] & wordMask) == 0) + { + return false; + } + _masks[wordIndex] &= ~wordMask; + + return true; } /// diff --git a/src/Ryujinx.Graphics.Gpu/Image/Pool.cs b/src/Ryujinx.Graphics.Gpu/Image/Pool.cs index d48a901e6..ca2e6b7b0 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/Pool.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/Pool.cs @@ -214,6 +214,8 @@ namespace Ryujinx.Graphics.Gpu.Image /// End address of the region of the pool that has been modified, exclusive protected void UpdateModifiedEntries(ulong address, ulong endAddress) { + // TODO: Remove this method (it's unused now). + int startId = (int)((address - Address) / DescriptorSize); int endId = (int)((endAddress - Address + (DescriptorSize - 1)) / DescriptorSize) - 1; @@ -228,6 +230,18 @@ namespace Ryujinx.Graphics.Gpu.Image _maximumAccessedId = Math.Max(_maximumAccessedId, endId); } + /// + /// Updates a entry that has been modified. + /// + /// Pool entry index + protected void UpdateModifiedEntry(int id) + { + ModifiedEntries.Set(id); + + _minimumAccessedId = Math.Min(_minimumAccessedId, id); + _maximumAccessedId = Math.Max(_maximumAccessedId, id); + } + /// /// Forces all entries as modified, to be updated if any shader uses bindless textures. /// diff --git a/src/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs b/src/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs index 42351528d..fbb164e27 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs @@ -146,6 +146,29 @@ namespace Ryujinx.Graphics.Gpu.Image } } + /// + /// Gets the sampler with the specified ID, and return true or false depending on the sampler entry being modified or not since the last call. + /// + /// ID of the texture + /// Sampler with the specified ID + /// True if the sampler entry was modified since the last call, false otherwise + public bool TryGetBindlessSampler(int id, out Sampler sampler) + { + if ((uint)id < Items.Length) + { + if (ModifiedEntries.Clear(id)) + { + sampler = Items[id] ?? GetValidated(id); + + return true; + } + } + + sampler = null; + + return false; + } + /// /// Gets the sampler at the given from the cache, /// or creates a new one if not found. @@ -174,8 +197,6 @@ namespace Ryujinx.Graphics.Gpu.Image { ulong endAddress = address + size; - UpdateModifiedEntries(address, endAddress); - for (; address < endAddress; address += DescriptorSize) { int id = (int)((address - Address) / DescriptorSize); @@ -192,10 +213,16 @@ namespace Ryujinx.Graphics.Gpu.Image continue; } + UpdateModifiedEntry(id); + sampler.Dispose(); Items[id] = null; } + else + { + UpdateModifiedEntry(id); + } } } diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs index 26ef5a53e..d7a01212c 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs @@ -5,7 +5,7 @@ using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Gpu.Shader; using Ryujinx.Graphics.Shader; using System; -using System.Collections.Generic; +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -61,6 +61,7 @@ namespace Ryujinx.Graphics.Gpu.Image private int _samplerPoolSequence; private BindlessTextureFlags[] _bindlessTextureFlags; + private uint[] _bindlessIndexedBuffersMask; private int _textureBufferIndex; @@ -97,6 +98,7 @@ namespace Ryujinx.Graphics.Gpu.Image _imageState = new TextureState[InitialImageStateSize]; _bindlessTextureFlags = new BindlessTextureFlags[stages]; + _bindlessIndexedBuffersMask = new uint[stages]; for (int stage = 0; stage < stages; stage++) { @@ -115,6 +117,7 @@ namespace Ryujinx.Graphics.Gpu.Image _imageBindings = bindings.ImageBindings; _bindlessTextureFlags = bindings.BindlessTextureFlags; + _bindlessIndexedBuffersMask = bindings.BindlessIndexedBuffersMask; SetMaxBindings(bindings.MaxTextureBinding, bindings.MaxImageBinding); } @@ -366,14 +369,14 @@ namespace Ryujinx.Graphics.Gpu.Image specStateMatches &= CommitTextureBindings(texturePool, samplerPool, ShaderStage.Compute, 0, poolModified, specState); specStateMatches &= CommitImageBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState); - if (_bindlessTextureFlags[0].HasFlag(BindlessTextureFlags.BindlessNvn)) - { - CommitBindlessResources(texturePool, ShaderStage.Compute, 0); - } - else if (_bindlessTextureFlags[0].HasFlag(BindlessTextureFlags.BindlessFull)) + if (_bindlessTextureFlags[0].HasFlag(BindlessTextureFlags.BindlessFull)) { texturePool.LoadAll(_context.Renderer, _samplerPool); } + else if ((_bindlessTextureFlags[0] & BindlessTextureFlags.BindlessNvnAny) != 0) + { + CommitBindlessResources(texturePool, ShaderStage.Compute, 0); + } } else { @@ -384,14 +387,14 @@ namespace Ryujinx.Graphics.Gpu.Image specStateMatches &= CommitTextureBindings(texturePool, samplerPool, stage, stageIndex, poolModified, specState); specStateMatches &= CommitImageBindings(texturePool, stage, stageIndex, poolModified, specState); - if (_bindlessTextureFlags[stageIndex].HasFlag(BindlessTextureFlags.BindlessNvn)) - { - CommitBindlessResources(texturePool, stage, stageIndex); - } - else if (_bindlessTextureFlags[stageIndex].HasFlag(BindlessTextureFlags.BindlessFull)) + if (_bindlessTextureFlags[stageIndex].HasFlag(BindlessTextureFlags.BindlessFull)) { texturePool.LoadAll(_context.Renderer, _samplerPool); } + else if ((_bindlessTextureFlags[stageIndex] & BindlessTextureFlags.BindlessNvnAny) != 0) + { + CommitBindlessResources(texturePool, stage, stageIndex); + } } } @@ -783,11 +786,89 @@ namespace Ryujinx.Graphics.Gpu.Image return; } - for (int index = 0; index < 32; index++) - { - int wordOffset = 8 + index * 2; + BindlessTextureFlags flags = _bindlessTextureFlags[stageIndex]; + uint buffersMask = _bindlessIndexedBuffersMask[stageIndex]; - int packedId = ReadConstantBuffer(stageIndex, _textureBufferIndex, wordOffset); + while (buffersMask != 0) + { + int bufferIndex = BitOperations.TrailingZeroCount(buffersMask); + + buffersMask &= ~(1u << bufferIndex); + + if (bufferIndex == Constants.DriverReserveTextureBindingsBuffer) + { + if (flags.HasFlag(BindlessTextureFlags.BindlessNvnCombined)) + { + CommitBindlessResourcesNvnCombined(pool, stageIndex); + } + + if (flags.HasFlag(BindlessTextureFlags.BindlessNvnSeparateTexture)) + { + CommitBindlessResourcesNvnSeparateTexture(pool, stageIndex); + } + + if (flags.HasFlag(BindlessTextureFlags.BindlessNvnSeparateSampler)) + { + CommitBindlessResourcesNvnSeparateSampler(pool, stageIndex); + } + + continue; + } + + ulong size = _isCompute + ? _channel.BufferManager.GetComputeUniformBufferSize(bufferIndex) + : _channel.BufferManager.GetGraphicsUniformBufferSize(stageIndex, bufferIndex); + + ReadOnlySpan cbData = GetConstantBufferRange(stageIndex, bufferIndex, 0, (int)(size / sizeof(int))); + + for (int index = 0; index < cbData.Length; index += 2) + { + int packedId = cbData[index]; + int highWord = cbData[index + 1]; + + if (highWord != 1) + { + continue; + } + + int textureId = TextureHandle.UnpackTextureId(packedId); + int samplerId; + + if (_samplerIndex == SamplerIndex.ViaHeaderIndex) + { + samplerId = textureId; + } + else + { + samplerId = TextureHandle.UnpackSamplerId(packedId); + } + + pool.UpdateBindlessCombined(_context.Renderer, samplerPool, textureId, samplerId); + } + } + } + + /// + /// Ensures that the texture bindings are visible to the host GPU. + /// Note: this actually performs the binding using the host graphics API. + /// + /// The current texture pool + /// The stage number of the specified shader stage + private void CommitBindlessResourcesNvnCombined(TexturePool pool, int stageIndex) + { + var samplerPool = _samplerPool; + + ReadOnlySpan cbData = GetConstantBufferRange(stageIndex, Constants.DriverReserveTextureBindingsBuffer, 8, 32); + + for (int index = 0; index < cbData.Length; index += 2) + { + int packedId = cbData[index]; + int highWord = cbData[index + 1]; + + if (highWord != 1) + { + continue; + } int textureId = TextureHandle.UnpackTextureId(packedId); int samplerId; @@ -801,38 +882,76 @@ namespace Ryujinx.Graphics.Gpu.Image samplerId = TextureHandle.UnpackSamplerId(packedId); } - Texture texture = pool.Get(textureId); + pool.UpdateBindlessCombined(_context.Renderer, samplerPool, textureId, samplerId); + } + } - if (texture == null) + /// + /// Ensures that the texture bindings are visible to the host GPU. + /// Note: this actually performs the binding using the host graphics API. + /// + /// The current texture pool + /// The stage number of the specified shader stage + private void CommitBindlessResourcesNvnSeparateTexture(TexturePool pool, int stageIndex) + { + ReadOnlySpan cbData = GetConstantBufferRange(stageIndex, Constants.DriverReserveTextureBindingsBuffer, 90, 128); + + for (int index = 0; index < cbData.Length; index += 2) + { + int packedId = cbData[index]; + int highWord = cbData[index + 1]; + + if (highWord != 1) { continue; } - if (texture.Target == Target.TextureBuffer) + int textureId = TextureHandle.UnpackTextureId(packedId); + + pool.UpdateBindlessCombined(_context.Renderer, null, textureId, 0); + } + } + + /// + /// Ensures that the texture bindings are visible to the host GPU. + /// Note: this actually performs the binding using the host graphics API. + /// + /// The current texture pool + /// The stage number of the specified shader stage + private void CommitBindlessResourcesNvnSeparateSampler(TexturePool pool, int stageIndex) + { + var samplerPool = _samplerPool; + if (samplerPool == null) + { + return; + } + + ReadOnlySpan cbData = GetConstantBufferRange(stageIndex, Constants.DriverReserveTextureBindingsBuffer, 346, 32); + + for (int index = 0; index < cbData.Length; index += 2) + { + int packedId = cbData[index]; + int highWord = cbData[index + 1]; + + if (highWord != 1) { - // Ensure that the buffer texture is using the correct buffer as storage. - // Buffers are frequently re-created to accomodate larger data, so we need to re-bind - // to ensure we're not using a old buffer that was already deleted. - TextureBindingInfo bindingInfo = new(texture.Target, texture.Format, 0, 0, 0, TextureUsageFlags.None); - ulong address = texture.Range.GetSubRange(0).Address; - ulong size = texture.Size; - _channel.BufferManager.SetBufferTextureStorage(texture.HostTexture, address, size, bindingInfo, texture.Format, false, textureId); + continue; + } + + int samplerId; + + if (_samplerIndex == SamplerIndex.ViaHeaderIndex) + { + samplerId = TextureHandle.UnpackTextureId(packedId); } else { - Sampler sampler = samplerPool?.Get(samplerId); + samplerId = TextureHandle.UnpackSamplerId(packedId); + } - if (sampler == null) - { - continue; - } - - _context.Renderer.Pipeline.RegisterBindlessTextureAndSampler( - textureId, - texture.HostTexture, - texture.ScaleFactor, - samplerId, - sampler.GetHostSampler(texture)); + if (samplerPool.TryGetBindlessSampler(samplerId, out Sampler sampler)) + { + _context.Renderer.Pipeline.RegisterBindlessSampler(samplerId, sampler.GetHostSampler(null)); } } } @@ -842,15 +961,18 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// Index of the shader stage where the constant buffer belongs /// Index of the constant buffer to read from - /// Index of the element on the constant buffer + /// Index of the first element on the constant buffer + /// Number of elements to access /// The value at the specified buffer and offset - private unsafe T ReadConstantBuffer(int stageIndex, int bufferIndex, int elementIndex) where T : unmanaged + private unsafe ReadOnlySpan GetConstantBufferRange(int stageIndex, int bufferIndex, int startIndex, int count) where T : unmanaged { ulong baseAddress = _isCompute ? _channel.BufferManager.GetComputeUniformBufferAddress(bufferIndex) : _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, bufferIndex); - return _channel.MemoryManager.Physical.Read(baseAddress + (ulong)elementIndex * (ulong)sizeof(T)); + int typeSize = sizeof(T); + + return MemoryMarshal.Cast(_channel.MemoryManager.Physical.GetSpan(baseAddress + (ulong)(startIndex * typeSize), count * typeSize)); } /// diff --git a/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs index ee652355a..b0121b61f 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs @@ -236,27 +236,83 @@ namespace Ryujinx.Graphics.Gpu.Image while ((id = ModifiedEntries.GetNextAndClear()) >= 0) { - Texture texture = Items[id] ?? GetValidated(id); + UpdateBindlessInternal(renderer, id); + } + } - if (texture != null) + /// + /// Updates the bindless texture with the given pool ID, if the pool entry has been modified since the last call. + /// + /// Renderer of the current GPU context + /// Optional sampler pool. If null, the sampler ID is ignored + /// ID of the texture + /// ID of the sampler + public void UpdateBindlessCombined(IRenderer renderer, SamplerPool samplerPool, int textureId, int samplerId) + { + if ((uint)textureId >= Items.Length) + { + return; + } + + Items[textureId]?.SynchronizeMemory(); + + bool textureModified = ModifiedEntries.Clear(textureId); + + if (samplerPool != null) + { + bool samplerModified = samplerPool.TryGetBindlessSampler(samplerId, out Sampler sampler); + + if (sampler == null) { - if (texture.Target == Target.TextureBuffer) + if (textureModified) { - _channel.BufferManager.SetBufferTextureStorage( - texture.HostTexture, - texture.Range.GetSubRange(0).Address, - texture.Size, - default, - 0, - false, - id); + UpdateBindlessInternal(renderer, textureId); + } + } + else if (textureModified || samplerModified) + { + Texture texture = Items[textureId] ?? GetValidated(textureId); + + if (texture != null) + { + if (texture.Target != Target.TextureBuffer) + { + renderer.Pipeline.RegisterBindlessTextureAndSampler( + textureId, + texture.HostTexture, + texture.ScaleFactor, + samplerId, + sampler.GetHostSampler(null)); + } } else { - renderer.Pipeline.RegisterBindlessTexture(id, texture.HostTexture, texture.ScaleFactor); + renderer.Pipeline.RegisterBindlessSampler(samplerId, sampler.GetHostSampler(null)); } } } + else if (textureModified) + { + UpdateBindlessInternal(renderer, textureId); + } + } + + /// + /// Updates the bindless texture with the given pool ID. + /// + /// Renderer of the current GPU context + /// ID of the texture + private void UpdateBindlessInternal(IRenderer renderer, int id) + { + Texture texture = Items[id] ?? GetValidated(id); + + if (texture != null) + { + if (texture.Target != Target.TextureBuffer) + { + renderer.Pipeline.RegisterBindlessTexture(id, texture.HostTexture, texture.ScaleFactor); + } + } } /// @@ -431,8 +487,6 @@ namespace Ryujinx.Graphics.Gpu.Image ulong endAddress = address + size; - UpdateModifiedEntries(address, endAddress); - for (; address < endAddress; address += DescriptorSize) { int id = (int)((address - Address) / DescriptorSize); @@ -451,6 +505,8 @@ namespace Ryujinx.Graphics.Gpu.Image continue; } + UpdateModifiedEntry(id); + if (texture.HasOneReference()) { _channel.MemoryManager.Physical.TextureCache.AddShortCache(texture, ref cachedDescriptor); @@ -461,6 +517,10 @@ namespace Ryujinx.Graphics.Gpu.Image texture.DecrementReferenceCount(this, id); } } + else + { + UpdateModifiedEntry(id); + } } } diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs index 78519cabb..ea5ac5a01 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs @@ -429,6 +429,27 @@ namespace Ryujinx.Graphics.Gpu.Memory return _gpUniformBuffers[stage].Buffers[index].Address; } + /// + /// Gets the size of the compute uniform buffer currently bound at the given index. + /// + /// Index of the uniform buffer binding + /// The uniform buffer size, or an undefined value if the buffer is not currently bound + public ulong GetComputeUniformBufferSize(int index) + { + return _cpUniformBuffers.Buffers[index].Size; + } + + /// + /// Gets the size of the graphics uniform buffer currently bound at the given index. + /// + /// Index of the shader stage + /// Index of the uniform buffer binding + /// The uniform buffer size, or an undefined value if the buffer is not currently bound + public ulong GetGraphicsUniformBufferSize(int stage, int index) + { + return _gpUniformBuffers[stage].Buffers[index].Size; + } + /// /// Gets the bounds of the uniform buffer currently bound at the given index. /// diff --git a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs index e18ff1f17..c1a595620 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs @@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Gpu.Shader public BufferDescriptor[][] StorageBufferBindings { get; } public BindlessTextureFlags[] BindlessTextureFlags { get; } + public uint[] BindlessIndexedBuffersMask { get; } public int MaxTextureBinding { get; } public int MaxImageBinding { get; } @@ -37,6 +38,7 @@ namespace Ryujinx.Graphics.Gpu.Shader StorageBufferBindings = new BufferDescriptor[stageCount][]; BindlessTextureFlags = new BindlessTextureFlags[stageCount]; + BindlessIndexedBuffersMask = new uint[stageCount]; int maxTextureBinding = -1; int maxImageBinding = -1; @@ -100,6 +102,7 @@ namespace Ryujinx.Graphics.Gpu.Shader StorageBufferBindings[i] = stage.Info.SBuffers.ToArray(); BindlessTextureFlags[i] = stage.Info.BindlessTextureFlags; + BindlessIndexedBuffersMask[i] = stage.Info.BindlessIndexedBuffersMask; } MaxTextureBinding = maxTextureBinding; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs index 4198cb693..29a227819 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs @@ -189,6 +189,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache /// Flags indicating if and how bindless texture accesses were translated for the shader stage. /// public BindlessTextureFlags BindlessTextureFlags; + + /// + /// Bit mask indicating which constant buffers are accessed on the shader using indexing to load texture handles. + /// + public uint BindlessIndexedBuffersMask; } private readonly DiskCacheGuestStorage _guestStorage; @@ -805,6 +810,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache images, dataInfo.Stage, dataInfo.BindlessTextureFlags, + dataInfo.BindlessIndexedBuffersMask, dataInfo.GeometryVerticesPerPrimitive, dataInfo.GeometryMaxOutputVertices, dataInfo.ThreadsPerInputPrimitive, @@ -836,6 +842,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache ImagesCount = (ushort)info.Images.Count, Stage = info.Stage, BindlessTextureFlags = info.BindlessTextureFlags, + BindlessIndexedBuffersMask = info.BindlessIndexedBuffersMask, GeometryVerticesPerPrimitive = (byte)info.GeometryVerticesPerPrimitive, GeometryMaxOutputVertices = (ushort)info.GeometryMaxOutputVertices, ThreadsPerInputPrimitive = (ushort)info.ThreadsPerInputPrimitive, diff --git a/src/Ryujinx.Graphics.Shader/BindlessTextureFlags.cs b/src/Ryujinx.Graphics.Shader/BindlessTextureFlags.cs index 0892ac1df..fafaa9dcf 100644 --- a/src/Ryujinx.Graphics.Shader/BindlessTextureFlags.cs +++ b/src/Ryujinx.Graphics.Shader/BindlessTextureFlags.cs @@ -5,7 +5,10 @@ namespace Ryujinx.Graphics.Shader None = 0, BindlessConverted = 1 << 0, - BindlessNvn = 1 << 1, - BindlessFull = 1 << 2, + BindlessNvnCombined = 1 << 1, + BindlessNvnSeparateTexture = 1 << 2, + BindlessNvnSeparateSampler = 1 << 3, + BindlessFull = 1 << 4, + BindlessNvnAny = BindlessNvnCombined | BindlessNvnSeparateTexture | BindlessNvnSeparateSampler, } } diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs index 1c3a9ddf6..511eeba41 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs @@ -14,7 +14,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl public static void Declare(CodeGenContext context, StructuredProgramInfo info) { context.AppendLine(context.TargetApi == TargetApi.Vulkan ? "#version 460 core" : "#version 450 core"); - context.AppendLine("#extension GL_ARB_bindless_texture : enable"); context.AppendLine("#extension GL_ARB_gpu_shader_int64 : enable"); if (context.HostCapabilities.SupportsShaderBallot) @@ -36,6 +35,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl { context.AppendLine("#extension GL_EXT_nonuniform_qualifier : enable"); } + else + { + context.AppendLine("#extension GL_ARB_bindless_texture : enable"); + } if (context.Definitions.Stage == ShaderStage.Compute) { diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/GetBindlessHandleVk.glsl b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/GetBindlessHandleVk.glsl index 05cb2ec3f..ee0bde6fa 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/GetBindlessHandleVk.glsl +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/GetBindlessHandleVk.glsl @@ -1,16 +1,16 @@ uint Helper_GetBindlessTextureIndex(int nvHandle) { int id = nvHandle & 0xfffff; - return bindless_table[id >> 8].x | uint(id & 0xff); + return bindless_table.table[id >> 8].x | uint(id & 0xff); } uint Helper_GetBindlessSamplerIndex(int nvHandle) { int id = (nvHandle >> 20) & 0xfff; - return bindless_table[id >> 8].y | uint(id & 0xff); + return bindless_table.table[id >> 8].y | uint(id & 0xff); } float Helper_GetBindlessScale(int nvHandle) { - return bindless_scales[Helper_GetBindlessTextureIndex(nvHandle)]; + return bindless_scales.scales[Helper_GetBindlessTextureIndex(nvHandle)]; } \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Shader/Constants.cs b/src/Ryujinx.Graphics.Shader/Constants.cs index 34cf8295f..fc4da7ba4 100644 --- a/src/Ryujinx.Graphics.Shader/Constants.cs +++ b/src/Ryujinx.Graphics.Shader/Constants.cs @@ -11,6 +11,12 @@ namespace Ryujinx.Graphics.Shader public const int NvnBaseInstanceByteOffset = 0x644; public const int NvnDrawIndexByteOffset = 0x648; + public const int NvnTextureCbSlot = 2; + public const int NvnSeparateTextureBindingsStartByteOffset = 0x168; + public const int NvnSeparateTextureBindingsEndByteOffset = 0x568; + public const int NvnSeparateSamplerBindingsStartByteOffset = 0x568; + public const int NvnSeparateSamplerBindingsEndByteOffset = 0x668; + public const int VkConstantBufferSetIndex = 0; public const int VkStorageBufferSetIndex = 1; public const int VkTextureSetIndex = 2; diff --git a/src/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs b/src/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs index 38465032d..a2c8394fc 100644 --- a/src/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs +++ b/src/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs @@ -12,6 +12,7 @@ namespace Ryujinx.Graphics.Shader public ShaderStage Stage { get; } public BindlessTextureFlags BindlessTextureFlags { get; } + public uint BindlessIndexedBuffersMask { get; } public int GeometryVerticesPerPrimitive { get; } public int GeometryMaxOutputVertices { get; } public int ThreadsPerInputPrimitive { get; } @@ -29,6 +30,7 @@ namespace Ryujinx.Graphics.Shader TextureDescriptor[] images, ShaderStage stage, BindlessTextureFlags bindlessTextureFlags, + uint bindlessIndexedBuffersMask, int geometryVerticesPerPrimitive, int geometryMaxOutputVertices, int threadsPerInputPrimitive, @@ -46,6 +48,7 @@ namespace Ryujinx.Graphics.Shader Stage = stage; BindlessTextureFlags = bindlessTextureFlags; + BindlessIndexedBuffersMask = bindlessIndexedBuffersMask; GeometryVerticesPerPrimitive = geometryVerticesPerPrimitive; GeometryMaxOutputVertices = geometryMaxOutputVertices; ThreadsPerInputPrimitive = threadsPerInputPrimitive; diff --git a/src/Ryujinx.Graphics.Shader/Translation/TransformContext.cs b/src/Ryujinx.Graphics.Shader/Translation/TransformContext.cs index 794b975b0..080249d2c 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/TransformContext.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/TransformContext.cs @@ -14,6 +14,7 @@ namespace Ryujinx.Graphics.Shader.Translation public readonly ShaderStage Stage; public readonly ref FeatureFlags UsedFeatures; public readonly ref BindlessTextureFlags BindlessTextureFlags; + public readonly ref uint BindlessIndexedBuffersMask; public readonly ref bool BindlessTexturesAllowed; public TransformContext( @@ -27,6 +28,7 @@ namespace Ryujinx.Graphics.Shader.Translation ShaderStage stage, ref FeatureFlags usedFeatures, ref BindlessTextureFlags bindlessTextureFlags, + ref uint bindlessIndexedBuffersMask, ref bool bindlessTexturesAllowed) { Hfm = hfm; @@ -39,6 +41,7 @@ namespace Ryujinx.Graphics.Shader.Translation Stage = stage; UsedFeatures = ref usedFeatures; BindlessTextureFlags = ref bindlessTextureFlags; + BindlessIndexedBuffersMask = ref bindlessIndexedBuffersMask; BindlessTexturesAllowed = ref bindlessTexturesAllowed; } } diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs index 4c7fbab0a..706c3347b 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs @@ -22,6 +22,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms context.ResourceManager, context.TargetApi, ref context.BindlessTextureFlags, + ref context.BindlessIndexedBuffersMask, context.BindlessTexturesAllowed, context.GpuAccessor.QueryTextureBufferIndex()); @@ -784,6 +785,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms ResourceManager resourceManager, TargetApi targetApi, ref BindlessTextureFlags bindlessTextureFlags, + ref uint bindlessIndexedBuffersMask, bool bindlessTexturesAllowed, int textureBufferIndex) { @@ -797,9 +799,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms { resourceManager.EnsureBindlessBinding(targetApi, texOp.Type, texOp.Inst.IsImage()); - if (IsIndexedAccess(resourceManager, texOp, textureBufferIndex)) + if (IsIndexedAccess(resourceManager, texOp, ref bindlessTextureFlags, ref bindlessIndexedBuffersMask, textureBufferIndex)) { - bindlessTextureFlags |= BindlessTextureFlags.BindlessNvn; return node; } @@ -865,7 +866,12 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms return node; } - private static bool IsIndexedAccess(ResourceManager resourceManager, TextureOperation texOp, int textureBufferIndex) + private static bool IsIndexedAccess( + ResourceManager resourceManager, + TextureOperation texOp, + ref BindlessTextureFlags bindlessTextureFlags, + ref uint bindlessIndexedBuffersMask, + int textureBufferIndex) { // Try to detect a indexed access. // The access is considered indexed if the handle is loaded with a LDC instruction @@ -875,6 +881,70 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms return false; } + if (handleAsgOp.Inst == Instruction.BitwiseOr) + { + if (IsCbLoadOrCb(resourceManager, handleAsgOp.GetSource(0), ref bindlessTextureFlags, out int ldc0CbSlot, textureBufferIndex) && + IsCbLoadOrCb(resourceManager, handleAsgOp.GetSource(1), ref bindlessTextureFlags, out int ldc1CbSlot, textureBufferIndex)) + { + bindlessIndexedBuffersMask |= (1u << ldc0CbSlot) | (1u << ldc1CbSlot); + + return true; + } + } + else if (IsCbLoad(resourceManager, handleAsgOp, out int cbSlot)) + { + bindlessTextureFlags |= BindlessTextureFlags.BindlessNvnCombined; + bindlessIndexedBuffersMask |= 1u << cbSlot; + + return true; + } + + return false; + } + + private static bool IsCbLoadOrCb( + ResourceManager resourceManager, + Operand operand, + ref BindlessTextureFlags bindlessTextureFlags, + out int cbSlot, + int textureBufferIndex) + { + cbSlot = 0; + + if (operand.Type == OperandType.ConstantBuffer) + { + cbSlot = operand.GetCbufSlot(); + + if (cbSlot == textureBufferIndex && textureBufferIndex == Constants.NvnTextureCbSlot) + { + int cbOffset = operand.GetCbufOffset(); + + if (cbOffset >= Constants.NvnSeparateTextureBindingsStartByteOffset / 4 && + cbOffset < Constants.NvnSeparateTextureBindingsEndByteOffset / 4) + { + bindlessTextureFlags |= BindlessTextureFlags.BindlessNvnSeparateTexture; + + return true; + } + else if (cbOffset >= Constants.NvnSeparateSamplerBindingsStartByteOffset / 4 && + cbOffset < Constants.NvnSeparateSamplerBindingsEndByteOffset / 4) + { + bindlessTextureFlags |= BindlessTextureFlags.BindlessNvnSeparateSampler; + + return true; + } + } + + return false; + } + + return operand.AsgOp is Operation operation && IsCbLoad(resourceManager, operation, out cbSlot); + } + + private static bool IsCbLoad(ResourceManager resourceManager, Operation handleAsgOp, out int cbSlot) + { + cbSlot = 0; + if (handleAsgOp.Inst != Instruction.Load || handleAsgOp.StorageKind != StorageKind.ConstantBuffer) { return false; @@ -883,8 +953,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms Operand ldcSrc0 = handleAsgOp.GetSource(0); return ldcSrc0.Type == OperandType.Constant && - resourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int cbSlot) && - cbSlot == textureBufferIndex; + resourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out cbSlot); } } } diff --git a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs index 30f929ca4..d198f8f9c 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs @@ -265,6 +265,7 @@ namespace Ryujinx.Graphics.Shader.Translation HelperFunctionManager hfm = new(funcs, Definitions.Stage); BindlessTextureFlags bindlessTextureFlags = BindlessTextureFlags.None; + uint bindlessIndexedBuffersMask = 0; bool bindlessTexturesAllowed = true; for (int i = 0; i < functions.Length; i++) @@ -302,6 +303,7 @@ namespace Ryujinx.Graphics.Shader.Translation Definitions.Stage, ref usedFeatures, ref bindlessTextureFlags, + ref bindlessIndexedBuffersMask, ref bindlessTexturesAllowed); Optimizer.RunPass(context); @@ -319,6 +321,7 @@ namespace Ryujinx.Graphics.Shader.Translation resourceManager, usedFeatures, bindlessTextureFlags, + bindlessIndexedBuffersMask, clipDistancesWritten); } @@ -330,6 +333,7 @@ namespace Ryujinx.Graphics.Shader.Translation ResourceManager resourceManager, FeatureFlags usedFeatures, BindlessTextureFlags bindlessTextureFlags, + uint bindlessIndexedBuffersMask, byte clipDistancesWritten) { var sInfo = StructuredProgram.MakeStructuredProgram( @@ -354,6 +358,7 @@ namespace Ryujinx.Graphics.Shader.Translation resourceManager.GetImageDescriptors(), originalDefinitions.Stage, bindlessTextureFlags, + bindlessIndexedBuffersMask, geometryVerticesPerPrimitive, originalDefinitions.MaxOutputVertices, originalDefinitions.ThreadsPerInputPrimitive, @@ -586,6 +591,7 @@ namespace Ryujinx.Graphics.Shader.Translation resourceManager, FeatureFlags.None, BindlessTextureFlags.None, + 0, 0); } @@ -683,6 +689,7 @@ namespace Ryujinx.Graphics.Shader.Translation resourceManager, FeatureFlags.RtLayer, BindlessTextureFlags.None, + 0, 0); } } diff --git a/src/Ryujinx.Graphics.Vulkan/BindlessManager.cs b/src/Ryujinx.Graphics.Vulkan/BindlessManager.cs index 46e599433..52623ee06 100644 --- a/src/Ryujinx.Graphics.Vulkan/BindlessManager.cs +++ b/src/Ryujinx.Graphics.Vulkan/BindlessManager.cs @@ -3,6 +3,7 @@ using Ryujinx.Graphics.GAL; using Silk.NET.Vulkan; using System; using System.Collections.Generic; +using System.ComponentModel.Design; using System.Numerics; using System.Runtime.InteropServices; @@ -270,25 +271,26 @@ namespace Ryujinx.Graphics.Vulkan bbiDsc.UpdateBufferImage(0, 0, i, bufferView, DescriptorType.StorageTexelBuffer); } } + else + { + btDsc.UpdateImage(0, 2, i, default, DescriptorType.SampledImage); + } } for (int i = 0; i < _samplerRefs.Length; i++) { var sampler = _samplerRefs[i]; - if (sampler != null) + var sd = new DescriptorImageInfo() { - var sd = new DescriptorImageInfo() - { - Sampler = sampler.Get(cbs).Value - }; + Sampler = sampler?.Get(cbs).Value ?? default + }; - if (sd.Sampler.Handle == 0) - { - sd.Sampler = dummySampler.GetSampler().Get(cbs).Value; - } - - bsDsc.UpdateImage(0, 0, i, sd, DescriptorType.Sampler); + if (sd.Sampler.Handle == 0) + { + sd.Sampler = dummySampler.GetSampler().Get(cbs).Value; } + + bsDsc.UpdateImage(0, 0, i, sd, DescriptorType.Sampler); } _pipelineLayout = plce.PipelineLayout; diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs index f21d40bfb..0933a52aa 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs @@ -108,7 +108,7 @@ namespace Ryujinx.Graphics.Vulkan // All bindless resources have the update after bind flag set, // this is required for Intel, otherwise it just crashes when binding the descriptor sets // for bindless resources (maybe because it is above the limit?) - bool updateAfterBind = setIndex >= PipelineBase.BindlessBufferTextureSetIndex; + bool updateAfterBind = setIndex >= PipelineBase.BindlessTexturesSetIndex; var dsc = _gd.DescriptorSetManager.AllocateDescriptorSet( _gd.Api,