From 29e9e90fe8310b546c136e123d47fb1c36a0826f Mon Sep 17 00:00:00 2001 From: Gabriel A Date: Wed, 19 Jul 2023 22:01:53 -0300 Subject: [PATCH] Initial bindless texture support --- src/Ryujinx.Graphics.GAL/IPipeline.cs | 4 + .../Multithreading/CommandHelper.cs | 3 + .../Multithreading/CommandType.cs | 3 + .../RegisterBindlessSamplerCommand.cs | 23 ++ ...egisterBindlessTextureAndSamplerCommand.cs | 34 ++ .../RegisterBindlessTextureCommand.cs | 25 ++ .../Multithreading/ThreadedPipeline.cs | 18 + src/Ryujinx.Graphics.GAL/ShaderInfo.cs | 7 +- src/Ryujinx.Graphics.Gpu/Constants.cs | 25 ++ .../Engine/Compute/ComputeClass.cs | 1 - .../Engine/ShaderTexture.cs | 2 +- src/Ryujinx.Graphics.Gpu/Image/BitMap.cs | 197 ++++++++++ src/Ryujinx.Graphics.Gpu/Image/Pool.cs | 45 +++ .../Image/SamplerDescriptor.cs | 18 + src/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs | 50 +++ src/Ryujinx.Graphics.Gpu/Image/Texture.cs | 13 + .../Image/TextureBindingsManager.cs | 161 +++++++- src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs | 100 +++++ .../Image/TextureValidation.cs | 107 ++++++ .../Memory/BufferManager.cs | 30 +- .../Memory/BufferTextureBinding.cs | 35 ++ .../Shader/CachedShaderBindings.cs | 6 + .../Shader/DiskCache/DiskCacheGpuAccessor.cs | 8 + .../Shader/DiskCache/DiskCacheHostStorage.cs | 9 +- .../Shader/GpuAccessor.cs | 8 + .../Shader/GpuAccessorBase.cs | 5 +- .../Shader/ShaderInfoBuilder.cs | 66 +++- .../Shader/ShaderSpecializationState.cs | 35 ++ src/Ryujinx.Graphics.OpenGL/HwCapabilities.cs | 4 + .../Image/BindlessHandleManager.cs | 245 ++++++++++++ .../Image/BindlessManager.cs | 166 ++++++++ src/Ryujinx.Graphics.OpenGL/Image/BitMap.cs | 189 +++++++++ .../Image/TextureBase.cs | 29 +- .../Image/TextureBuffer.cs | 2 + .../Image/TextureView.cs | 2 + src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs | 2 +- src/Ryujinx.Graphics.OpenGL/Pipeline.cs | 23 +- src/Ryujinx.Graphics.OpenGL/TypedBuffer.cs | 79 ++++ .../BindlessTextureFlags.cs | 11 + .../CodeGen/CodeGenParameters.cs | 5 +- .../CodeGen/Glsl/CodeGenContext.cs | 2 + .../CodeGen/Glsl/Declarations.cs | 126 +++--- .../HelperFunctions/GetBindlessHandle.glsl | 22 ++ .../HelperFunctions/GetBindlessHandleVk.glsl | 16 + .../HelperFunctions/HelperFunctionNames.cs | 4 + .../Glsl/Instructions/InstGenMemory.cs | 191 ++++------ .../CodeGen/Glsl/OperandManager.cs | 12 +- .../CodeGen/Spirv/CodeGenContext.cs | 5 +- .../CodeGen/Spirv/Declarations.cs | 122 ++++-- .../CodeGen/Spirv/Instructions.cs | 263 ++++++++----- .../CodeGen/Spirv/SpirvGenerator.cs | 11 + src/Ryujinx.Graphics.Shader/Constants.cs | 11 + src/Ryujinx.Graphics.Shader/IGpuAccessor.cs | 9 + .../IntermediateRepresentation/Instruction.cs | 8 + .../IntermediateRepresentation/Operation.cs | 20 + .../TextureOperation.cs | 17 +- .../ResourceReservationCounts.cs | 4 +- .../Ryujinx.Graphics.Shader.csproj | 2 + src/Ryujinx.Graphics.Shader/SamplerType.cs | 32 +- .../ShaderProgramInfo.cs | 3 + .../StructuredIr/SetBindingPair.cs | 46 +++ .../StructuredIr/ShaderProperties.cs | 32 +- .../StructuredIr/TextureDefinition.cs | 6 +- src/Ryujinx.Graphics.Shader/TextureHandle.cs | 12 + .../Translation/HelperFunctionManager.cs | 62 +++ .../Translation/HelperFunctionName.cs | 2 + .../Optimizations/BindlessToIndexed.cs | 118 ------ .../Translation/Optimizations/Optimizer.cs | 25 +- .../Translation/ResourceManager.cs | 212 +++++++---- .../Translation/ResourceReservations.cs | 7 +- .../Translation/TransformContext.cs | 11 +- .../Translation/Transforms/TexturePass.cs | 208 ++++++++-- .../Transforms/VectorComponentSelect.cs | 2 +- .../Translation/TranslatorContext.cs | 24 +- .../BindlessManager.cs | 360 ++++++++++++++++++ .../DescriptorSetCollection.cs | 19 +- .../DescriptorSetUpdater.cs | 63 ++- src/Ryujinx.Graphics.Vulkan/PipelineBase.cs | 57 ++- .../PipelineLayoutCache.cs | 18 +- .../PipelineLayoutCacheEntry.cs | 41 +- .../PipelineLayoutFactory.cs | 51 ++- .../PipelineLayoutUsageInfo.cs | 35 ++ src/Ryujinx.Graphics.Vulkan/PipelineState.cs | 18 +- src/Ryujinx.Graphics.Vulkan/PipelineUid.cs | 7 +- .../ShaderCollection.cs | 46 ++- src/Ryujinx.Graphics.Vulkan/TextureBuffer.cs | 2 + .../VulkanInitialization.cs | 14 + src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs | 4 +- 88 files changed, 3522 insertions(+), 655 deletions(-) create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/RegisterBindlessSamplerCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/RegisterBindlessTextureAndSamplerCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/RegisterBindlessTextureCommand.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/BitMap.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureValidation.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/BindlessHandleManager.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/BindlessManager.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/BitMap.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/TypedBuffer.cs create mode 100644 src/Ryujinx.Graphics.Shader/BindlessTextureFlags.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/GetBindlessHandle.glsl create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/GetBindlessHandleVk.glsl create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/SetBindingPair.cs delete mode 100644 src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToIndexed.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/BindlessManager.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/PipelineLayoutUsageInfo.cs diff --git a/src/Ryujinx.Graphics.GAL/IPipeline.cs b/src/Ryujinx.Graphics.GAL/IPipeline.cs index f5978cefa..57d65a3a5 100644 --- a/src/Ryujinx.Graphics.GAL/IPipeline.cs +++ b/src/Ryujinx.Graphics.GAL/IPipeline.cs @@ -42,6 +42,10 @@ namespace Ryujinx.Graphics.GAL void EndTransformFeedback(); + void RegisterBindlessSampler(int samplerId, ISampler sampler); + void RegisterBindlessTexture(int textureId, ITexture texture, float textureScale); + void RegisterBindlessTextureAndSampler(int textureId, ITexture texture, float textureScale, int samplerId, ISampler sampler); + void SetAlphaTest(bool enable, float reference, CompareOp op); void SetBlendState(AdvancedBlendDescriptor blend); diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs b/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs index 6cd6f1599..6e43126d9 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs @@ -100,6 +100,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading Register(CommandType.DrawTexture); Register(CommandType.EndHostConditionalRendering); Register(CommandType.EndTransformFeedback); + Register(CommandType.RegisterBindlessSampler); + Register(CommandType.RegisterBindlessTexture); + Register(CommandType.RegisterBindlessTextureAndSampler); Register(CommandType.SetAlphaTest); Register(CommandType.SetBlendStateAdvanced); Register(CommandType.SetBlendState); diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs b/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs index c24a934aa..89f9b1911 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs @@ -62,6 +62,9 @@ DrawTexture, EndHostConditionalRendering, EndTransformFeedback, + RegisterBindlessSampler, + RegisterBindlessTexture, + RegisterBindlessTextureAndSampler, SetAlphaTest, SetBlendStateAdvanced, SetBlendState, diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/RegisterBindlessSamplerCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/RegisterBindlessSamplerCommand.cs new file mode 100644 index 000000000..ec26ca38d --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/RegisterBindlessSamplerCommand.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct RegisterBindlessSamplerCommand : IGALCommand, IGALCommand + { + public CommandType CommandType => CommandType.RegisterBindlessSampler; + private int _samplerId; + private TableRef _sampler; + + public void Set(int samplerId, TableRef sampler) + { + _samplerId = samplerId; + _sampler = sampler; + } + + public static void Run(ref RegisterBindlessSamplerCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.RegisterBindlessSampler(command._samplerId, command._sampler.GetAs(threaded)?.Base); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/RegisterBindlessTextureAndSamplerCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/RegisterBindlessTextureAndSamplerCommand.cs new file mode 100644 index 000000000..27463cce6 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/RegisterBindlessTextureAndSamplerCommand.cs @@ -0,0 +1,34 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct RegisterBindlessTextureAndSamplerCommand : IGALCommand, IGALCommand + { + public CommandType CommandType => CommandType.RegisterBindlessTextureAndSampler; + private int _textureId; + private int _samplerId; + private TableRef _texture; + private TableRef _sampler; + private float _textureScale; + + public void Set(int textureId, TableRef texture, float textureScale, int samplerId, TableRef sampler) + { + _textureId = textureId; + _samplerId = samplerId; + _textureScale = textureScale; + _texture = texture; + _sampler = sampler; + } + + public static void Run(ref RegisterBindlessTextureAndSamplerCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.RegisterBindlessTextureAndSampler( + command._textureId, + command._texture.GetAs(threaded)?.Base, + command._textureScale, + command._samplerId, + command._sampler.GetAs(threaded)?.Base); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/RegisterBindlessTextureCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/RegisterBindlessTextureCommand.cs new file mode 100644 index 000000000..1e3389844 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/RegisterBindlessTextureCommand.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct RegisterBindlessTextureCommand : IGALCommand, IGALCommand + { + public CommandType CommandType => CommandType.RegisterBindlessTexture; + private int _textureId; + private TableRef _texture; + private float _textureScale; + + public void Set(int textureId, TableRef texture, float textureScale) + { + _textureId = textureId; + _texture = texture; + _textureScale = textureScale; + } + + public static void Run(ref RegisterBindlessTextureCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.RegisterBindlessTexture(command._textureId, command._texture.GetAs(threaded)?.Base, command._textureScale); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs index 69c67d642..6ea389b15 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs @@ -123,6 +123,24 @@ namespace Ryujinx.Graphics.GAL.Multithreading _renderer.QueueCommand(); } + public void RegisterBindlessSampler(int samplerId, ISampler sampler) + { + _renderer.New().Set(samplerId, Ref(sampler)); + _renderer.QueueCommand(); + } + + public void RegisterBindlessTexture(int textureId, ITexture texture, float textureScale) + { + _renderer.New().Set(textureId, Ref(texture), textureScale); + _renderer.QueueCommand(); + } + + public void RegisterBindlessTextureAndSampler(int textureId, ITexture texture, float textureScale, int samplerId, ISampler sampler) + { + _renderer.New().Set(textureId, Ref(texture), textureScale, samplerId, Ref(sampler)); + _renderer.QueueCommand(); + } + public void SetAlphaTest(bool enable, float reference, CompareOp op) { _renderer.New().Set(enable, reference, op); diff --git a/src/Ryujinx.Graphics.GAL/ShaderInfo.cs b/src/Ryujinx.Graphics.GAL/ShaderInfo.cs index 2fd3227dc..6c24ec25f 100644 --- a/src/Ryujinx.Graphics.GAL/ShaderInfo.cs +++ b/src/Ryujinx.Graphics.GAL/ShaderInfo.cs @@ -3,21 +3,24 @@ namespace Ryujinx.Graphics.GAL public struct ShaderInfo { public int FragmentOutputMap { get; } + public bool HasBindless { get; } public ResourceLayout ResourceLayout { get; } public ProgramPipelineState? State { get; } public bool FromCache { get; set; } - public ShaderInfo(int fragmentOutputMap, ResourceLayout resourceLayout, ProgramPipelineState state, bool fromCache = false) + public ShaderInfo(int fragmentOutputMap, bool hasBindless, ResourceLayout resourceLayout, ProgramPipelineState state, bool fromCache = false) { FragmentOutputMap = fragmentOutputMap; + HasBindless = hasBindless; ResourceLayout = resourceLayout; State = state; FromCache = fromCache; } - public ShaderInfo(int fragmentOutputMap, ResourceLayout resourceLayout, bool fromCache = false) + public ShaderInfo(int fragmentOutputMap, bool hasBindless, ResourceLayout resourceLayout, bool fromCache = false) { FragmentOutputMap = fragmentOutputMap; + HasBindless = hasBindless; ResourceLayout = resourceLayout; State = null; FromCache = fromCache; diff --git a/src/Ryujinx.Graphics.Gpu/Constants.cs b/src/Ryujinx.Graphics.Gpu/Constants.cs index c553d988e..6da27e8b7 100644 --- a/src/Ryujinx.Graphics.Gpu/Constants.cs +++ b/src/Ryujinx.Graphics.Gpu/Constants.cs @@ -89,5 +89,30 @@ namespace Ryujinx.Graphics.Gpu /// Maximum size that an storage buffer is assumed to have when the correct size is unknown. /// public const ulong MaxUnknownStorageSize = 0x100000; + + /// + /// Maximum width and height for 1D, 2D and cube textures, including array and multisample variants. + /// + public const int MaxTextureSize = 0x4000; + + /// + /// Maximum width, height and depth for 3D textures. + /// + public const int Max3DTextureSize = 0x800; + + /// + /// Maximum layers for array textures. + /// + public const int MaxArrayTextureLayers = 0x800; + + /// + /// Maximum width (effectively the size in pixels) for buffer textures. + /// + public const int MaxBufferTextureSize = 0x8000000; + + /// + /// Alignment in bytes for pitch linear textures. + /// + public const int LinearStrideAlignment = 0x20; } } diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs b/src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs index 67743de37..b96d9ce91 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs @@ -195,7 +195,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute // Should never return false for mismatching spec state, since the shader was fetched above. _channel.TextureManager.CommitComputeBindings(cs.SpecializationState); - _channel.BufferManager.CommitComputeBindings(); _context.Renderer.Pipeline.DispatchCompute(qmd.CtaRasterWidth, qmd.CtaRasterHeight, qmd.CtaRasterDepth); diff --git a/src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs b/src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs index 40d9a97df..065067331 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs @@ -16,7 +16,7 @@ namespace Ryujinx.Graphics.Gpu.Engine /// Texture target value public static Target GetTarget(SamplerType type) { - type &= ~(SamplerType.Indexed | SamplerType.Shadow); + type &= ~SamplerType.Shadow; switch (type) { diff --git a/src/Ryujinx.Graphics.Gpu/Image/BitMap.cs b/src/Ryujinx.Graphics.Gpu/Image/BitMap.cs new file mode 100644 index 000000000..18b9c4e17 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/BitMap.cs @@ -0,0 +1,197 @@ +using System.Numerics; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Represents a list of bits. + /// + class BitMap + { + private const int IntSize = 64; + + private const int IntShift = 6; + private const int IntMask = IntSize - 1; + + private readonly ulong[] _masks; + + /// + /// Creates a new instance of the bitmap. + /// + /// Size (in bits) that the bitmap can hold + public BitMap(int count) + { + _masks = new ulong[(count + IntMask) / IntSize]; + } + + /// + /// Sets a bit to 1. + /// + /// Index of the bit + /// True if the bit value was modified by this operation, false otherwise + public bool Set(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; + } + + /// + /// Sets a range of bits to 1. + /// + /// Inclusive index of the first bit to set + /// Inclusive index of the last bit to set + public void SetRange(int start, int end) + { + if (start == end) + { + Set(start); + return; + } + + int startIndex = start >> IntShift; + int startBit = start & IntMask; + ulong startMask = ulong.MaxValue << startBit; + + int endIndex = end >> IntShift; + int endBit = end & IntMask; + ulong endMask = ulong.MaxValue >> (IntMask - endBit); + + if (startIndex == endIndex) + { + _masks[startIndex] |= startMask & endMask; + } + else + { + _masks[startIndex] |= startMask; + + for (int i = startIndex + 1; i < endIndex; i++) + { + _masks[i] = ulong.MaxValue; + } + + _masks[endIndex] |= endMask; + } + } + + /// + /// Sets a bit to 0. + /// + /// Index of the bit + public void Clear(int bit) + { + int wordIndex = bit / IntSize; + int wordBit = bit & IntMask; + + ulong wordMask = 1UL << wordBit; + + _masks[wordIndex] &= ~wordMask; + } + + /// + /// Finds the first bit with a value of 0. + /// + /// Index of the bit with value 0, or -1 if none found + public int FindFirstUnset() + { + int index = 0; + + while (index < _masks.Length && _masks[index] == ulong.MaxValue) + { + index++; + } + + if (index == _masks.Length) + { + return -1; + } + + int bit = index * IntSize; + + bit += BitOperations.TrailingZeroCount(~_masks[index]); + + return bit; + } + + private int _iterIndex; + private ulong _iterMask; + + /// + /// Starts iterating from bit 0. + /// + public void BeginIterating() + { + _iterIndex = 0; + _iterMask = _masks.Length != 0 ? _masks[0] : 0; + } + + /// + /// Gets the next bit set to 1. + /// + /// Index of the bit, or -1 if none found + public int GetNext() + { + if (_iterIndex >= _masks.Length) + { + return -1; + } + + while (_iterMask == 0 && _iterIndex + 1 < _masks.Length) + { + _iterMask = _masks[++_iterIndex]; + } + + if (_iterMask == 0) + { + return -1; + } + + int bit = BitOperations.TrailingZeroCount(_iterMask); + + _iterMask &= ~(1UL << bit); + + return _iterIndex * IntSize + bit; + } + + /// + /// Gets the next bit set to 1, while also setting it to 0. + /// + /// Index of the bit, or -1 if none found + public int GetNextAndClear() + { + if (_iterIndex >= _masks.Length) + { + return -1; + } + + ulong mask = _masks[_iterIndex]; + + while (mask == 0 && _iterIndex + 1 < _masks.Length) + { + mask = _masks[++_iterIndex]; + } + + if (mask == 0) + { + return -1; + } + + int bit = BitOperations.TrailingZeroCount(mask); + + mask &= ~(1UL << bit); + + _masks[_iterIndex] = mask; + + return _iterIndex * IntSize + bit; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/Pool.cs b/src/Ryujinx.Graphics.Gpu/Image/Pool.cs index 0c3a219de..d48a901e6 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/Pool.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/Pool.cs @@ -22,6 +22,11 @@ namespace Ryujinx.Graphics.Gpu.Image protected T1[] Items; protected T2[] DescriptorCache; + protected readonly BitMap ModifiedEntries; + + private int _minimumAccessedId; + private int _maximumAccessedId; + /// /// The maximum ID value of resources on the pool (inclusive). /// @@ -61,6 +66,11 @@ namespace Ryujinx.Graphics.Gpu.Image int count = maximumId + 1; + ModifiedEntries = new BitMap(count); + + _minimumAccessedId = int.MaxValue; + _maximumAccessedId = 0; + ulong size = (ulong)(uint)count * DescriptorSize; Items = new T1[count]; @@ -197,6 +207,41 @@ namespace Ryujinx.Graphics.Gpu.Image return false; } + /// + /// Updates the set of entries that have been modified. + /// + /// Start address of the region of the pool that has been modfied + /// End address of the region of the pool that has been modified, exclusive + protected void UpdateModifiedEntries(ulong address, ulong endAddress) + { + int startId = (int)((address - Address) / DescriptorSize); + int endId = (int)((endAddress - Address + (DescriptorSize - 1)) / DescriptorSize) - 1; + + if (endId < startId) + { + return; + } + + ModifiedEntries.SetRange(startId, endId); + + _minimumAccessedId = Math.Min(_minimumAccessedId, startId); + _maximumAccessedId = Math.Max(_maximumAccessedId, endId); + } + + /// + /// Forces all entries as modified, to be updated if any shader uses bindless textures. + /// + public void ForceModifiedEntries() + { + for (int id = _minimumAccessedId; id <= _maximumAccessedId; id++) + { + if (Items[id] != null) + { + ModifiedEntries.Set(id); + } + } + } + protected abstract void InvalidateRangeImpl(ulong address, ulong size); protected abstract void Delete(T1 item); diff --git a/src/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs b/src/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs index e04c31dfa..660676350 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs @@ -113,6 +113,24 @@ namespace Ryujinx.Graphics.Gpu.Image return (CompareOp)(((Word0 >> 10) & 7) + 1); } + /// + /// Unpacks the font filter width. + /// + /// Font filter width + public int UnpackFontFilterWidth() + { + return (int)(Word0 >> 14) & 7; + } + + /// + /// Unpacks the font filter height. + /// + /// Font filter height + public int UnpackFontFilterHeight() + { + return (int)(Word0 >> 17) & 7; + } + /// /// Unpacks and converts the maximum anisotropy value used for texture anisotropic filtering. /// diff --git a/src/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs b/src/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs index 3efcad760..42351528d 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs @@ -1,4 +1,5 @@ using Ryujinx.Graphics.Gpu.Memory; +using System; using System.Collections.Generic; namespace Ryujinx.Graphics.Gpu.Image @@ -117,6 +118,53 @@ namespace Ryujinx.Graphics.Gpu.Image return ModifiedSequenceNumber; } + /// + /// Loads all the samplers currently registered by the guest application on the pool. + /// This is required for bindless access, as it's not possible to predict which sampler will be used. + /// + public void LoadAll() + { + if (SequenceNumber != Context.SequenceNumber) + { + SequenceNumber = Context.SequenceNumber; + + SynchronizeMemory(); + } + + ModifiedEntries.BeginIterating(); + + int id; + + while ((id = ModifiedEntries.GetNextAndClear()) >= 0) + { + Sampler sampler = Items[id] ?? GetValidated(id); + + if (sampler != null) + { + Context.Renderer.Pipeline.RegisterBindlessSampler(id, sampler.GetHostSampler(null)); + } + } + } + + /// + /// Gets the sampler at the given from the cache, + /// or creates a new one if not found. + /// This will return null if the sampler entry is considered invalid. + /// + /// Index of the sampler on the pool + /// Sampler for the given pool index + private Sampler GetValidated(int id) + { + SamplerDescriptor descriptor = GetDescriptor(id); + + if (descriptor.UnpackFontFilterWidth() != 1 || descriptor.UnpackFontFilterHeight() != 1 || (descriptor.Word0 >> 23) != 0) + { + return null; + } + + return Get(id); + } + /// /// Implementation of the sampler pool range invalidation. /// @@ -126,6 +174,8 @@ namespace Ryujinx.Graphics.Gpu.Image { ulong endAddress = address + size; + UpdateModifiedEntries(address, endAddress); + for (; address < endAddress; address += DescriptorSize) { int id = (int)((address - Address) / DescriptorSize); diff --git a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs index f1615b388..5742bae9a 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -1462,6 +1462,19 @@ namespace Ryujinx.Graphics.Gpu.Image DisposeTextures(); HostTexture = hostTexture; + + ForceTexturePoolUpdate(); + } + + /// + /// Forces the entries on all texture pool where this texture is present to be updated. + /// + private void ForceTexturePoolUpdate() + { + foreach (TexturePoolOwner poolOwner in _poolOwners) + { + poolOwner.Pool.ForceModifiedEntry(poolOwner.ID); + } } /// diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs index 8eca18b48..ea2bd00ba 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs @@ -5,6 +5,7 @@ using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Gpu.Shader; using Ryujinx.Graphics.Shader; using System; +using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -59,6 +60,8 @@ namespace Ryujinx.Graphics.Gpu.Image private int _texturePoolSequence; private int _samplerPoolSequence; + private BindlessTextureFlags[] _bindlessTextureFlags; + private int _textureBufferIndex; private int _lastFragmentTotal; @@ -93,6 +96,8 @@ namespace Ryujinx.Graphics.Gpu.Image _textureState = new TextureState[InitialTextureStateSize]; _imageState = new TextureState[InitialImageStateSize]; + _bindlessTextureFlags = new BindlessTextureFlags[stages]; + for (int stage = 0; stage < stages; stage++) { _textureBindings[stage] = new TextureBindingInfo[InitialTextureStateSize]; @@ -109,6 +114,8 @@ namespace Ryujinx.Graphics.Gpu.Image _textureBindings = bindings.TextureBindings; _imageBindings = bindings.ImageBindings; + _bindlessTextureFlags = bindings.BindlessTextureFlags; + SetMaxBindings(bindings.MaxTextureBinding, bindings.MaxImageBinding); } @@ -305,6 +312,23 @@ namespace Ryujinx.Graphics.Gpu.Image // If it wasn't, then it's possible to avoid looking up textures again when the handle remains the same. if (_cachedTexturePool != texturePool || _cachedSamplerPool != samplerPool) { + bool anyFullBindless = false; + + for (int index = 0; index < (_isCompute ? 1 : _bindlessTextureFlags.Length); index++) + { + if (_bindlessTextureFlags[index].HasFlag(BindlessTextureFlags.BindlessFull)) + { + anyFullBindless = true; + break; + } + } + + if (anyFullBindless) + { + texturePool?.ForceModifiedEntries(); + samplerPool?.ForceModifiedEntries(); + } + Rebind(); _cachedTexturePool = texturePool; @@ -341,6 +365,15 @@ 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)) + { + texturePool.LoadAll(_context.Renderer, _samplerPool); + } } else { @@ -350,6 +383,15 @@ 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)) + { + texturePool.LoadAll(_context.Renderer, _samplerPool); + } } } @@ -460,8 +502,12 @@ namespace Ryujinx.Graphics.Gpu.Image ReadOnlySpan cachedTextureBuffer = Span.Empty; ReadOnlySpan cachedSamplerBuffer = Span.Empty; + int maxTexturesPerStage = TextureHandle.GetMaxTexturesPerStage(_context.Capabilities.Api); + for (int index = 0; index < textureCount; index++) { + bool asBindless = index >= maxTexturesPerStage; + TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index]; TextureUsageFlags usageFlags = bindingInfo.Flags; @@ -524,7 +570,17 @@ namespace Ryujinx.Graphics.Gpu.Image // Ensure that the buffer texture is using the correct buffer as storage. // Buffers are frequently re-created to accommodate larger data, so we need to re-bind // to ensure we're not using a old buffer that was already deleted. - _channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, bindingInfo.Format, false); + ulong address = texture.Range.GetSubRange(0).Address; + ulong size = texture.Size; + + if (asBindless) + { + _channel.BufferManager.SetBufferTextureStorage(hostTexture, address, size, bindingInfo, bindingInfo.Format, false, textureId); + } + else + { + _channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, address, size, bindingInfo, bindingInfo.Format, false); + } // Cache is not used for buffer texture, it must always rebind. state.CachedTexture = null; @@ -545,7 +601,14 @@ namespace Ryujinx.Graphics.Gpu.Image state.Texture = hostTexture; state.Sampler = hostSampler; - _context.Renderer.Pipeline.SetTextureAndSampler(stage, bindingInfo.Binding, hostTexture, hostSampler); + if (asBindless) + { + _context.Renderer.Pipeline.RegisterBindlessTextureAndSampler(textureId, hostTexture, texture?.ScaleFactor ?? 1f, samplerId, hostSampler); + } + else + { + _context.Renderer.Pipeline.SetTextureAndSampler(stage, bindingInfo.Binding, hostTexture, hostSampler); + } } state.CachedTexture = texture; @@ -703,6 +766,93 @@ namespace Ryujinx.Graphics.Gpu.Image return specStateMatches; } + /// + /// 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 shader stage using the textures to be bound + /// The stage number of the specified shader stage + private void CommitBindlessResources(TexturePool pool, ShaderStage stage, int stageIndex) + { + var samplerPool = _samplerPool; + + if (pool == null) + { + Logger.Error?.Print(LogClass.Gpu, $"Shader stage \"{stage}\" uses bindless textures, but texture pool was not set."); + return; + } + + for (int index = 0; index < 32; index++) + { + int wordOffset = 8 + index * 2; + + int packedId = ReadConstantBuffer(stageIndex, _textureBufferIndex, wordOffset); + + int textureId = TextureHandle.UnpackTextureId(packedId); + int samplerId; + + if (_samplerIndex == SamplerIndex.ViaHeaderIndex) + { + samplerId = textureId; + } + else + { + samplerId = TextureHandle.UnpackSamplerId(packedId); + } + + Texture texture = pool.Get(textureId); + + if (texture == null) + { + continue; + } + + if (texture.Target == Target.TextureBuffer) + { + // 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 TextureBindingInfo(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); + } + else + { + Sampler sampler = samplerPool?.Get(samplerId); + + if (sampler == null) + { + continue; + } + + _context.Renderer.Pipeline.RegisterBindlessTextureAndSampler( + textureId, + texture.HostTexture, + texture.ScaleFactor, + samplerId, + sampler.GetHostSampler(texture)); + } + } + } + + /// + /// Reads a value from a constant buffer. + /// + /// 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 + /// The value at the specified buffer and offset + private unsafe T ReadConstantBuffer(int stageIndex, int bufferIndex, int elementIndex) 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)); + } + /// /// Gets the texture descriptor for a given texture handle. /// @@ -749,14 +899,13 @@ namespace Ryujinx.Graphics.Gpu.Image } /// - /// Reads a packed texture and sampler ID (basically, the real texture handle) - /// from the texture constant buffer. + /// Reads a combined texture and sampler handle from the texture constant buffer. /// /// The number of the shader stage where the texture is bound - /// A word offset of the handle on the buffer (the "fake" shader handle) + /// The word offset of the handle on the buffer /// 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) + /// The combined texture and sampler handle [MethodImpl(MethodImplOptions.AggressiveInlining)] private int ReadPackedId(int stageIndex, int wordOffset, int textureBufferIndex, int samplerBufferIndex) { diff --git a/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs index 0fdb6cd64..cbb163b5b 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs @@ -2,6 +2,7 @@ using Ryujinx.Common.Logging; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Texture; +using Ryujinx.Memory; using Ryujinx.Memory.Range; using System; using System.Collections.Concurrent; @@ -212,6 +213,94 @@ namespace Ryujinx.Graphics.Gpu.Image return ModifiedSequenceNumber; } + /// + /// Loads all the textures currently registered by the guest application on the pool. + /// This is required for bindless access, as it's not possible to predict which textures will be used. + /// + /// Renderer of the current GPU context + /// The currently active sampler pool + public void LoadAll(IRenderer renderer, SamplerPool activeSamplerPool) + { + activeSamplerPool?.LoadAll(); + + if (SequenceNumber != Context.SequenceNumber) + { + SequenceNumber = Context.SequenceNumber; + + SynchronizeMemory(); + } + + ModifiedEntries.BeginIterating(); + + int id; + + while ((id = ModifiedEntries.GetNextAndClear()) >= 0) + { + Texture texture = Items[id] ?? GetValidated(id); + + if (texture != null) + { + if (texture.Target == Target.TextureBuffer) + { + _channel.BufferManager.SetBufferTextureStorage( + texture.HostTexture, + texture.Range.GetSubRange(0).Address, + texture.Size, + default, + 0, + false, + id); + } + else + { + renderer.Pipeline.RegisterBindlessTexture(id, texture.HostTexture, texture.ScaleFactor); + } + } + } + } + + /// + /// Gets the texture at the given from the cache, + /// or creates a new one if not found. + /// This will return null if the texture entry is considered invalid. + /// + /// Index of the texture on the pool + /// Texture for the given pool index + private Texture GetValidated(int id) + { + TextureDescriptor descriptor = GetDescriptor(id); + + if (!FormatTable.TryGetTextureFormat(descriptor.UnpackFormat(), descriptor.UnpackSrgb(), out _)) + { + return null; + } + + TextureInfo info = GetInfo(descriptor, out int layerSize); + TextureValidationResult validationResult = TextureValidation.Validate(ref info); + + if (validationResult != TextureValidationResult.Valid) + { + return null; + } + + // TODO: Eventually get rid of that... + // For now it avoids creating textures for garbage entries in some cases, but it is not + // correct as a width or height of 8192 is valid (although extremely unlikely). + if (info.Width > 8192 || info.Height > 8192 || info.DepthOrLayers > 8192) + { + return null; + } + + try + { + return Get(id); + } + catch (InvalidMemoryRegionException) + { + return null; + } + } + /// /// Forcibly remove a texture from this pool's items. /// If deferred, the dereference will be queued to occur on the render thread. @@ -342,6 +431,8 @@ namespace Ryujinx.Graphics.Gpu.Image ulong endAddress = address + size; + UpdateModifiedEntries(address, endAddress); + for (; address < endAddress; address += DescriptorSize) { int id = (int)((address - Address) / DescriptorSize); @@ -373,6 +464,15 @@ namespace Ryujinx.Graphics.Gpu.Image } } + /// + /// Forces a entry as modified, to be updated if any shader uses bindless textures. + /// + /// ID of the entry to be updated + public void ForceModifiedEntry(int id) + { + ModifiedEntries.Set(id); + } + /// /// Gets texture information from a texture descriptor. /// diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureValidation.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureValidation.cs new file mode 100644 index 000000000..66e50e405 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureValidation.cs @@ -0,0 +1,107 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Texture validation result. + /// + enum TextureValidationResult + { + Valid, + InvalidSize, + InvalidTarget, + InvalidFormat + } + + /// + /// Texture validation utilities. + /// + static class TextureValidation + { + /// + /// Checks if the texture parameters are valid. + /// + /// Texture parameters + /// Validation result + public static TextureValidationResult Validate(ref TextureInfo info) + { + bool validSize; + + switch (info.Target) + { + case Target.Texture1D: + validSize = (uint)info.Width <= Constants.MaxTextureSize; + break; + case Target.Texture2D: + validSize = (uint)info.Width <= Constants.MaxTextureSize && + (uint)info.Height <= Constants.MaxTextureSize; + break; + case Target.Texture3D: + validSize = (uint)info.Width <= Constants.Max3DTextureSize && + (uint)info.Height <= Constants.Max3DTextureSize && + (uint)info.DepthOrLayers <= Constants.Max3DTextureSize; + break; + case Target.Texture1DArray: + validSize = (uint)info.Width <= Constants.MaxTextureSize && + (uint)info.DepthOrLayers <= Constants.MaxArrayTextureLayers; + break; + case Target.Texture2DArray: + validSize = (uint)info.Width <= Constants.MaxTextureSize && + (uint)info.Height <= Constants.MaxTextureSize && + (uint)info.DepthOrLayers <= Constants.MaxArrayTextureLayers; + break; + case Target.Texture2DMultisample: + validSize = (uint)info.Width <= Constants.MaxTextureSize && + (uint)info.Height <= Constants.MaxTextureSize; + break; + case Target.Texture2DMultisampleArray: + validSize = (uint)info.Width <= Constants.MaxTextureSize && + (uint)info.Height <= Constants.MaxTextureSize && + (uint)info.DepthOrLayers <= Constants.MaxArrayTextureLayers; + break; + case Target.Cubemap: + validSize = (uint)info.Width <= Constants.MaxTextureSize && + (uint)info.Height <= Constants.MaxTextureSize && info.Width == info.Height; + break; + case Target.CubemapArray: + validSize = (uint)info.Width <= Constants.MaxTextureSize && + (uint)info.Height <= Constants.MaxTextureSize && + (uint)info.DepthOrLayers <= Constants.MaxArrayTextureLayers && info.Width == info.Height; + break; + case Target.TextureBuffer: + validSize = (uint)info.Width <= Constants.MaxBufferTextureSize; + break; + default: + return TextureValidationResult.InvalidTarget; + } + + if (!validSize) + { + return TextureValidationResult.InvalidSize; + } + + if (info.IsLinear && (uint)info.Width > (uint)info.Stride) + { + return TextureValidationResult.InvalidSize; + } + + return TextureValidationResult.Valid; + } + + /// + /// Checks if a sampler can be used in combination with a given texture. + /// + /// Texture parameters + /// Sampler parameters + /// True if they can be used together, false otherwise + public static bool IsSamplerCompatible(TextureInfo info, SamplerDescriptor sampler) + { + if (info.FormatInfo.Format.IsDepthOrStencil() != (sampler.UnpackCompareMode() == CompareMode.CompareRToTexture)) + { + return false; + } + + return true; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs index 8e9b4b858..78519cabb 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs @@ -482,7 +482,11 @@ namespace Ryujinx.Graphics.Gpu.Memory // The texture must be rebound to use the new storage if it was updated. - if (binding.IsImage) + if (binding.AsBindless) + { + _context.Renderer.Pipeline.RegisterBindlessTexture(binding.TextureId, binding.Texture, 1f); + } + else if (binding.IsImage) { _context.Renderer.Pipeline.SetImage(binding.BindingInfo.Binding, binding.Texture, binding.Format); } @@ -812,6 +816,30 @@ namespace Ryujinx.Graphics.Gpu.Memory _bufferTextures.Add(new BufferTextureBinding(stage, texture, address, size, bindingInfo, format, isImage)); } + /// + /// Sets the buffer storage of a bindless buffer texture. This will be bound when the buffer manager commits bindings. + /// + /// Buffer texture + /// Address of the buffer in memory + /// Size of the buffer in bytes + /// Binding info for the buffer texture + /// Format of the buffer texture + /// Whether the binding is for an image or a sampler + /// ID of the texture on the pool/param> + public void SetBufferTextureStorage( + ITexture texture, + ulong address, + ulong size, + TextureBindingInfo bindingInfo, + Format format, + bool isImage, + int textureId) + { + _channel.MemoryManager.Physical.BufferCache.CreateBuffer(address, size); + + _bufferTextures.Add(new BufferTextureBinding(texture, address, size, bindingInfo, format, isImage, textureId)); + } + /// /// Force all bound textures and images to be rebound the next time CommitBindings is called. /// diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureBinding.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureBinding.cs index b7a0e7264..29457ae92 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureBinding.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureBinding.cs @@ -44,6 +44,16 @@ namespace Ryujinx.Graphics.Gpu.Memory /// public bool IsImage { get; } + /// + /// Indicates if the texture should be bound as a bindless texture. + /// + public bool AsBindless { get; } + + /// + /// For bindless textures, indicates the texture ID. + /// + public int TextureId { get; } + /// /// Create a new buffer texture binding. /// @@ -70,6 +80,31 @@ namespace Ryujinx.Graphics.Gpu.Memory BindingInfo = bindingInfo; Format = format; IsImage = isImage; + AsBindless = false; + TextureId = 0; + } + + /// + /// Create a new bindless buffer texture binding. + /// + /// Buffer texture + /// Base address + /// Size in bytes + /// Binding info + /// Binding format + /// Whether the binding is for an image or a sampler + /// ID of the texture on the pool + public BufferTextureBinding(ITexture texture, ulong address, ulong size, TextureBindingInfo bindingInfo, Format format, bool isImage, int textureId) + { + Stage = default; + Texture = texture; + Address = address; + Size = size; + BindingInfo = bindingInfo; + Format = format; + IsImage = isImage; + AsBindless = true; + TextureId = textureId; } } } diff --git a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs index 1734f08a2..e18ff1f17 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs @@ -17,6 +17,8 @@ namespace Ryujinx.Graphics.Gpu.Shader public BufferDescriptor[][] ConstantBufferBindings { get; } public BufferDescriptor[][] StorageBufferBindings { get; } + public BindlessTextureFlags[] BindlessTextureFlags { get; } + public int MaxTextureBinding { get; } public int MaxImageBinding { get; } @@ -34,6 +36,8 @@ namespace Ryujinx.Graphics.Gpu.Shader ConstantBufferBindings = new BufferDescriptor[stageCount][]; StorageBufferBindings = new BufferDescriptor[stageCount][]; + BindlessTextureFlags = new BindlessTextureFlags[stageCount]; + int maxTextureBinding = -1; int maxImageBinding = -1; int offset = isCompute ? 0 : 1; @@ -94,6 +98,8 @@ namespace Ryujinx.Graphics.Gpu.Shader ConstantBufferBindings[i] = stage.Info.CBuffers.ToArray(); StorageBufferBindings[i] = stage.Info.SBuffers.ToArray(); + + BindlessTextureFlags[i] = stage.Info.BindlessTextureFlags; } MaxTextureBinding = maxTextureBinding; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs index de6432bc1..5f8848482 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs @@ -123,6 +123,14 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache return _oldSpecState.GetTextureTarget(_stageIndex, handle, cbufSlot).ConvertSamplerType(); } + /// + public int QueryTextureBufferIndex() + { + byte textureBufferIndex = _oldSpecState.GetTextureBufferIndex(); + _newSpecState.RecordTextureBufferIndex(textureBufferIndex); + return textureBufferIndex; + } + /// public bool QueryTextureCoordNormalized(int handle, int cbufSlot) { diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs index 0dc4b1a72..4198cb693 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs @@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMinor = 2; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; - private const uint CodeGenVersion = 5791; + private const uint CodeGenVersion = 3001; private const string SharedTocFileName = "shared.toc"; private const string SharedDataFileName = "shared.data"; @@ -184,6 +184,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache /// Indicates if the vertex shader accesses draw parameters. /// public bool UsesDrawParameters; + + /// + /// Flags indicating if and how bindless texture accesses were translated for the shader stage. + /// + public BindlessTextureFlags BindlessTextureFlags; } private readonly DiskCacheGuestStorage _guestStorage; @@ -799,6 +804,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache textures, images, dataInfo.Stage, + dataInfo.BindlessTextureFlags, dataInfo.GeometryVerticesPerPrimitive, dataInfo.GeometryMaxOutputVertices, dataInfo.ThreadsPerInputPrimitive, @@ -829,6 +835,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache TexturesCount = (ushort)info.Textures.Count, ImagesCount = (ushort)info.Images.Count, Stage = info.Stage, + BindlessTextureFlags = info.BindlessTextureFlags, GeometryVerticesPerPrimitive = (byte)info.GeometryVerticesPerPrimitive, GeometryMaxOutputVertices = (ushort)info.GeometryMaxOutputVertices, ThreadsPerInputPrimitive = (ushort)info.ThreadsPerInputPrimitive, diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs index 1d84d0e46..8845d694e 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs @@ -134,6 +134,14 @@ namespace Ryujinx.Graphics.Gpu.Shader return GetTextureDescriptor(handle, cbufSlot).UnpackTextureTarget().ConvertSamplerType(); } + /// + public int QueryTextureBufferIndex() + { + byte textureBufferIndex = (byte)_state.PoolState.TextureBufferIndex; + _state.SpecializationState?.RecordTextureBufferIndex(textureBufferIndex); + return textureBufferIndex; + } + /// public bool QueryTextureCoordNormalized(int handle, int cbufSlot) { diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs index a5b31363b..8deb83c74 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs @@ -40,7 +40,10 @@ namespace Ryujinx.Graphics.Gpu.Shader /// Indicates that the vertex shader will be emulated on a compute shader public void InitializeReservedCounts(bool tfEnabled, bool vertexAsCompute) { - ResourceReservationCounts rrc = new(!_context.Capabilities.SupportsTransformFeedback && tfEnabled, vertexAsCompute); + ResourceReservationCounts rrc = new( + _context.Capabilities.Api, + !_context.Capabilities.SupportsTransformFeedback && tfEnabled, + vertexAsCompute); _reservedConstantBuffers = rrc.ReservedConstantBuffers; _reservedStorageBuffers = rrc.ReservedStorageBuffers; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs index c2258026c..da775e703 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs @@ -1,5 +1,6 @@ using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Shader; +using System; using System.Collections.Generic; namespace Ryujinx.Graphics.Gpu.Shader @@ -10,11 +11,17 @@ namespace Ryujinx.Graphics.Gpu.Shader class ShaderInfoBuilder { private const int TotalSets = 4; + private const int TotalBindlessSets = 9; private const int UniformSetIndex = 0; private const int StorageSetIndex = 1; private const int TextureSetIndex = 2; private const int ImageSetIndex = 3; + private const int BindlessTextureSetIndex = 4; + private const int BindlessBufferTextureSetIndex = 5; + private const int BindlessSamplerSetIndex = 6; + private const int BindlessImageSetIndex = 7; + private const int BindlessBufferImageSetIndex = 8; private const ResourceStages SupportBufferStages = ResourceStages.Compute | @@ -27,17 +34,20 @@ namespace Ryujinx.Graphics.Gpu.Shader ResourceStages.TessellationEvaluation | ResourceStages.Geometry; + private const ResourceStages GraphicsStages = VtgStages | ResourceStages.Fragment; + private readonly GpuContext _context; private int _fragmentOutputMap; + private bool _anyBindless; private readonly int _reservedConstantBuffers; private readonly int _reservedStorageBuffers; private readonly int _reservedTextures; private readonly int _reservedImages; - private readonly List[] _resourceDescriptors; - private readonly List[] _resourceUsages; + private List[] _resourceDescriptors; + private List[] _resourceUsages; /// /// Creates a new shader info builder. @@ -63,7 +73,10 @@ namespace Ryujinx.Graphics.Gpu.Shader AddDescriptor(SupportBufferStages, ResourceType.UniformBuffer, UniformSetIndex, 0, 1); AddUsage(SupportBufferStages, ResourceType.UniformBuffer, UniformSetIndex, 0, 1); - ResourceReservationCounts rrc = new(!context.Capabilities.SupportsTransformFeedback && tfEnabled, vertexAsCompute); + ResourceReservationCounts rrc = new( + context.Capabilities.Api, + !context.Capabilities.SupportsTransformFeedback && tfEnabled, + vertexAsCompute); _reservedConstantBuffers = rrc.ReservedConstantBuffers; _reservedStorageBuffers = rrc.ReservedStorageBuffers; @@ -97,6 +110,30 @@ namespace Ryujinx.Graphics.Gpu.Shader _fragmentOutputMap = info.FragmentOutputMap; } + if (info.BindlessTextureFlags != BindlessTextureFlags.None && !_anyBindless) + { + Array.Resize(ref _resourceDescriptors, TotalBindlessSets); + Array.Resize(ref _resourceUsages, TotalBindlessSets); + + for (int index = TotalSets; index < TotalBindlessSets; index++) + { + _resourceDescriptors[index] = new(); + _resourceUsages[index] = new(); + } + + ResourceStages bindlessStages = info.Stage == ShaderStage.Compute ? ResourceStages.Compute : GraphicsStages; + + AddDescriptor(bindlessStages, ResourceType.UniformBuffer, BindlessTextureSetIndex, 0, 1); + AddDescriptor(bindlessStages, ResourceType.StorageBuffer, BindlessTextureSetIndex, 1, 1); + AddArrayDescriptor(bindlessStages, ResourceType.Texture, BindlessTextureSetIndex, 2, 0); + AddArrayDescriptor(bindlessStages, ResourceType.BufferTexture, BindlessBufferTextureSetIndex, 0, 0); + AddArrayDescriptor(bindlessStages, ResourceType.Sampler, BindlessSamplerSetIndex, 0, 0); + AddArrayDescriptor(bindlessStages, ResourceType.Image, BindlessImageSetIndex, 0, 0); + AddArrayDescriptor(bindlessStages, ResourceType.BufferImage, BindlessBufferImageSetIndex, 0, 0); + + _anyBindless = true; + } + int stageIndex = GpuAccessorBase.GetStageIndex(info.Stage switch { ShaderStage.TessellationControl => 1, @@ -154,6 +191,19 @@ namespace Ryujinx.Graphics.Gpu.Shader } } + /// + /// Adds an array of resource descriptors to the list of descriptors. + /// + /// Shader stages where the resource is used + /// Type of the resource + /// Descriptor set number where the resource will be bound + /// Binding number where the resource will be bound + /// Number of array elements + private void AddArrayDescriptor(ResourceStages stages, ResourceType type, int setIndex, int binding, int count) + { + _resourceDescriptors[setIndex].Add(new ResourceDescriptor(binding, count, type, stages)); + } + /// /// Adds two interleaved groups of resources to the list of descriptors. /// @@ -235,10 +285,10 @@ namespace Ryujinx.Graphics.Gpu.Shader /// Shader information public ShaderInfo Build(ProgramPipelineState? pipeline, bool fromCache = false) { - var descriptors = new ResourceDescriptorCollection[TotalSets]; - var usages = new ResourceUsageCollection[TotalSets]; + var descriptors = new ResourceDescriptorCollection[_resourceDescriptors.Length]; + var usages = new ResourceUsageCollection[_resourceUsages.Length]; - for (int index = 0; index < TotalSets; index++) + for (int index = 0; index < _resourceDescriptors.Length; index++) { descriptors[index] = new ResourceDescriptorCollection(_resourceDescriptors[index].ToArray().AsReadOnly()); usages[index] = new ResourceUsageCollection(_resourceUsages[index].ToArray().AsReadOnly()); @@ -248,11 +298,11 @@ namespace Ryujinx.Graphics.Gpu.Shader if (pipeline.HasValue) { - return new ShaderInfo(_fragmentOutputMap, resourceLayout, pipeline.Value, fromCache); + return new ShaderInfo(_fragmentOutputMap, _anyBindless, resourceLayout, pipeline.Value, fromCache); } else { - return new ShaderInfo(_fragmentOutputMap, resourceLayout, fromCache); + return new ShaderInfo(_fragmentOutputMap, _anyBindless, resourceLayout, fromCache); } } diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs index a41f761bd..565d3db35 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs @@ -30,11 +30,13 @@ namespace Ryujinx.Graphics.Gpu.Shader { PrimitiveTopology = 1 << 1, TransformFeedback = 1 << 3, + TextureBufferIndex = 1 << 4, } private QueriedStateFlags _queriedState; private bool _compute; private byte _constantBufferUsePerStage; + private byte _textureBufferIndex; /// /// Compute engine state. @@ -323,6 +325,16 @@ namespace Ryujinx.Graphics.Gpu.Shader state.Value.CoordNormalized = coordNormalized; } + /// + /// Records the index of the constant buffer with texture handles. + /// + /// Index of the constant buffer with texture handles + public void RecordTextureBufferIndex(byte index) + { + _textureBufferIndex = index; + _queriedState |= QueriedStateFlags.TextureBufferIndex; + } + /// /// Indicates that the format of a given texture was used during the shader translation process. /// @@ -385,18 +397,30 @@ namespace Ryujinx.Graphics.Gpu.Shader /// Shader stage where the texture is used /// Offset in words of the texture handle on the texture buffer /// Slot of the texture buffer constant buffer + /// Format of the given texture, and whether that format is a sRGB format public (uint, bool) GetFormat(int stageIndex, int handle, int cbufSlot) { TextureSpecializationState state = GetTextureSpecState(stageIndex, handle, cbufSlot).Value; return (state.Format, state.FormatSrgb); } + /// + /// Gets the index of the constant buffer with texture handles. + /// + /// Index of the constant buffer with texture handles + public byte GetTextureBufferIndex() + { + // Note: We assume the NVN default if the cache is old and did not store the buffer index. + return _queriedState.HasFlag(QueriedStateFlags.TextureBufferIndex) ? _textureBufferIndex : (byte)TextureHandle.NvnTextureBufferIndex; + } + /// /// Gets the recorded target of a given texture. /// /// Shader stage where the texture is used /// Offset in words of the texture handle on the texture buffer /// Slot of the texture buffer constant buffer + /// Target of the given texture public TextureTarget GetTextureTarget(int stageIndex, int handle, int cbufSlot) { return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.TextureTarget; @@ -408,6 +432,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// Shader stage where the texture is used /// Offset in words of the texture handle on the texture buffer /// Slot of the texture buffer constant buffer + /// Normalization state of the given texture public bool GetCoordNormalized(int stageIndex, int handle, int cbufSlot) { return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.CoordNormalized; @@ -799,6 +824,11 @@ namespace Ryujinx.Graphics.Gpu.Shader constantBufferUsePerStageMask &= ~(1 << index); } + if (specState._queriedState.HasFlag(QueriedStateFlags.TextureBufferIndex)) + { + dataReader.Read(ref specState._textureBufferIndex); + } + bool hasPipelineState = false; dataReader.Read(ref hasPipelineState); @@ -870,6 +900,11 @@ namespace Ryujinx.Graphics.Gpu.Shader constantBufferUsePerStageMask &= ~(1 << index); } + if (_queriedState.HasFlag(QueriedStateFlags.TextureBufferIndex)) + { + dataWriter.Write(ref _textureBufferIndex); + } + bool hasPipelineState = PipelineState.HasValue; dataWriter.Write(ref hasPipelineState); diff --git a/src/Ryujinx.Graphics.OpenGL/HwCapabilities.cs b/src/Ryujinx.Graphics.OpenGL/HwCapabilities.cs index cf0b0645c..43a3b22c4 100644 --- a/src/Ryujinx.Graphics.OpenGL/HwCapabilities.cs +++ b/src/Ryujinx.Graphics.OpenGL/HwCapabilities.cs @@ -5,6 +5,7 @@ namespace Ryujinx.Graphics.OpenGL { static class HwCapabilities { + private static readonly Lazy _supportsArbBindlessTexture = new Lazy(() => HasExtension("GL_ARB_bindless_texture")); private static readonly Lazy _supportsAlphaToCoverageDitherControl = new(() => HasExtension("GL_NV_alpha_to_coverage_dither_control")); private static readonly Lazy _supportsAstcCompression = new(() => HasExtension("GL_KHR_texture_compression_astc_ldr")); private static readonly Lazy _supportsBlendEquationAdvanced = new(() => HasExtension("GL_NV_blend_equation_advanced")); @@ -14,6 +15,7 @@ namespace Ryujinx.Graphics.OpenGL private static readonly Lazy _supportsGeometryShaderPassthrough = new(() => HasExtension("GL_NV_geometry_shader_passthrough")); private static readonly Lazy _supportsImageLoadFormatted = new(() => HasExtension("GL_EXT_shader_image_load_formatted")); private static readonly Lazy _supportsIndirectParameters = new(() => HasExtension("GL_ARB_indirect_parameters")); + private static readonly Lazy _supportsNvBindlessTexture = new Lazy(() => HasExtension("GL_NV_bindless_texture")); private static readonly Lazy _supportsParallelShaderCompile = new(() => HasExtension("GL_ARB_parallel_shader_compile")); private static readonly Lazy _supportsPolygonOffsetClamp = new(() => HasExtension("GL_EXT_polygon_offset_clamp")); private static readonly Lazy _supportsQuads = new(SupportsQuadsCheck); @@ -51,6 +53,7 @@ namespace Ryujinx.Graphics.OpenGL public static bool UsePersistentBufferForFlush => _gpuVendor.Value == GpuVendor.AmdWindows || _gpuVendor.Value == GpuVendor.Nvidia; + public static bool SupportsArbBindlessTexture => _supportsArbBindlessTexture.Value; public static bool SupportsAlphaToCoverageDitherControl => _supportsAlphaToCoverageDitherControl.Value; public static bool SupportsAstcCompression => _supportsAstcCompression.Value; public static bool SupportsBlendEquationAdvanced => _supportsBlendEquationAdvanced.Value; @@ -60,6 +63,7 @@ namespace Ryujinx.Graphics.OpenGL public static bool SupportsGeometryShaderPassthrough => _supportsGeometryShaderPassthrough.Value; public static bool SupportsImageLoadFormatted => _supportsImageLoadFormatted.Value; public static bool SupportsIndirectParameters => _supportsIndirectParameters.Value; + public static bool SupportsNvBindlessTexture => _supportsNvBindlessTexture.Value; public static bool SupportsParallelShaderCompile => _supportsParallelShaderCompile.Value; public static bool SupportsPolygonOffsetClamp => _supportsPolygonOffsetClamp.Value; public static bool SupportsQuads => _supportsQuads.Value; diff --git a/src/Ryujinx.Graphics.OpenGL/Image/BindlessHandleManager.cs b/src/Ryujinx.Graphics.OpenGL/Image/BindlessHandleManager.cs new file mode 100644 index 000000000..e1239f407 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Image/BindlessHandleManager.cs @@ -0,0 +1,245 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.OpenGL.Image +{ + /// + /// Host bindless texture handle manager. + /// + class BindlessHandleManager + { + // This uses two tables to store the handles. + // The first level has a fixed size, and stores indices pointing into the second level. + // The second level is dynamically allocated, and stores the host handles themselves (among other things). + // + // The first level is indexed using the low bits of the (guest) texture ID and sampler ID. + // The second level can be thought as a 2D array, where the first dimension is indexed using the index from + // the first level, and the second dimension is indexed using the high texture ID and sampler ID bits. + + private const int BlockSize = 0x10000; + + private readonly BitMap _freeList; + + /// + /// Second level block state. + /// + private struct Block + { + public int Index; + public int ReferenceCount; + } + + private readonly Block[] _blocks; + + private readonly Dictionary> _texturesOnBlocks; + + /// + /// Handle entry accessed by the shader. + /// + private struct HandleEntry + { + public long Handle; + public float Scale; + public uint Padding; + + public HandleEntry(long handle, float scale) + { + Handle = handle; + Scale = scale; + Padding = 0; + } + } + + private readonly TypedBuffer _textureList; // First level. + private readonly TypedBuffer _handleList; // Second level. + + private readonly ITexture _bufferTextureForTextureList; + private readonly ITexture _bufferTextureForHandleList; + + /// + /// Creates a new instance of the host bindless texture handle manager. + /// + /// Renderer + public BindlessHandleManager(OpenGLRenderer renderer) + { + _freeList = new BitMap(); + _blocks = new Block[0x100000]; + _texturesOnBlocks = new Dictionary>(); + + _textureList = new TypedBuffer(renderer, 0x100000); + _handleList = new TypedBuffer(renderer, BlockSize); + + _bufferTextureForTextureList = CreateBufferTexture(renderer, _textureList); + _bufferTextureForHandleList = CreateBufferTexture(renderer, _handleList); + } + + /// + /// Creates a buffer texture with the provided buffer. + /// + /// Type of the data on the buffer + /// Renderer + /// Buffer + /// Buffer texture + private static ITexture CreateBufferTexture(OpenGLRenderer renderer, TypedBuffer buffer) where T : unmanaged + { + int bytesPerPixel = Unsafe.SizeOf(); + + Format format = bytesPerPixel switch + { + 1 => Format.R8Uint, + 2 => Format.R16Uint, + 4 => Format.R32Uint, + 8 => Format.R32G32Uint, + 16 => Format.R32G32B32A32Uint, + _ => throw new ArgumentException("Invalid type specified.") + }; + + ITexture texture = renderer.CreateTexture(new TextureCreateInfo( + buffer.Size / bytesPerPixel, + 1, + 1, + 1, + 1, + 1, + 1, + bytesPerPixel, + format, + DepthStencilMode.Depth, + Target.TextureBuffer, + SwizzleComponent.Red, + SwizzleComponent.Green, + SwizzleComponent.Blue, + SwizzleComponent.Alpha)); + + texture.SetStorage(buffer.GetBufferRange()); + + return texture; + } + + /// + /// Binds the multi-level handle table buffer textures on the host. + /// + /// Renderer + public void Bind(OpenGLRenderer renderer) + { + // TODO: Proper shader stage (doesn't really matter as the OpenGL backend doesn't use this at all). + renderer.Pipeline.SetTextureAndSampler(ShaderStage.Vertex, 0, _bufferTextureForTextureList, null); + renderer.Pipeline.SetTextureAndSampler(ShaderStage.Vertex, 1, _bufferTextureForHandleList, null); + } + + /// + /// Adds a new host handle to the table. + /// + /// Guest ID of the texture the handle belongs to + /// Guest ID of the sampler the handle belongs to + /// Host handle + /// Texture scale factor + public void AddBindlessHandle(int textureId, int samplerId, long handle, float scale) + { + int tableIndex = GetTableIndex(textureId, samplerId); + int blockIndex = GetBlockIndex(tableIndex); + int subIndex = GetSubIndex(textureId, samplerId); + + _blocks[tableIndex].ReferenceCount++; + + if (!_texturesOnBlocks.TryGetValue(subIndex, out List list)) + { + _texturesOnBlocks.Add(subIndex, list = new List()); + } + + list.Add(tableIndex); + + if (_handleList.EnsureCapacity((blockIndex + 1) * BlockSize)) + { + _bufferTextureForHandleList.SetStorage(_handleList.GetBufferRange()); + } + + _handleList.Write(blockIndex * BlockSize + subIndex, new HandleEntry(handle, scale)); + } + + /// + /// Removes a handle from the table. + /// + /// Guest ID of the texture the handle belongs to + /// Guest ID of the sampler the handle belongs to + public void RemoveBindlessHandle(int textureId, int samplerId) + { + int tableIndex = GetTableIndex(textureId, samplerId); + int blockIndex = _blocks[tableIndex].Index - 1; + int subIndex = GetSubIndex(textureId, samplerId); + + Debug.Assert(blockIndex >= 0); + + _handleList.Write(blockIndex * BlockSize + subIndex, new HandleEntry(0L, 0f)); + + if (_texturesOnBlocks.TryGetValue(subIndex, out List list)) + { + for (int i = 0; i < list.Count; i++) + { + PutBlockIndex(list[i]); + } + + _texturesOnBlocks.Remove(subIndex); + } + } + + /// + /// Gets a index, pointing inside the second level table, from the first level table. + /// This will dynamically allocate a new block on the second level if needed, and write + /// its index into the first level. + /// + /// Index pointing inside the first level table, where the other index is located + /// The index of a block on the second level table + private int GetBlockIndex(int tableIndex) + { + if (_blocks[tableIndex].Index != 0) + { + return _blocks[tableIndex].Index - 1; + } + + int blockIndex = _freeList.FindFirstUnset(); + + _freeList.Set(blockIndex); + + _blocks[tableIndex].Index = blockIndex + 1; + + _textureList.Write(tableIndex, blockIndex); + + return blockIndex; + } + + /// + /// Indicates that a given block was dereferenced, eventually freeing it if no longer in use. + /// + /// Index of the block index on the first level table + private void PutBlockIndex(int tableIndex) + { + if (--_blocks[tableIndex].ReferenceCount == 0) + { + _freeList.Clear(_blocks[tableIndex].Index - 1); + + _blocks[tableIndex].Index = 0; + } + } + + /// + /// Assembles a index from the low bits of the texture and sampler ID, used for the first level indexing. + /// + /// Texture ID + /// Sampler ID + /// The first level index + private static int GetTableIndex(int textureId, int samplerId) => (textureId >> 8) | ((samplerId >> 8) << 12); + + /// + /// Assembles a index from the low bits of the texture and sampler ID, used for the second level indexing. + /// + /// Texture ID + /// Sampler ID + /// The second level index + private static int GetSubIndex(int textureId, int samplerId) => (textureId & 0xff) | ((samplerId & 0xff) << 8); + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Image/BindlessManager.cs b/src/Ryujinx.Graphics.OpenGL/Image/BindlessManager.cs new file mode 100644 index 000000000..773cc1a99 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Image/BindlessManager.cs @@ -0,0 +1,166 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.OpenGL.Image +{ + /// + /// Host bindless texture manager. + /// + class BindlessManager + { + private readonly OpenGLRenderer _renderer; + private BindlessHandleManager _handleManager; + private readonly Dictionary _separateTextures; + private readonly Dictionary _separateSamplers; + + private readonly HashSet _handles = new HashSet(); + + public BindlessManager(OpenGLRenderer renderer) + { + _renderer = renderer; + _separateTextures = new(); + _separateSamplers = new(); + } + + public void AddSeparateSampler(int samplerId, ISampler sampler) + { + _separateSamplers[samplerId] = sampler; + + foreach ((int textureId, (ITexture texture, float textureScale)) in _separateTextures) + { + Add(textureId, texture, textureScale, samplerId, sampler); + } + } + + public void AddSeparateTexture(int textureId, ITexture texture, float textureScale) + { + _separateTextures[textureId] = (texture, textureScale); + + bool hasDeletedSamplers = false; + + foreach ((int samplerId, ISampler sampler) in _separateSamplers) + { + if ((sampler as Sampler).Handle == 0) + { + hasDeletedSamplers = true; + continue; + } + + Add(textureId, texture, textureScale, samplerId, sampler); + } + + if (hasDeletedSamplers) + { + List toRemove = new List(); + + foreach ((int samplerId, ISampler sampler) in _separateSamplers) + { + if ((sampler as Sampler).Handle == 0) + { + toRemove.Add(samplerId); + } + } + + foreach (int samplerId in toRemove) + { + _separateSamplers.Remove(samplerId); + } + } + } + + public void Add(int textureId, ITexture texture, float textureScale, int samplerId, ISampler sampler) + { + EnsureHandleManager(); + Register(textureId, samplerId, texture as TextureBase, sampler as Sampler, textureScale); + } + + private void Register(int textureId, int samplerId, TextureBase texture, Sampler sampler, float textureScale) + { + if (texture == null) + { + return; + } + + long bindlessHandle = sampler != null + ? GetTextureSamplerHandle(texture.Handle, sampler.Handle) + : GetTextureHandle(texture.Handle); + + if (bindlessHandle != 0 && texture.AddBindlessHandle(textureId, samplerId, this, bindlessHandle)) + { + _handles.Add(bindlessHandle); + MakeTextureHandleResident(bindlessHandle); + _handleManager.AddBindlessHandle(textureId, samplerId, bindlessHandle, textureScale); + } + } + + public void Unregister(int textureId, int samplerId, long bindlessHandle) + { + _handleManager.RemoveBindlessHandle(textureId, samplerId); + MakeTextureHandleNonResident(bindlessHandle); + _handles.Remove(bindlessHandle); + _separateTextures.Remove(textureId); + } + + private void EnsureHandleManager() + { + if (_handleManager == null) + { + _handleManager = new BindlessHandleManager(_renderer); + _handleManager.Bind(_renderer); + } + } + + private static long GetTextureHandle(int texture) + { + if (HwCapabilities.SupportsNvBindlessTexture) + { + return GL.NV.GetTextureHandle(texture); + } + else if (HwCapabilities.SupportsArbBindlessTexture) + { + return GL.Arb.GetTextureHandle(texture); + } + + return 0; + } + + private static long GetTextureSamplerHandle(int texture, int sampler) + { + if (HwCapabilities.SupportsNvBindlessTexture) + { + return GL.NV.GetTextureSamplerHandle(texture, sampler); + } + else if (HwCapabilities.SupportsArbBindlessTexture) + { + return GL.Arb.GetTextureSamplerHandle(texture, sampler); + } + + return 0; + } + + private static void MakeTextureHandleResident(long handle) + { + if (HwCapabilities.SupportsNvBindlessTexture) + { + GL.NV.MakeTextureHandleResident(handle); + } + else if (HwCapabilities.SupportsArbBindlessTexture) + { + GL.Arb.MakeTextureHandleResident(handle); + } + } + + private static void MakeTextureHandleNonResident(long handle) + { + if (HwCapabilities.SupportsNvBindlessTexture) + { + GL.NV.MakeTextureHandleNonResident(handle); + } + else if (HwCapabilities.SupportsArbBindlessTexture) + { + GL.Arb.MakeTextureHandleNonResident(handle); + } + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Image/BitMap.cs b/src/Ryujinx.Graphics.OpenGL/Image/BitMap.cs new file mode 100644 index 000000000..a3a445e24 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Image/BitMap.cs @@ -0,0 +1,189 @@ +using System.Collections.Generic; +using System.Numerics; + +namespace Ryujinx.Graphics.OpenGL.Image +{ + /// + /// Represents a list of bits. + /// + class BitMap + { + private const int IntSize = 64; + private const int IntMask = IntSize - 1; + + private readonly List _masks; + + /// + /// Creates a new instance of the bitmap. + /// + public BitMap() + { + _masks = new List(0); + } + + /// + /// Creates a new instance of the bitmap. + /// + /// Initial size (in bits) that the bitmap can hold + public BitMap(int initialCapacity) + { + int count = (initialCapacity + IntMask) / IntSize; + + _masks = new List(count); + + while (count-- > 0) + { + _masks.Add(0); + } + } + + /// + /// Sets a bit on the list to 1. + /// + /// Index of the bit + /// True if the bit value was modified by this operation, false otherwise + public bool Set(int bit) + { + EnsureCapacity(bit + 1); + + int wordIndex = bit / IntSize; + int wordBit = bit & IntMask; + + ulong wordMask = 1UL << wordBit; + + if ((_masks[wordIndex] & wordMask) != 0) + { + return false; + } + + _masks[wordIndex] |= wordMask; + + return true; + } + + /// + /// Sets a bit on the list to 0. + /// + /// Index of the bit + public void Clear(int bit) + { + EnsureCapacity(bit + 1); + + int wordIndex = bit / IntSize; + int wordBit = bit & IntMask; + + ulong wordMask = 1UL << wordBit; + + _masks[wordIndex] &= ~wordMask; + } + + /// + /// Finds the first bit on the list with a value of 0. + /// + /// Index of the bit with value 0 + public int FindFirstUnset() + { + int index = 0; + + while (index < _masks.Count && _masks[index] == ulong.MaxValue) + { + index++; + } + + if (index == _masks.Count) + { + _masks.Add(0); + } + + int bit = index * IntSize; + + bit += BitOperations.TrailingZeroCount(~_masks[index]); + + return bit; + } + + /// + /// Ensures that the array can hold a given number of bits, resizing as needed. + /// + /// Number of bits + private void EnsureCapacity(int size) + { + while (_masks.Count * IntSize < size) + { + _masks.Add(0); + } + } + + private int _iterIndex; + private ulong _iterMask; + + /// + /// Starts iterating from bit 0. + /// + public void BeginIterating() + { + _iterIndex = 0; + _iterMask = _masks.Count != 0 ? _masks[0] : 0; + } + + /// + /// Gets the next bit set to 1 on the list. + /// + /// Index of the bit, or -1 if none found + public int GetNext() + { + if (_iterIndex >= _masks.Count) + { + return -1; + } + + while (_iterMask == 0 && _iterIndex + 1 < _masks.Count) + { + _iterMask = _masks[++_iterIndex]; + } + + if (_iterMask == 0) + { + return -1; + } + + int bit = BitOperations.TrailingZeroCount(_iterMask); + + _iterMask &= ~(1UL << bit); + + return _iterIndex * IntSize + bit; + } + + /// + /// Gets the next bit set to 1 on the list, while also setting it to 0. + /// + /// Index of the bit, or -1 if none found + public int GetNextAndClear() + { + if (_iterIndex >= _masks.Count) + { + return -1; + } + + ulong mask = _masks[_iterIndex]; + + while (mask == 0 && _iterIndex + 1 < _masks.Count) + { + mask = _masks[++_iterIndex]; + } + + if (mask == 0) + { + return -1; + } + + int bit = BitOperations.TrailingZeroCount(mask); + + mask &= ~(1UL << bit); + + _masks[_iterIndex] = mask; + + return _iterIndex * IntSize + bit; + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureBase.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureBase.cs index 070a36b5e..6fa785a7e 100644 --- a/src/Ryujinx.Graphics.OpenGL/Image/TextureBase.cs +++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureBase.cs @@ -1,5 +1,6 @@ using OpenTK.Graphics.OpenGL; using Ryujinx.Graphics.GAL; +using System.Collections.Generic; namespace Ryujinx.Graphics.OpenGL.Image { @@ -15,6 +16,8 @@ namespace Ryujinx.Graphics.OpenGL.Image public Target Target => Info.Target; public Format Format => Info.Format; + private Dictionary _bindlessHandles; + public TextureBase(TextureCreateInfo info) { Info = info; @@ -35,8 +38,32 @@ namespace Ryujinx.Graphics.OpenGL.Image public static void ClearBinding(int unit) { - GL.ActiveTexture(TextureUnit.Texture0 + unit); GL.BindTextureUnit(unit, 0); } + + public bool AddBindlessHandle(int textureId, int samplerId, BindlessManager owner, long bindlessHandle) + { + var bindlessHandles = _bindlessHandles ??= new(); + return bindlessHandles.TryAdd(samplerId, (owner, bindlessHandle, textureId)); + } + + public void RevokeBindlessAccess() + { + if (_bindlessHandles == null) + { + return; + } + + foreach (var kv in _bindlessHandles) + { + int samplerId = kv.Key; + (BindlessManager owner, long bindlessHandle, int textureId) = kv.Value; + + owner.Unregister(textureId, samplerId, bindlessHandle); + } + + _bindlessHandles.Clear(); + _bindlessHandles = null; + } } } diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs index c177ae9c6..40d4764a1 100644 --- a/src/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs +++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs @@ -97,6 +97,8 @@ namespace Ryujinx.Graphics.OpenGL.Image public void Dispose() { + RevokeBindlessAccess(); + if (Handle != 0) { GL.DeleteTexture(Handle); diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs index 7f1b1c382..c17dba09c 100644 --- a/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs +++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs @@ -890,6 +890,8 @@ namespace Ryujinx.Graphics.OpenGL.Image /// public void Release() { + RevokeBindlessAccess(); + bool hadHandle = Handle != 0; if (_parent.DefaultView != this) diff --git a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs index 667ea7825..6c97274c0 100644 --- a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs +++ b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs @@ -45,7 +45,7 @@ namespace Ryujinx.Graphics.OpenGL public OpenGLRenderer() { - _pipeline = new Pipeline(); + _pipeline = new Pipeline(this); _counters = new Counters(); _window = new Window(this); _textureCopy = new TextureCopy(this); diff --git a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs index 923c85d7e..75c598943 100644 --- a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs +++ b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs @@ -26,7 +26,6 @@ namespace Ryujinx.Graphics.OpenGL private IntPtr _indexBaseOffset; private DrawElementsType _elementsType; - private PrimitiveType _primitiveType; private int _stencilFrontMask; @@ -66,9 +65,11 @@ namespace Ryujinx.Graphics.OpenGL private readonly BufferHandle[] _tfbs; private readonly BufferRange[] _tfbTargets; + private readonly BindlessManager _bindlessManager; + private ColorF _blendConstant; - internal Pipeline() + internal Pipeline(OpenGLRenderer renderer) { _drawTexture = new DrawTextureEmulation(); _rasterizerDiscard = false; @@ -82,6 +83,8 @@ namespace Ryujinx.Graphics.OpenGL _tfbs = new BufferHandle[Constants.MaxTransformFeedbackBuffers]; _tfbTargets = new BufferRange[Constants.MaxTransformFeedbackBuffers]; + + _bindlessManager = new BindlessManager(renderer); } public void Barrier() @@ -759,6 +762,21 @@ namespace Ryujinx.Graphics.OpenGL _tfEnabled = false; } + public void RegisterBindlessSampler(int samplerId, ISampler sampler) + { + _bindlessManager.AddSeparateSampler(samplerId, sampler); + } + + public void RegisterBindlessTexture(int textureId, ITexture texture, float textureScale) + { + _bindlessManager.AddSeparateTexture(textureId, texture, textureScale); + } + + public void RegisterBindlessTextureAndSampler(int textureId, ITexture texture, float textureScale, int samplerId, ISampler sampler) + { + _bindlessManager.Add(textureId, texture, textureScale, samplerId, sampler); + } + public void SetAlphaTest(bool enable, float reference, CompareOp op) { if (!enable) @@ -1302,7 +1320,6 @@ namespace Ryujinx.Graphics.OpenGL } } - public void SetTransformFeedbackBuffers(ReadOnlySpan buffers) { if (_tfEnabled) diff --git a/src/Ryujinx.Graphics.OpenGL/TypedBuffer.cs b/src/Ryujinx.Graphics.OpenGL/TypedBuffer.cs new file mode 100644 index 000000000..497d1c522 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/TypedBuffer.cs @@ -0,0 +1,79 @@ +using Ryujinx.Graphics.GAL; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.OpenGL +{ + /// + /// Represents a buffer that stores data of a given type. + /// + /// Type of the buffer data + class TypedBuffer where T : unmanaged + { + private readonly OpenGLRenderer _renderer; + private BufferHandle _buffer; + + /// + /// Size of the buffer in bytes. + /// + public int Size { get; private set; } + + /// + /// Creates a new instance of the typed buffer. + /// + /// Renderer + /// Number of data elements on the buffer + public TypedBuffer(OpenGLRenderer renderer, int count) + { + _renderer = renderer; + _buffer = renderer.CreateBuffer(Size = count * Unsafe.SizeOf(), BufferHandle.Null); + renderer.SetBufferData(_buffer, 0, new byte[Size]); + } + + /// + /// Writes data into a given buffer index. + /// + /// Index to write the data + /// Data to be written + public void Write(int index, T value) + { + _renderer.SetBufferData(_buffer, index * Unsafe.SizeOf(), MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1))); + } + + /// + /// Ensures that the buffer can hold a given number of elements. + /// + /// Number of elements + /// True if the buffer was resized and needs to be rebound, false otherwise + public bool EnsureCapacity(int count) + { + int size = count * Unsafe.SizeOf(); + + if (Size < size) + { + BufferHandle oldBuffer = _buffer; + BufferHandle newBuffer = _renderer.CreateBuffer(size, BufferHandle.Null); + _renderer.SetBufferData(newBuffer, 0, new byte[size]); + + _renderer.Pipeline.CopyBuffer(oldBuffer, newBuffer, 0, 0, Size); + _renderer.DeleteBuffer(oldBuffer); + + _buffer = newBuffer; + Size = size; + + return true; + } + + return false; + } + + /// + /// Gets a buffer range covering the whole buffer. + /// + /// The buffer range + public BufferRange GetBufferRange() + { + return new BufferRange(_buffer, 0, Size); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/BindlessTextureFlags.cs b/src/Ryujinx.Graphics.Shader/BindlessTextureFlags.cs new file mode 100644 index 000000000..005759532 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/BindlessTextureFlags.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Shader +{ + public enum BindlessTextureFlags : ushort + { + None = 0, + + BindlessConverted = 1 << 0, + BindlessNvn = 1 << 1, + BindlessFull = 1 << 2, + } +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/CodeGenParameters.cs b/src/Ryujinx.Graphics.Shader/CodeGen/CodeGenParameters.cs index f692c428b..727ecab35 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/CodeGenParameters.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/CodeGenParameters.cs @@ -11,6 +11,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen public readonly HostCapabilities HostCapabilities; public readonly ILogger Logger; public readonly TargetApi TargetApi; + public readonly BindlessTextureFlags BindlessTextureFlags; public CodeGenParameters( AttributeUsage attributeUsage, @@ -18,7 +19,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen ShaderProperties properties, HostCapabilities hostCapabilities, ILogger logger, - TargetApi targetApi) + TargetApi targetApi, + BindlessTextureFlags bindlessTextureFlags) { AttributeUsage = attributeUsage; Definitions = definitions; @@ -26,6 +28,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen HostCapabilities = hostCapabilities; Logger = logger; TargetApi = targetApi; + BindlessTextureFlags = bindlessTextureFlags; } } } diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/CodeGenContext.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/CodeGenContext.cs index cd9c71280..2bd74b9a8 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/CodeGenContext.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/CodeGenContext.cs @@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl public HostCapabilities HostCapabilities { get; } public ILogger Logger { get; } public TargetApi TargetApi { get; } + public BindlessTextureFlags BindlessTextureFlags { get; } public OperandManager OperandManager { get; } @@ -36,6 +37,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl HostCapabilities = parameters.HostCapabilities; Logger = parameters.Logger; TargetApi = parameters.TargetApi; + BindlessTextureFlags = parameters.BindlessTextureFlags; OperandManager = new OperandManager(); diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs index 500de71f6..1c3a9ddf6 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Numerics; namespace Ryujinx.Graphics.Shader.CodeGen.Glsl { @@ -15,6 +14,7 @@ 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) @@ -32,6 +32,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl context.AppendLine("#extension GL_EXT_shader_image_load_formatted : enable"); context.AppendLine("#extension GL_EXT_texture_shadow_lod : enable"); + if (context.TargetApi == TargetApi.Vulkan) + { + context.AppendLine("#extension GL_EXT_nonuniform_qualifier : enable"); + } + if (context.Definitions.Stage == ShaderStage.Compute) { context.AppendLine("#extension GL_ARB_compute_shader : enable"); @@ -189,6 +194,18 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl } } + if (context.BindlessTextureFlags != BindlessTextureFlags.None) + { + if (context.TargetApi == TargetApi.Vulkan) + { + AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/GetBindlessHandleVk.glsl"); + } + else + { + AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/GetBindlessHandle.glsl"); + } + } + if ((info.HelperFunctionsMask & HelperFunctionsMask.MultiplyHighS32) != 0) { AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighS32.glsl"); @@ -337,83 +354,84 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl } } - private static void DeclareSamplers(CodeGenContext context, IEnumerable definitions) + private static void DeclareSamplers(CodeGenContext context, IEnumerable samplers) { - int arraySize = 0; - - foreach (var definition in definitions) + foreach (var sampler in samplers) { - string indexExpr = string.Empty; - - if (definition.Type.HasFlag(SamplerType.Indexed)) - { - if (arraySize == 0) - { - arraySize = ResourceManager.SamplerArraySize; - } - else if (--arraySize != 0) - { - continue; - } - - indexExpr = $"[{NumberFormatter.FormatInt(arraySize)}]"; - } - - string samplerTypeName = definition.Type.ToGlslSamplerType(); + string samplerTypeName = sampler.Type.HasFlag(SamplerType.Separate) + ? (sampler.Type & ~SamplerType.Separate).ToGlslTextureType() + : sampler.Type.ToGlslSamplerType(); string layout = string.Empty; if (context.TargetApi == TargetApi.Vulkan) { - layout = $", set = {definition.Set}"; + layout = $"set = {sampler.Set}, "; } - context.AppendLine($"layout (binding = {definition.Binding}{layout}) uniform {samplerTypeName} {definition.Name}{indexExpr};"); + string suffix = string.Empty; + + if (sampler.ArraySize == 0) + { + suffix = "[]"; + } + else if (sampler.ArraySize != 1) + { + suffix = $"[{(uint)sampler.ArraySize}]"; + } + + if (sampler.ArraySize != 1) + { + Console.WriteLine($"found bindless {sampler.Name} {(sampler.Type & ~SamplerType.Shadow)}"); + context.OperandManager.BindlessTextures[sampler.Type & ~(SamplerType.Shadow | SamplerType.Separate)] = sampler.Name; + } + + context.AppendLine($"layout ({layout}binding = {sampler.Binding}) uniform {samplerTypeName} {sampler.Name}{suffix};"); } } - private static void DeclareImages(CodeGenContext context, IEnumerable definitions) + private static void DeclareImages(CodeGenContext context, IEnumerable images) { - int arraySize = 0; - - foreach (var definition in definitions) + foreach (var image in images) { - string indexExpr = string.Empty; + string imageTypeName = image.Type.ToGlslImageType(image.Format.GetComponentType()); - if (definition.Type.HasFlag(SamplerType.Indexed)) - { - if (arraySize == 0) - { - arraySize = ResourceManager.SamplerArraySize; - } - else if (--arraySize != 0) - { - continue; - } - - indexExpr = $"[{NumberFormatter.FormatInt(arraySize)}]"; - } - - string imageTypeName = definition.Type.ToGlslImageType(definition.Format.GetComponentType()); - - if (definition.Flags.HasFlag(TextureUsageFlags.ImageCoherent)) + if (image.Flags.HasFlag(TextureUsageFlags.ImageCoherent)) { imageTypeName = "coherent " + imageTypeName; } - string layout = definition.Format.ToGlslFormat(); - - if (!string.IsNullOrEmpty(layout)) - { - layout = ", " + layout; - } + string layout = string.Empty; if (context.TargetApi == TargetApi.Vulkan) { - layout = $", set = {definition.Set}{layout}"; + layout = $"set = {image.Set}, "; } - context.AppendLine($"layout (binding = {definition.Binding}{layout}) uniform {imageTypeName} {definition.Name}{indexExpr};"); + string format = image.Format.ToGlslFormat(); + + if (!string.IsNullOrEmpty(format)) + { + format = ", " + format; + } + + string suffix = string.Empty; + + if (image.ArraySize == 0) + { + suffix = "[]"; + } + else if (image.ArraySize != 1) + { + suffix = $"[{(uint)image.ArraySize}]"; + } + + if (image.ArraySize != 1) + { + context.OperandManager.BindlessTextures[image.Type] = image.Name; + } + + context.AppendLine($"layout ({layout}binding = {image.Binding}{format}) uniform {imageTypeName} {image.Name}{suffix};"); } } diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/GetBindlessHandle.glsl b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/GetBindlessHandle.glsl new file mode 100644 index 000000000..af67453c5 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/GetBindlessHandle.glsl @@ -0,0 +1,22 @@ +layout(binding = 0) uniform usamplerBuffer texture_list; +layout(binding = 1) uniform usamplerBuffer handle_list; + +uvec4 Helper_GetBindlessInfo(int nvHandle) +{ + int textureId = nvHandle & 0xfffff; + int samplerId = (nvHandle >> 20) & 0xfff; + int index = (textureId >> 8) | ((samplerId >> 8) << 12); + int subIdx = (textureId & 0xff) | ((samplerId & 0xff) << 8); + int hndIdx = int(texelFetch(texture_list, index).x) * 0x10000 + subIdx; + return texelFetch(handle_list, hndIdx); +} + +uvec2 Helper_GetBindlessHandle(int nvHandle) +{ + return Helper_GetBindlessInfo(nvHandle).xy; +} + +float Helper_GetBindlessScale(int nvHandle) +{ + return uintBitsToFloat(Helper_GetBindlessInfo(nvHandle).z); +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/GetBindlessHandleVk.glsl b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/GetBindlessHandleVk.glsl new file mode 100644 index 000000000..05cb2ec3f --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/GetBindlessHandleVk.glsl @@ -0,0 +1,16 @@ +uint Helper_GetBindlessTextureIndex(int nvHandle) +{ + int id = nvHandle & 0xfffff; + return bindless_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); +} + +float Helper_GetBindlessScale(int nvHandle) +{ + return bindless_scales[Helper_GetBindlessTextureIndex(nvHandle)]; +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/HelperFunctionNames.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/HelperFunctionNames.cs index 0b80ac2b6..ff0a893c3 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/HelperFunctionNames.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/HelperFunctionNames.cs @@ -2,6 +2,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl { static class HelperFunctionNames { + public static string GetBindlessHandle = "Helper_GetBindlessHandle"; + public static string GetBindlessTextureIndexVk = "Helper_GetBindlessTextureIndex"; + public static string GetBindlessSamplerIndexVk = "Helper_GetBindlessSamplerIndex"; + public static string MultiplyHighS32 = "Helper_MultiplyHighS32"; public static string MultiplyHighU32 = "Helper_MultiplyHighU32"; diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs index 2e90bd16d..9e59adb20 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs @@ -16,33 +16,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; - // TODO: Bindless texture support. For now we just return 0/do nothing. - if (isBindless) - { - switch (texOp.Inst) - { - case Instruction.ImageStore: - return "// imageStore(bindless)"; - case Instruction.ImageLoad: - AggregateType componentType = texOp.Format.GetComponentType(); - - NumberFormatter.TryFormat(0, componentType, out string imageConst); - - AggregateType outputType = texOp.GetVectorType(componentType); - - if ((outputType & AggregateType.ElementCountMask) != 0) - { - return $"{Declarations.GetVarTypeName(context, outputType, precise: false)}({imageConst})"; - } - - return imageConst; - default: - return NumberFormatter.FormatInt(0); - } - } - bool isArray = (texOp.Type & SamplerType.Array) != 0; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; var texCallBuilder = new StringBuilder(); @@ -70,21 +44,27 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions texCallBuilder.Append(texOp.Inst == Instruction.ImageLoad ? "imageLoad" : "imageStore"); } - int srcIndex = isBindless ? 1 : 0; + int srcIndex = 0; string Src(AggregateType type) { return GetSoureExpr(context, texOp.GetSource(srcIndex++), type); } - string indexExpr = null; + AggregateType type = texOp.Format.GetComponentType(); - if (isIndexed) + string bindlessHandle = null; + string imageName; + + if (isBindless) { - indexExpr = Src(AggregateType.S32); + bindlessHandle = Src(AggregateType.S32); + imageName = GetBindlessImage(context, texOp.Type, type, bindlessHandle); + } + else + { + imageName = GetImageName(context.Properties, texOp); } - - string imageName = GetImageName(context.Properties, texOp, indexExpr); texCallBuilder.Append('('); texCallBuilder.Append(imageName); @@ -117,8 +97,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions if (texOp.Inst == Instruction.ImageStore) { - AggregateType type = texOp.Format.GetComponentType(); - string[] cElems = new string[4]; for (int index = 0; index < 4; index++) @@ -150,8 +128,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions if (texOp.Inst == Instruction.ImageAtomic) { - AggregateType type = texOp.Format.GetComponentType(); - if ((texOp.Flags & TextureFlags.AtomicMask) == TextureFlags.CAS) { Append(Src(type)); // Compare value. @@ -207,18 +183,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions return NumberFormatter.FormatFloat(0); } - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; + string samplerName = GetSamplerName(context.Properties, texOp); - string indexExpr = null; - - if (isIndexed) - { - indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32); - } - - string samplerName = GetSamplerName(context.Properties, texOp, indexExpr); - - int coordsIndex = isBindless || isIndexed ? 1 : 0; + int coordsIndex = isBindless ? 1 : 0; string coordsExpr; @@ -260,7 +227,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0; bool isArray = (texOp.Type & SamplerType.Array) != 0; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0; bool isShadow = (texOp.Type & SamplerType.Shadow) != 0; @@ -286,24 +252,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions hasLodLevel = false; } - // TODO: Bindless texture support. For now we just return 0. - if (isBindless) - { - string scalarValue = NumberFormatter.FormatFloat(0); - - if (colorIsVector) - { - AggregateType outputType = texOp.GetVectorType(AggregateType.FP32); - - if ((outputType & AggregateType.ElementCountMask) != 0) - { - return $"{Declarations.GetVarTypeName(context, outputType, precise: false)}({scalarValue})"; - } - } - - return scalarValue; - } - string texCall = intCoords ? "texelFetch" : "texture"; if (isGather) @@ -328,23 +276,24 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions texCall += "Offsets"; } - int srcIndex = isBindless ? 1 : 0; + int srcIndex = 0; string Src(AggregateType type) { return GetSoureExpr(context, texOp.GetSource(srcIndex++), type); } - string indexExpr = null; + string bindlessHandle = null; - if (isIndexed) + if (isBindless) { - indexExpr = Src(AggregateType.S32); + bindlessHandle = Src(AggregateType.S32); + texCall += "(" + GetBindlessSampler(context, texOp.Type, bindlessHandle); + } + else + { + texCall += "(" + GetSamplerName(context.Properties, texOp); } - - string samplerName = GetSamplerName(context.Properties, texOp, indexExpr); - - texCall += "(" + samplerName; int coordsCount = texOp.Type.GetDimensions(); @@ -523,22 +472,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; - // TODO: Bindless texture support. For now we just return 0. - if (isBindless) - { - return NumberFormatter.FormatInt(0); - } + string bindlessHandle = isBindless ? GetSoureExpr(context, operation.GetSource(0), AggregateType.S32) : null; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - - string indexExpr = null; - - if (isIndexed) - { - indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32); - } - - string samplerName = GetSamplerName(context.Properties, texOp, indexExpr); + string samplerName = isBindless + ? GetBindlessSampler(context, texOp.Type, bindlessHandle) + : GetSamplerName(context.Properties, texOp); return $"textureSamples({samplerName})"; } @@ -549,22 +487,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; - // TODO: Bindless texture support. For now we just return 0. - if (isBindless) - { - return NumberFormatter.FormatInt(0); - } + string bindlessHandle = isBindless ? GetSoureExpr(context, operation.GetSource(0), AggregateType.S32) : null; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - - string indexExpr = null; - - if (isIndexed) - { - indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32); - } - - string samplerName = GetSamplerName(context.Properties, texOp, indexExpr); + string samplerName = isBindless + ? GetBindlessSampler(context, texOp.Type, bindlessHandle) + : GetSamplerName(context.Properties, texOp); if (texOp.Index == 3) { @@ -572,13 +499,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions } else { - context.Properties.Textures.TryGetValue(texOp.Binding, out TextureDefinition definition); + context.Properties.Textures.TryGetValue(SetBindingPair.Unpack(texOp.Binding), out TextureDefinition definition); bool hasLod = !definition.Type.HasFlag(SamplerType.Multisample) && (definition.Type & SamplerType.Mask) != SamplerType.TextureBuffer; string texCall; if (hasLod) { - int lodSrcIndex = isBindless || isIndexed ? 1 : 0; + int lodSrcIndex = isBindless ? 1 : 0; IAstNode lod = operation.GetSource(lodSrcIndex); string lodExpr = GetSoureExpr(context, lod, GetSrcVarType(operation.Inst, lodSrcIndex)); @@ -619,8 +546,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions int binding = bindingIndex.Value; BufferDefinition buffer = storageKind == StorageKind.ConstantBuffer - ? context.Properties.ConstantBuffers[binding] - : context.Properties.StorageBuffers[binding]; + ? context.Properties.ConstantBuffers[SetBindingPair.Unpack(binding)] + : context.Properties.StorageBuffers[SetBindingPair.Unpack(binding)]; if (operation.GetSource(srcIndex++) is not AstOperand fieldIndex || fieldIndex.Type != OperandType.Constant) { @@ -748,28 +675,52 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions return varName; } - private static string GetSamplerName(ShaderProperties resourceDefinitions, AstTextureOperation texOp, string indexExpr) + private static string GetBindlessSampler(CodeGenContext context, SamplerType type, string bindlessHandle) { - string name = resourceDefinitions.Textures[texOp.Binding].Name; + string samplerType = type.ToGlslSamplerType(); - if (texOp.Type.HasFlag(SamplerType.Indexed)) + if (context.TargetApi == TargetApi.Vulkan) { - name = $"{name}[{indexExpr}]"; - } + string textureIndex = $"{HelperFunctionNames.GetBindlessTextureIndexVk}({bindlessHandle})"; + string samplerIndex = $"{HelperFunctionNames.GetBindlessSamplerIndexVk}({bindlessHandle})"; - return name; + string bindlessTextureArrayName = context.OperandManager.BindlessTextures[type & ~SamplerType.Shadow]; + string bindlessSamplerArrayName = context.OperandManager.BindlessTextures[SamplerType.None]; + + return $"{samplerType}({bindlessTextureArrayName}[{textureIndex}], {bindlessSamplerArrayName}[{samplerIndex}])"; + } + else + { + return $"{samplerType}({HelperFunctionNames.GetBindlessHandle}({bindlessHandle}))"; + } } - private static string GetImageName(ShaderProperties resourceDefinitions, AstTextureOperation texOp, string indexExpr) + private static string GetBindlessImage(CodeGenContext context, SamplerType type, AggregateType componentType, string bindlessHandle) { - string name = resourceDefinitions.Images[texOp.Binding].Name; + string imageType = type.ToGlslImageType(componentType); - if (texOp.Type.HasFlag(SamplerType.Indexed)) + if (context.TargetApi == TargetApi.Vulkan) { - name = $"{name}[{indexExpr}]"; - } + string textureIndex = $"{HelperFunctionNames.GetBindlessTextureIndexVk}({bindlessHandle})"; - return name; + string bindlessImageArrayName = context.OperandManager.BindlessImages[type]; + + return $"{bindlessImageArrayName}[{textureIndex}]"; + } + else + { + return $"{imageType}({HelperFunctionNames.GetBindlessHandle}({bindlessHandle}))"; + } + } + + private static string GetSamplerName(ShaderProperties resourceDefinitions, AstTextureOperation texOp) + { + return resourceDefinitions.Textures[SetBindingPair.Unpack(texOp.Binding)].Name; + } + + private static string GetImageName(ShaderProperties resourceDefinitions, AstTextureOperation texOp) + { + return resourceDefinitions.Images[SetBindingPair.Unpack(texOp.Binding)].Name; } private static string GetMask(int index) diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs index 53ecc4531..8d1ee906b 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs @@ -13,9 +13,15 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl { private readonly Dictionary _locals; + public Dictionary BindlessTextures { get; } + public Dictionary BindlessImages { get; } + public OperandManager() { - _locals = new Dictionary(); + _locals = new(); + + BindlessTextures = new(); + BindlessImages = new(); } public string DeclareLocal(AstOperand operand) @@ -68,8 +74,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl } BufferDefinition buffer = operation.StorageKind == StorageKind.ConstantBuffer - ? context.Properties.ConstantBuffers[bindingIndex.Value] - : context.Properties.StorageBuffers[bindingIndex.Value]; + ? context.Properties.ConstantBuffers[SetBindingPair.Unpack(bindingIndex.Value)] + : context.Properties.StorageBuffers[SetBindingPair.Unpack(bindingIndex.Value)]; StructureField field = buffer.Type.Fields[fieldIndex.Value]; return field.Type & AggregateType.ElementTypeMask; diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs index 53267c60b..8ff6a6dec 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs @@ -29,15 +29,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv public Dictionary ConstantBuffers { get; } = new(); public Dictionary StorageBuffers { get; } = new(); - public Dictionary LocalMemories { get; } = new(); public Dictionary SharedMemories { get; } = new(); - public Dictionary SamplersTypes { get; } = new(); public Dictionary Samplers { get; } = new(); public Dictionary Images { get; } = new(); - public Dictionary Inputs { get; } = new(); + public Dictionary BindlessTextures { get; } = new(); + public Dictionary BindlessImages { get; } = new(); public Dictionary Outputs { get; } = new(); public Dictionary InputsPerPatch { get; } = new(); public Dictionary OutputsPerPatch { get; } = new(); diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs index 45933a21b..be95e4531 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs @@ -43,12 +43,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv public static void DeclareAll(CodeGenContext context, StructuredProgramInfo info) { - DeclareConstantBuffers(context, context.Properties.ConstantBuffers.Values); - DeclareStorageBuffers(context, context.Properties.StorageBuffers.Values); + DeclareConstantBuffers(context, context.Properties.ConstantBuffers); + DeclareStorageBuffers(context, context.Properties.StorageBuffers); DeclareMemories(context, context.Properties.LocalMemories, context.LocalMemories, StorageClass.Private); DeclareMemories(context, context.Properties.SharedMemories, context.SharedMemories, StorageClass.Workgroup); - DeclareSamplers(context, context.Properties.Textures.Values); - DeclareImages(context, context.Properties.Images.Values); + DeclareSamplers(context, context.Properties.Textures); + DeclareImages(context, context.Properties.Images); DeclareInputsAndOutputs(context, info); } @@ -58,7 +58,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv Dictionary dict, StorageClass storage) { - foreach ((int id, MemoryDefinition memory) in memories) + foreach ((int id, var memory) in memories) { var pointerType = context.TypePointer(storage, context.GetType(memory.Type, memory.ArrayLength)); var variable = context.Variable(pointerType, storage); @@ -69,21 +69,21 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv } } - private static void DeclareConstantBuffers(CodeGenContext context, IEnumerable buffers) + private static void DeclareConstantBuffers(CodeGenContext context, IReadOnlyDictionary buffers) { DeclareBuffers(context, buffers, isBuffer: false); } - private static void DeclareStorageBuffers(CodeGenContext context, IEnumerable buffers) + private static void DeclareStorageBuffers(CodeGenContext context, IReadOnlyDictionary buffers) { DeclareBuffers(context, buffers, isBuffer: true); } - private static void DeclareBuffers(CodeGenContext context, IEnumerable buffers, bool isBuffer) + private static void DeclareBuffers(CodeGenContext context, IReadOnlyDictionary buffers, bool isBuffer) { HashSet decoratedTypes = new(); - foreach (BufferDefinition buffer in buffers) + foreach ((SetBindingPair sbPair, var buffer) in buffers) { int setIndex = context.TargetApi == TargetApi.Vulkan ? buffer.Set : 0; int alignment = buffer.Layout == BufferLayout.Std140 ? 16 : 4; @@ -145,46 +145,71 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv if (isBuffer) { - context.StorageBuffers.Add(buffer.Binding, variable); + context.StorageBuffers.Add(sbPair.Pack(), variable); } else { - context.ConstantBuffers.Add(buffer.Binding, variable); + context.ConstantBuffers.Add(sbPair.Pack(), variable); } } } - private static void DeclareSamplers(CodeGenContext context, IEnumerable samplers) + private static void DeclareSamplers(CodeGenContext context, IReadOnlyDictionary samplers) { - foreach (var sampler in samplers) + foreach ((SetBindingPair sbPair, var sampler) in samplers) { int setIndex = context.TargetApi == TargetApi.Vulkan ? sampler.Set : 0; - var dim = (sampler.Type & SamplerType.Mask) switch + SpvInstruction imageType; + SpvInstruction sampledImageType; + + if (sampler.Type != SamplerType.None) { - SamplerType.Texture1D => Dim.Dim1D, - SamplerType.Texture2D => Dim.Dim2D, - SamplerType.Texture3D => Dim.Dim3D, - SamplerType.TextureCube => Dim.Cube, - SamplerType.TextureBuffer => Dim.Buffer, - _ => throw new InvalidOperationException($"Invalid sampler type \"{sampler.Type & SamplerType.Mask}\"."), - }; + var dim = GetDim(sampler.Type); - var imageType = context.TypeImage( - context.TypeFP32(), - dim, - sampler.Type.HasFlag(SamplerType.Shadow), - sampler.Type.HasFlag(SamplerType.Array), - sampler.Type.HasFlag(SamplerType.Multisample), - 1, - ImageFormat.Unknown); + imageType = context.TypeImage( + context.TypeFP32(), + dim, + sampler.Type.HasFlag(SamplerType.Shadow), + sampler.Type.HasFlag(SamplerType.Array), + sampler.Type.HasFlag(SamplerType.Multisample), + 1, + ImageFormat.Unknown); - var sampledImageType = context.TypeSampledImage(imageType); - var sampledImagePointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageType); - var sampledImageVariable = context.Variable(sampledImagePointerType, StorageClass.UniformConstant); + sampledImageType = context.TypeSampledImage(imageType); + } + else + { + imageType = sampledImageType = context.TypeSampler(); + } - context.Samplers.Add(sampler.Binding, (imageType, sampledImageType, sampledImageVariable)); - context.SamplersTypes.Add(sampler.Binding, sampler.Type); + var imageTypeForSampler = sampler.Type.HasFlag(SamplerType.Separate) ? imageType : sampledImageType; + var sampledImagePointerType = context.TypePointer(StorageClass.UniformConstant, imageTypeForSampler); + + SpvInstruction sampledImageArrayPointerType = sampledImagePointerType; + + if (sampler.ArraySize == 0) + { + var sampledImageArrayType = context.TypeRuntimeArray(imageTypeForSampler); + sampledImageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageArrayType); + } + else if (sampler.ArraySize != 1) + { + var sampledImageArrayType = context.TypeArray(imageTypeForSampler, context.Constant(context.TypeU32(), sampler.ArraySize)); + sampledImageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageArrayType); + } + + var sampledImageVariable = context.Variable(sampledImageArrayPointerType, StorageClass.UniformConstant); + + if (sampler.ArraySize != 1) + { + context.BindlessTextures[sampler.Type & ~(SamplerType.Shadow | SamplerType.Separate)] = (imageType, sampledImageType, sampledImagePointerType, sampledImageVariable); + } + else + { + context.Samplers.Add(sbPair.Pack(), (imageType, sampledImageType, sampledImageVariable)); + context.SamplersTypes.Add(sbPair.Pack(), sampler.Type); + } context.Name(sampledImageVariable, sampler.Name); context.Decorate(sampledImageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex); @@ -193,9 +218,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv } } - private static void DeclareImages(CodeGenContext context, IEnumerable images) + private static void DeclareImages(CodeGenContext context, IReadOnlyDictionary images) { - foreach (var image in images) + foreach ((SetBindingPair sbPair, var image) in images) { int setIndex = context.TargetApi == TargetApi.Vulkan ? image.Set : 0; @@ -211,9 +236,30 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv GetImageFormat(image.Format)); var imagePointerType = context.TypePointer(StorageClass.UniformConstant, imageType); - var imageVariable = context.Variable(imagePointerType, StorageClass.UniformConstant); - context.Images.Add(image.Binding, (imageType, imageVariable)); + SpvInstruction imageArrayPointerType = imagePointerType; + + if (image.ArraySize == 0) + { + var imageArrayType = context.TypeRuntimeArray(imageType); + imageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, imageArrayType); + } + else if (image.ArraySize != 1) + { + var imageArrayType = context.TypeArray(imageType, context.Constant(context.TypeU32(), image.ArraySize)); + imageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, imageArrayType); + } + + var imageVariable = context.Variable(imageArrayPointerType, StorageClass.UniformConstant); + + if (image.ArraySize != 1) + { + context.BindlessImages[image.Type] = (imageType, imagePointerType, imageVariable); + } + else + { + context.Images.Add(sbPair.Pack(), (imageType, imageVariable)); + } context.Name(imageVariable, image.Name); context.Decorate(imageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex); diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs index 50a73ab83..a6f49c692 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs @@ -595,31 +595,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv var componentType = texOp.Format.GetComponentType(); - // TODO: Bindless texture support. For now we just return 0/do nothing. - if (isBindless) - { - return new OperationResult(componentType, componentType switch - { - AggregateType.S32 => context.Constant(context.TypeS32(), 0), - AggregateType.U32 => context.Constant(context.TypeU32(), 0u), - _ => context.Constant(context.TypeFP32(), 0f), - }); - } - bool isArray = (texOp.Type & SamplerType.Array) != 0; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - int srcIndex = isBindless ? 1 : 0; + int srcIndex = 0; SpvInstruction Src(AggregateType type) { return context.Get(type, texOp.GetSource(srcIndex++)); } - if (isIndexed) - { - Src(AggregateType.S32); - } + SpvInstruction bindlessHandle = isBindless ? Src(AggregateType.S32) : null; int coordsCount = texOp.Type.GetDimensions(); @@ -646,9 +631,19 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv SpvInstruction value = Src(componentType); - (var imageType, var imageVariable) = context.Images[texOp.Binding]; + SpvInstruction imageVariable; - context.Load(imageType, imageVariable); + if (isBindless) + { + (_, var bindlessImagePointerType, var bindlessImageVariable) = context.BindlessImages[texOp.Type]; + + var imageIndex = GenerateBindlessTextureHandleToIndex(context, bindlessHandle); + imageVariable = context.AccessChain(bindlessImagePointerType, bindlessImageVariable, imageIndex); + } + else + { + (_, imageVariable) = context.Images[texOp.Binding]; + } SpvInstruction resultType = context.GetType(componentType); SpvInstruction imagePointerType = context.TypePointer(StorageClass.Image, resultType); @@ -687,26 +682,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv var componentType = texOp.Format.GetComponentType(); - // TODO: Bindless texture support. For now we just return 0/do nothing. - if (isBindless) - { - return GetZeroOperationResult(context, texOp, componentType, isVector: true); - } - bool isArray = (texOp.Type & SamplerType.Array) != 0; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - int srcIndex = isBindless ? 1 : 0; + int srcIndex = 0; SpvInstruction Src(AggregateType type) { return context.Get(type, texOp.GetSource(srcIndex++)); } - if (isIndexed) - { - Src(AggregateType.S32); - } + SpvInstruction bindlessHandle = isBindless ? Src(AggregateType.S32) : null; int coordsCount = texOp.Type.GetDimensions(); @@ -731,9 +716,27 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv pCoords = Src(AggregateType.S32); } - (var imageType, var imageVariable) = context.Images[texOp.Binding]; + SpvInstruction bindlessIndex; + SpvInstruction image; + + if (isBindless) + { + (var imageType, var imagePointerType, var imageVariable) = context.BindlessImages[texOp.Type]; + + var imageIndex = GenerateBindlessTextureHandleToIndex(context, bindlessHandle); + var imagePointer = context.AccessChain(imagePointerType, imageVariable, imageIndex); + + bindlessIndex = imageIndex; + image = context.Load(imageType, imagePointer); + } + else + { + (var imageType, var imageVariable) = context.Images[texOp.Binding]; + + bindlessIndex = null; + image = context.Load(imageType, imageVariable); + } - var image = context.Load(imageType, imageVariable); var imageComponentType = context.GetType(componentType); var swizzledResultType = texOp.GetVectorType(componentType); @@ -749,26 +752,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; - // TODO: Bindless texture support. For now we just return 0/do nothing. - if (isBindless) - { - return OperationResult.Invalid; - } - bool isArray = (texOp.Type & SamplerType.Array) != 0; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - int srcIndex = isBindless ? 1 : 0; + int srcIndex = 0; SpvInstruction Src(AggregateType type) { return context.Get(type, texOp.GetSource(srcIndex++)); } - if (isIndexed) - { - Src(AggregateType.S32); - } + SpvInstruction bindlessHandle = isBindless ? Src(AggregateType.S32) : null; int coordsCount = texOp.Type.GetDimensions(); @@ -818,9 +811,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv var texel = context.CompositeConstruct(context.TypeVector(context.GetType(componentType), ComponentsCount), cElems); - (var imageType, var imageVariable) = context.Images[texOp.Binding]; + SpvInstruction image; - var image = context.Load(imageType, imageVariable); + if (isBindless) + { + (var imageType, var imagePointerType, var imageVariable) = context.BindlessImages[texOp.Type]; + + var imageIndex = GenerateBindlessTextureHandleToIndex(context, bindlessHandle); + var imagePointer = context.AccessChain(imagePointerType, imageVariable, imageIndex); + + image = context.Load(imageType, imagePointer); + } + else + { + (var imageType, var imageVariable) = context.Images[texOp.Binding]; + + image = context.Load(imageType, imageVariable); + } context.ImageWrite(image, pCoords, texel, ImageOperandsMask.MaskNone); @@ -856,14 +863,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - - // TODO: Bindless texture support. For now we just return 0. - if (isBindless) - { - return new OperationResult(AggregateType.S32, context.Constant(context.TypeS32(), 0)); - } - int srcIndex = 0; SpvInstruction Src(AggregateType type) @@ -871,10 +870,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv return context.Get(type, texOp.GetSource(srcIndex++)); } - if (isIndexed) - { - Src(AggregateType.S32); - } + SpvInstruction bindlessHandle = isBindless ? Src(AggregateType.S32) : null; int pCount = texOp.Type.GetDimensions(); @@ -897,9 +893,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv pCoords = Src(AggregateType.FP32); } - (_, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding]; + SpvInstruction image; - var image = context.Load(sampledImageType, sampledImageVariable); + if (isBindless) + { + (var imageType, _, var imagePointerType, var imageVariable) = context.BindlessTextures[texOp.Type & ~SamplerType.Shadow]; + + var imageIndex = GenerateBindlessTextureHandleToIndex(context, bindlessHandle); + var imagePointer = context.AccessChain(imagePointerType, imageVariable, imageIndex); + + image = context.Load(imageType, imagePointer); + } + else + { + (_, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding]; + + image = context.Load(sampledImageType, sampledImageVariable); + } var resultType = context.TypeVector(context.TypeFP32(), 2); var packed = context.ImageQueryLod(resultType, image, pCoords); @@ -1192,29 +1202,19 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0; bool isArray = (texOp.Type & SamplerType.Array) != 0; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0; bool isShadow = (texOp.Type & SamplerType.Shadow) != 0; bool colorIsVector = isGather || !isShadow; - // TODO: Bindless texture support. For now we just return 0. - if (isBindless) - { - return GetZeroOperationResult(context, texOp, AggregateType.FP32, colorIsVector); - } - - int srcIndex = isBindless ? 1 : 0; + int srcIndex = 0; SpvInstruction Src(AggregateType type) { return context.Get(type, texOp.GetSource(srcIndex++)); } - if (isIndexed) - { - Src(AggregateType.S32); - } + SpvInstruction bindlessHandle = isBindless ? Src(AggregateType.S32) : null; int coordsCount = texOp.Type.GetDimensions(); @@ -1262,6 +1262,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv } SpvInstruction pCoords = AssemblePVector(pCount); + SpvInstruction bindlessIndex = isBindless ? GenerateBindlessTextureHandleToIndex(context, bindlessHandle) : null; SpvInstruction AssembleDerivativesVector(int count) { @@ -1421,9 +1422,33 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv var resultType = colorIsVector ? context.TypeVector(context.TypeFP32(), 4) : context.TypeFP32(); - (var imageType, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding]; + SpvInstruction imageType; + SpvInstruction image; - var image = context.Load(sampledImageType, sampledImageVariable); + if (isBindless) + { + (imageType, var sampledImageType, var imagePointerType, var imageVariable) = context.BindlessTextures[texOp.Type & ~SamplerType.Shadow]; + + var imageIndex = GenerateBindlessTextureHandleToIndex(context, bindlessHandle); + var imagePointer = context.AccessChain(imagePointerType, imageVariable, imageIndex); + + image = context.Load(imageType, imagePointer); + + (_, var samplerType, var samplerPointerType, var bindlessSamplerArray) = context.BindlessTextures[SamplerType.None]; + + var samplerIndex = GenerateBindlessSamplerHandleToIndex(context, bindlessHandle); + var samplerPointer = context.AccessChain(samplerPointerType, bindlessSamplerArray, samplerIndex); + + var sampler = context.Load(samplerType, samplerPointer); + + image = context.SampledImage(sampledImageType, image, sampler); + } + else + { + (imageType, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding]; + + image = context.Load(sampledImageType, sampledImageVariable); + } if (intCoords) { @@ -1493,13 +1518,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv return new OperationResult(AggregateType.S32, context.Constant(context.TypeS32(), 0)); } - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - - if (isIndexed) - { - context.GetS32(texOp.GetSource(0)); - } - (var imageType, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding]; var image = context.Load(sampledImageType, sampledImageVariable); @@ -1516,22 +1534,30 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; - // TODO: Bindless texture support. For now we just return 0. + SpvInstruction bindlessIndex; + SpvInstruction imageType; + SpvInstruction image; + if (isBindless) { - return new OperationResult(AggregateType.S32, context.Constant(context.TypeS32(), 0)); + SpvInstruction bindlessHandle = context.GetS32(operation.GetSource(0)); + + (imageType, _, var imagePointerType, var imageVariable) = context.BindlessTextures[texOp.Type & ~SamplerType.Shadow]; + + var imageIndex = GenerateBindlessTextureHandleToIndex(context, bindlessHandle); + var imagePointer = context.AccessChain(imagePointerType, imageVariable, imageIndex); + + bindlessIndex = imageIndex; + image = context.Load(imageType, imagePointer); } - - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - - if (isIndexed) + else { - context.GetS32(texOp.GetSource(0)); + (imageType, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding]; + + bindlessIndex = null; + image = context.Load(sampledImageType, sampledImageVariable); } - (var imageType, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding]; - - var image = context.Load(sampledImageType, sampledImageVariable); image = context.Image(imageType, image); if (texOp.Index == 3) @@ -1540,7 +1566,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv } else { - var type = context.SamplersTypes[texOp.Binding]; + var type = isBindless ? texOp.Type : context.SamplersTypes[texOp.Binding]; bool hasLod = !type.HasFlag(SamplerType.Multisample) && type != SamplerType.TextureBuffer; int dimensions = (type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : type.GetDimensions(); @@ -1556,7 +1582,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv if (hasLod) { - int lodSrcIndex = isBindless || isIndexed ? 1 : 0; + int lodSrcIndex = isBindless ? 1 : 0; var lod = context.GetS32(operation.GetSource(lodSrcIndex)); result = context.ImageQuerySizeLod(resultType, image, lod); } @@ -1638,6 +1664,51 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv return new OperationResult(AggregateType.Bool, result); } + private static SpvInstruction GenerateBindlessTextureHandleToIndex(CodeGenContext context, SpvInstruction bindlessHandle) + { + var bindlessTable = context.ConstantBuffers[SetBindingPair.Pack(Constants.BindlessTextureSetIndex, Constants.BindlessTableBinding)]; + var id = context.BitwiseAnd(context.TypeS32(), bindlessHandle, context.Constant(context.TypeS32(), 0xfffff)); + var tableIndex = context.ShiftRightArithmetic(context.TypeS32(), id, context.Constant(context.TypeS32(), 8)); + + var pointerUint = context.TypePointer(StorageClass.Uniform, context.TypeU32()); + var baseIndex = context.AccessChain( + pointerUint, + bindlessTable, + context.Constant(context.TypeS32(), 0), + tableIndex, + context.Constant(context.TypeU32(), 0)); + + baseIndex = context.Load(context.TypeU32(), baseIndex); + + var idLow = context.BitwiseAnd(context.TypeS32(), id, context.Constant(context.TypeS32(), 0xff)); + var index = context.BitwiseOr(context.TypeU32(), baseIndex, idLow); + + return index; + } + + private static SpvInstruction GenerateBindlessSamplerHandleToIndex(CodeGenContext context, SpvInstruction bindlessHandle) + { + var bindlessTable = context.ConstantBuffers[SetBindingPair.Pack(Constants.BindlessTextureSetIndex, Constants.BindlessTableBinding)]; + var idHigh = context.ShiftRightArithmetic(context.TypeS32(), bindlessHandle, context.Constant(context.TypeS32(), 20)); + var id = context.BitwiseAnd(context.TypeS32(), idHigh, context.Constant(context.TypeS32(), 0xfff)); + var tableIndex = context.ShiftRightArithmetic(context.TypeS32(), id, context.Constant(context.TypeS32(), 8)); + + var pointerUint = context.TypePointer(StorageClass.Uniform, context.TypeU32()); + var baseIndex = context.AccessChain( + pointerUint, + bindlessTable, + context.Constant(context.TypeS32(), 0), + tableIndex, + context.Constant(context.TypeU32(), 1)); + + baseIndex = context.Load(context.TypeU32(), baseIndex); + + var idLow = context.BitwiseAnd(context.TypeS32(), id, context.Constant(context.TypeS32(), 0xff)); + var index = context.BitwiseOr(context.TypeU32(), baseIndex, idLow); + + return index; + } + private static OperationResult GenerateCompare( CodeGenContext context, AstOperation operation, @@ -1746,8 +1817,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv } BufferDefinition buffer = storageKind == StorageKind.ConstantBuffer - ? context.Properties.ConstantBuffers[bindingIndex.Value] - : context.Properties.StorageBuffers[bindingIndex.Value]; + ? context.Properties.ConstantBuffers[SetBindingPair.Unpack(bindingIndex.Value)] + : context.Properties.StorageBuffers[SetBindingPair.Unpack(bindingIndex.Value)]; StructureField field = buffer.Type.Fields[fieldIndex.Value]; storageClass = StorageClass.Uniform; diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs index a1e9054f6..4f739c08f 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs @@ -105,6 +105,17 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv context.AddCapability(Capability.ShaderViewportMaskNV); } + if (parameters.BindlessTextureFlags != BindlessTextureFlags.None) + { + context.AddExtension("SPV_EXT_descriptor_indexing"); + context.AddCapability(Capability.Sampled1D); + context.AddCapability(Capability.Image1D); + context.AddCapability(Capability.SampledCubeArray); + context.AddCapability(Capability.ImageCubeArray); + context.AddCapability(Capability.StorageImageMultisample); + context.AddCapability(Capability.RuntimeDescriptorArray); + } + if ((info.HelperFunctionsMask & NeedsInvocationIdMask) != 0) { info.IoDefinitions.Add(new IoDefinition(StorageKind.Input, IoVariable.SubgroupLaneId)); diff --git a/src/Ryujinx.Graphics.Shader/Constants.cs b/src/Ryujinx.Graphics.Shader/Constants.cs index 6317369f0..34cf8295f 100644 --- a/src/Ryujinx.Graphics.Shader/Constants.cs +++ b/src/Ryujinx.Graphics.Shader/Constants.cs @@ -10,5 +10,16 @@ namespace Ryujinx.Graphics.Shader public const int NvnBaseVertexByteOffset = 0x640; public const int NvnBaseInstanceByteOffset = 0x644; public const int NvnDrawIndexByteOffset = 0x648; + + public const int VkConstantBufferSetIndex = 0; + public const int VkStorageBufferSetIndex = 1; + public const int VkTextureSetIndex = 2; + public const int VkImageSetIndex = 3; + + // Bindless emulation. + + public const int BindlessTextureSetIndex = 4; + public const int BindlessTableBinding = 0; + public const int BindlessScalesBinding = 1; } } diff --git a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs index 29a5435e3..2df2b1442 100644 --- a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs +++ b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs @@ -404,6 +404,15 @@ namespace Ryujinx.Graphics.Shader return SamplerType.Texture2D; } + /// + /// Queries the number of the constant buffer where the texture handles are located. + /// + /// Constant buffer where the texture handles are located + int QueryTextureBufferIndex() + { + return 2; // NVN default. + } + /// /// Queries texture coordinate normalization information. /// diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs index e5695ebc2..4b1d173e3 100644 --- a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs +++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs @@ -161,5 +161,13 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation inst &= Instruction.Mask; return inst == Instruction.Lod || inst == Instruction.TextureQuerySamples || inst == Instruction.TextureQuerySize; } + + public static bool IsImage(this Instruction inst) + { + inst &= Instruction.Mask; + return inst == Instruction.ImageLoad || + inst == Instruction.ImageStore || + inst == Instruction.ImageAtomic; + } } } diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs index f5396a884..8eafecbd9 100644 --- a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs +++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs @@ -144,6 +144,26 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation } } + public void PrependSources(Operand[] operands) + { + int endIndex = operands.Length; + + Array.Resize(ref _sources, endIndex + _sources.Length); + Array.Copy(_sources, 0, _sources, endIndex, _sources.Length - endIndex); + + for (int index = 0; index < operands.Length; index++) + { + Operand source = operands[index]; + + if (source.Type == OperandType.LocalVariable) + { + source.UseOps.Add(this); + } + + _sources[index] = source; + } + } + public void AppendSources(Operand[] operands) { int startIndex = _sources.Length; diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs index fa5550a64..bf5be8cf6 100644 --- a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs +++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs @@ -26,19 +26,11 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation Binding = binding; } - public void TurnIntoIndexed(int binding) - { - Type |= SamplerType.Indexed; - Flags &= ~TextureFlags.Bindless; - Binding = binding; - } - public void SetBinding(int binding) { if ((Flags & TextureFlags.Bindless) != 0) { Flags &= ~TextureFlags.Bindless; - RemoveSource(0); } @@ -49,5 +41,14 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation { Flags |= TextureFlags.LodLevel; } + + public void TurnIntoBindless(Operand handle) + { + if ((Flags & TextureFlags.Bindless) == 0) + { + Flags |= TextureFlags.Bindless; + PrependSources(new Operand[] { handle }); + } + } } } diff --git a/src/Ryujinx.Graphics.Shader/ResourceReservationCounts.cs b/src/Ryujinx.Graphics.Shader/ResourceReservationCounts.cs index c0bae8eab..a42ff8228 100644 --- a/src/Ryujinx.Graphics.Shader/ResourceReservationCounts.cs +++ b/src/Ryujinx.Graphics.Shader/ResourceReservationCounts.cs @@ -9,9 +9,9 @@ namespace Ryujinx.Graphics.Shader public readonly int ReservedTextures { get; } public readonly int ReservedImages { get; } - public ResourceReservationCounts(bool isTransformFeedbackEmulated, bool vertexAsCompute) + public ResourceReservationCounts(TargetApi targetApi, bool isTransformFeedbackEmulated, bool vertexAsCompute) { - ResourceReservations reservations = new(isTransformFeedbackEmulated, vertexAsCompute); + ResourceReservations reservations = new(targetApi, isTransformFeedbackEmulated, vertexAsCompute); ReservedConstantBuffers = reservations.ReservedConstantBuffers; ReservedStorageBuffers = reservations.ReservedStorageBuffers; diff --git a/src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj b/src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj index 8ccf5348f..23882013b 100644 --- a/src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj +++ b/src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj @@ -10,6 +10,8 @@ + + diff --git a/src/Ryujinx.Graphics.Shader/SamplerType.cs b/src/Ryujinx.Graphics.Shader/SamplerType.cs index 85e97368f..6794afd88 100644 --- a/src/Ryujinx.Graphics.Shader/SamplerType.cs +++ b/src/Ryujinx.Graphics.Shader/SamplerType.cs @@ -16,9 +16,9 @@ namespace Ryujinx.Graphics.Shader Mask = 0xff, Array = 1 << 8, - Indexed = 1 << 9, - Multisample = 1 << 10, - Shadow = 1 << 11, + Multisample = 1 << 9, + Shadow = 1 << 10, + Separate = 1 << 11, } static class SamplerTypeExtensions @@ -40,6 +40,7 @@ namespace Ryujinx.Graphics.Shader { string typeName = (type & SamplerType.Mask) switch { + SamplerType.None => "sampler", SamplerType.Texture1D => "sampler1D", SamplerType.TextureBuffer => "samplerBuffer", SamplerType.Texture2D => "sampler2D", @@ -66,6 +67,31 @@ namespace Ryujinx.Graphics.Shader return typeName; } + public static string ToGlslTextureType(this SamplerType type) + { + string typeName = (type & SamplerType.Mask) switch + { + SamplerType.Texture1D => "texture1D", + SamplerType.TextureBuffer => "textureBuffer", + SamplerType.Texture2D => "texture2D", + SamplerType.Texture3D => "texture3D", + SamplerType.TextureCube => "textureCube", + _ => throw new ArgumentException($"Invalid texture type \"{type}\"."), + }; + + if ((type & SamplerType.Multisample) != 0) + { + typeName += "MS"; + } + + if ((type & SamplerType.Array) != 0) + { + typeName += "Array"; + } + + return typeName; + } + public static string ToGlslImageType(this SamplerType type, AggregateType componentType) { string typeName = (type & SamplerType.Mask) switch diff --git a/src/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs b/src/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs index 22823ac38..38465032d 100644 --- a/src/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs +++ b/src/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs @@ -11,6 +11,7 @@ namespace Ryujinx.Graphics.Shader public ReadOnlyCollection Images { get; } public ShaderStage Stage { get; } + public BindlessTextureFlags BindlessTextureFlags { get; } public int GeometryVerticesPerPrimitive { get; } public int GeometryMaxOutputVertices { get; } public int ThreadsPerInputPrimitive { get; } @@ -27,6 +28,7 @@ namespace Ryujinx.Graphics.Shader TextureDescriptor[] textures, TextureDescriptor[] images, ShaderStage stage, + BindlessTextureFlags bindlessTextureFlags, int geometryVerticesPerPrimitive, int geometryMaxOutputVertices, int threadsPerInputPrimitive, @@ -43,6 +45,7 @@ namespace Ryujinx.Graphics.Shader Images = Array.AsReadOnly(images); Stage = stage; + BindlessTextureFlags = bindlessTextureFlags; GeometryVerticesPerPrimitive = geometryVerticesPerPrimitive; GeometryMaxOutputVertices = geometryMaxOutputVertices; ThreadsPerInputPrimitive = threadsPerInputPrimitive; diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/SetBindingPair.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/SetBindingPair.cs new file mode 100644 index 000000000..c87109310 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/SetBindingPair.cs @@ -0,0 +1,46 @@ +using System; + +namespace Ryujinx.Graphics.Shader +{ + readonly struct SetBindingPair : IEquatable + { + public int Set { get; } + public int Binding { get; } + + public SetBindingPair(int set, int binding) + { + Set = set; + Binding = binding; + } + + public override bool Equals(object obj) + { + return base.Equals(obj); + } + + public bool Equals(SetBindingPair other) + { + return other.Set == Set && other.Binding == Binding; + } + + public override int GetHashCode() + { + return ((uint)Set | (ulong)(uint)Binding << 32).GetHashCode(); + } + + public int Pack() + { + return Pack(Set, Binding); + } + + public static int Pack(int set, int binding) + { + return (ushort)set | (checked((ushort)binding) << 16); + } + + public static SetBindingPair Unpack(int packed) + { + return new((ushort)packed, (ushort)((uint)packed >> 16)); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/ShaderProperties.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/ShaderProperties.cs index 8c12c2aaf..62013ac98 100644 --- a/src/Ryujinx.Graphics.Shader/StructuredIr/ShaderProperties.cs +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/ShaderProperties.cs @@ -4,48 +4,48 @@ namespace Ryujinx.Graphics.Shader.StructuredIr { class ShaderProperties { - private readonly Dictionary _constantBuffers; - private readonly Dictionary _storageBuffers; - private readonly Dictionary _textures; - private readonly Dictionary _images; + private readonly Dictionary _constantBuffers; + private readonly Dictionary _storageBuffers; + private readonly Dictionary _textures; + private readonly Dictionary _images; private readonly Dictionary _localMemories; private readonly Dictionary _sharedMemories; - public IReadOnlyDictionary ConstantBuffers => _constantBuffers; - public IReadOnlyDictionary StorageBuffers => _storageBuffers; - public IReadOnlyDictionary Textures => _textures; - public IReadOnlyDictionary Images => _images; + public IReadOnlyDictionary ConstantBuffers => _constantBuffers; + public IReadOnlyDictionary StorageBuffers => _storageBuffers; + public IReadOnlyDictionary Textures => _textures; + public IReadOnlyDictionary Images => _images; public IReadOnlyDictionary LocalMemories => _localMemories; public IReadOnlyDictionary SharedMemories => _sharedMemories; public ShaderProperties() { - _constantBuffers = new Dictionary(); - _storageBuffers = new Dictionary(); - _textures = new Dictionary(); - _images = new Dictionary(); + _constantBuffers = new Dictionary(); + _storageBuffers = new Dictionary(); + _textures = new Dictionary(); + _images = new Dictionary(); _localMemories = new Dictionary(); _sharedMemories = new Dictionary(); } public void AddOrUpdateConstantBuffer(BufferDefinition definition) { - _constantBuffers[definition.Binding] = definition; + _constantBuffers[new(definition.Set, definition.Binding)] = definition; } public void AddOrUpdateStorageBuffer(BufferDefinition definition) { - _storageBuffers[definition.Binding] = definition; + _storageBuffers[new(definition.Set, definition.Binding)] = definition; } public void AddOrUpdateTexture(TextureDefinition definition) { - _textures[definition.Binding] = definition; + _textures[new(definition.Set, definition.Binding)] = definition; } public void AddOrUpdateImage(TextureDefinition definition) { - _images[definition.Binding] = definition; + _images[new(definition.Set, definition.Binding)] = definition; } public int AddLocalMemory(MemoryDefinition definition) diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/TextureDefinition.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/TextureDefinition.cs index e45c82854..200adb0b8 100644 --- a/src/Ryujinx.Graphics.Shader/StructuredIr/TextureDefinition.cs +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/TextureDefinition.cs @@ -8,8 +8,9 @@ namespace Ryujinx.Graphics.Shader public SamplerType Type { get; } public TextureFormat Format { get; } public TextureUsageFlags Flags { get; } + public int ArraySize { get; } - public TextureDefinition(int set, int binding, string name, SamplerType type, TextureFormat format, TextureUsageFlags flags) + public TextureDefinition(int set, int binding, string name, SamplerType type, TextureFormat format, TextureUsageFlags flags, int arraySize = 1) { Set = set; Binding = binding; @@ -17,11 +18,12 @@ namespace Ryujinx.Graphics.Shader Type = type; Format = format; Flags = flags; + ArraySize = arraySize; } public TextureDefinition SetFlag(TextureUsageFlags flag) { - return new TextureDefinition(Set, Binding, Name, Type, Format, Flags | flag); + return new TextureDefinition(Set, Binding, Name, Type, Format, Flags | flag, ArraySize); } } } diff --git a/src/Ryujinx.Graphics.Shader/TextureHandle.cs b/src/Ryujinx.Graphics.Shader/TextureHandle.cs index fc9ab2d67..ca120b889 100644 --- a/src/Ryujinx.Graphics.Shader/TextureHandle.cs +++ b/src/Ryujinx.Graphics.Shader/TextureHandle.cs @@ -1,3 +1,4 @@ +using Ryujinx.Graphics.Shader.Translation; using System; using System.Runtime.CompilerServices; @@ -13,6 +14,11 @@ namespace Ryujinx.Graphics.Shader public static class TextureHandle { + // Maximum is actually 32 for OpenGL, but we reserve 2 textures for bindless emulation. + private const int MaxTexturesPerStageGl = 30; + private const int MaxTexturesPerStageVk = 64; + public const int NvnTextureBufferIndex = 2; + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int PackSlots(int cbufSlot0, int cbufSlot1) { @@ -120,5 +126,11 @@ namespace Ryujinx.Graphics.Shader return handle; } + + public static int GetMaxTexturesPerStage(TargetApi api) + { + // TODO: Query that value from the backend since those limits are not really fixed per API. + return api == TargetApi.Vulkan ? MaxTexturesPerStageVk : MaxTexturesPerStageGl; + } } } diff --git a/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionManager.cs b/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionManager.cs index ef2f8759d..3ed56cc4d 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionManager.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionManager.cs @@ -1,6 +1,7 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation; using System; using System.Collections.Generic; +using System.Reflection.Metadata; using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; namespace Ryujinx.Graphics.Shader.Translation @@ -77,7 +78,9 @@ namespace Ryujinx.Graphics.Shader.Translation HelperFunctionName.ConvertDoubleToFloat => GenerateConvertDoubleToFloatFunction(), HelperFunctionName.ConvertFloatToDouble => GenerateConvertFloatToDoubleFunction(), HelperFunctionName.TexelFetchScale => GenerateTexelFetchScaleFunction(), + HelperFunctionName.TexelFetchScaleBindless => GenerateTexelFetchScaleBindlessFunction(), HelperFunctionName.TextureSizeUnscale => GenerateTextureSizeUnscaleFunction(), + HelperFunctionName.TextureSizeUnscaleBindless => GenerateTextureSizeUnscaleBindlessFunction(), _ => throw new ArgumentException($"Invalid function name {functionName}"), }; } @@ -412,6 +415,29 @@ namespace Ryujinx.Graphics.Shader.Translation return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "TexelFetchScale", true, inArgumentsCount, 0); } + private Function GenerateTexelFetchScaleBindlessFunction() + { + EmitterContext context = new(); + + Operand input = Argument(0); + Operand nvHandle = Argument(1); + + Operand scale = GetBindlessScale(context, nvHandle); + + Operand scaleIsOne = context.FPCompareEqual(scale, ConstF(1f)); + Operand lblScaleNotOne = Label(); + + context.BranchIfFalse(lblScaleNotOne, scaleIsOne); + context.Return(input); + context.MarkLabel(lblScaleNotOne); + + Operand inputScaled2 = context.FPMultiply(context.IConvertS32ToFP32(input), scale); + + context.Return(context.FP32ConvertToS32(inputScaled2)); + + return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "TexelFetchScaleBindless", true, 2, 0); + } + private Function GenerateTextureSizeUnscaleFunction() { EmitterContext context = new(); @@ -436,6 +462,29 @@ namespace Ryujinx.Graphics.Shader.Translation return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "TextureSizeUnscale", true, 2, 0); } + private Function GenerateTextureSizeUnscaleBindlessFunction() + { + EmitterContext context = new(); + + Operand input = Argument(0); + Operand nvHandle = Argument(1); + + Operand scale = GetBindlessScale(context, nvHandle); + + Operand scaleIsOne = context.FPCompareEqual(scale, ConstF(1f)); + Operand lblScaleNotOne = Label(); + + context.BranchIfFalse(lblScaleNotOne, scaleIsOne); + context.Return(input); + context.MarkLabel(lblScaleNotOne); + + Operand inputUnscaled = context.FPDivide(context.IConvertS32ToFP32(input), scale); + + context.Return(context.FP32ConvertToS32(inputUnscaled)); + + return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "TextureSizeUnscaleBindless", true, 2, 0); + } + private Operand GetScaleIndex(EmitterContext context, Operand index) { switch (_stage) @@ -448,6 +497,19 @@ namespace Ryujinx.Graphics.Shader.Translation } } + private Operand GetBindlessScale(EmitterContext context, Operand nvHandle) + { + int bindlessTableBinding = SetBindingPair.Pack(Constants.BindlessTextureSetIndex, Constants.BindlessTableBinding); + int bindlessScalesBinding = SetBindingPair.Pack(Constants.BindlessTextureSetIndex, Constants.BindlessScalesBinding); + + Operand id = context.BitwiseAnd(nvHandle, Const(0xfffff)); + Operand tableIndex = context.ShiftRightU32(id, Const(8)); + Operand scaleIndex = context.Load(StorageKind.ConstantBuffer, bindlessTableBinding, Const(0), tableIndex, Const(0)); + Operand scale = context.Load(StorageKind.ConstantBuffer, bindlessScalesBinding, Const(0), scaleIndex); + + return scale; + } + public static Operand GetBitOffset(EmitterContext context, Operand offset) { return context.ShiftLeft(context.BitwiseAnd(offset, Const(3)), Const(3)); diff --git a/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionName.cs b/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionName.cs index 09b17729d..9abdd037e 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionName.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionName.cs @@ -15,6 +15,8 @@ namespace Ryujinx.Graphics.Shader.Translation ShuffleUp, ShuffleXor, TexelFetchScale, + TexelFetchScaleBindless, TextureSizeUnscale, + TextureSizeUnscaleBindless, } } diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToIndexed.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToIndexed.cs deleted file mode 100644 index 2bd31fe1b..000000000 --- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToIndexed.cs +++ /dev/null @@ -1,118 +0,0 @@ -using Ryujinx.Graphics.Shader.IntermediateRepresentation; -using System.Collections.Generic; - -using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; - -namespace Ryujinx.Graphics.Shader.Translation.Optimizations -{ - static class BindlessToIndexed - { - private const int NvnTextureBufferIndex = 2; - - public static void RunPass(BasicBlock block, ResourceManager resourceManager) - { - // We can turn a bindless texture access into a indexed access, - // as long the following conditions are true: - // - The handle is loaded using a LDC instruction. - // - The handle is loaded from the constant buffer with the handles (CB2 for NVN). - // - The load has a constant offset. - // The base offset of the array of handles on the constant buffer is the constant offset. - for (LinkedListNode node = block.Operations.First; node != null; node = node.Next) - { - if (node.Value is not TextureOperation texOp) - { - continue; - } - - if ((texOp.Flags & TextureFlags.Bindless) == 0) - { - continue; - } - - if (texOp.GetSource(0).AsgOp is not Operation handleAsgOp) - { - continue; - } - - if (handleAsgOp.Inst != Instruction.Load || - handleAsgOp.StorageKind != StorageKind.ConstantBuffer || - handleAsgOp.SourcesCount != 4) - { - continue; - } - - Operand ldcSrc0 = handleAsgOp.GetSource(0); - - if (ldcSrc0.Type != OperandType.Constant || - !resourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int src0CbufSlot) || - src0CbufSlot != NvnTextureBufferIndex) - { - continue; - } - - Operand ldcSrc1 = handleAsgOp.GetSource(1); - - // We expect field index 0 to be accessed. - if (ldcSrc1.Type != OperandType.Constant || ldcSrc1.Value != 0) - { - continue; - } - - Operand ldcSrc2 = handleAsgOp.GetSource(2); - - // FIXME: This is missing some checks, for example, a check to ensure that the shift value is 2. - // Might be not worth fixing since if that doesn't kick in, the result will be no texture - // to access anyway which is also wrong. - // Plus this whole transform is fundamentally flawed as-is since we have no way to know the array size. - // Eventually, this should be entirely removed in favor of a implementation that supports true bindless - // texture access. - if (ldcSrc2.AsgOp is not Operation shrOp || shrOp.Inst != Instruction.ShiftRightU32) - { - continue; - } - - if (shrOp.GetSource(0).AsgOp is not Operation shrOp2 || shrOp2.Inst != Instruction.ShiftRightU32) - { - continue; - } - - if (shrOp2.GetSource(0).AsgOp is not Operation addOp || addOp.Inst != Instruction.Add) - { - continue; - } - - Operand addSrc1 = addOp.GetSource(1); - - if (addSrc1.Type != OperandType.Constant) - { - continue; - } - - TurnIntoIndexed(resourceManager, texOp, addSrc1.Value / 4); - - Operand index = Local(); - - Operand source = addOp.GetSource(0); - - Operation shrBy3 = new(Instruction.ShiftRightU32, index, source, Const(3)); - - block.Operations.AddBefore(node, shrBy3); - - texOp.SetSource(0, index); - } - } - - private static void TurnIntoIndexed(ResourceManager resourceManager, TextureOperation texOp, int handle) - { - int binding = resourceManager.GetTextureOrImageBinding( - texOp.Inst, - texOp.Type | SamplerType.Indexed, - texOp.Format, - texOp.Flags & ~TextureFlags.Bindless, - NvnTextureBufferIndex, - handle); - - texOp.TurnIntoIndexed(binding); - } - } -} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs index 17427a5f9..13a518a8e 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs @@ -16,11 +16,34 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations GlobalToStorage.RunPass(context.Hfm, context.Blocks, context.ResourceManager, context.GpuAccessor, context.TargetLanguage); bool hostSupportsShaderFloat64 = context.GpuAccessor.QueryHostSupportsShaderFloat64(); + int textureBufferIndex = context.GpuAccessor.QueryTextureBufferIndex(); // Those passes are looking for specific patterns and only needs to run once. for (int blkIndex = 0; blkIndex < context.Blocks.Length; blkIndex++) { - BindlessToIndexed.RunPass(context.Blocks[blkIndex], context.ResourceManager); + if (textureBufferIndex == TextureHandle.NvnTextureBufferIndex) + { + BasicBlock block = context.Blocks[blkIndex]; + + for (LinkedListNode node = block.Operations.First; node != null; node = node.Next) + { + for (int index = 0; index < node.Value.SourcesCount; index++) + { + Operand src = node.Value.GetSource(index); + + // The shader accessing constant buffer 2 is an indication that + // the bindless access is for separate texture/sampler combinations. + // Bindless elimination should be able to take care of that, but if it doesn't, + // we still don't want to use full bindless for those cases + if (src.Type == OperandType.ConstantBuffer && src.GetCbufSlot() == textureBufferIndex) + { + context.BindlessTexturesAllowed = false; + break; + } + } + } + } + BindlessElimination.RunPass(context.Blocks[blkIndex], context.ResourceManager, context.GpuAccessor); // FragmentCoord only exists on fragment shaders, so we don't need to check other stages. diff --git a/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs b/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs index 83332711f..a27fae2cc 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs @@ -14,9 +14,6 @@ namespace Ryujinx.Graphics.Shader.Translation private const int DefaultLocalMemorySize = 128; private const int DefaultSharedMemorySize = 4096; - // TODO: Non-hardcoded array size. - public const int SamplerArraySize = 4; - private static readonly string[] _stagePrefixes = new string[] { "cp", "vp", "tcp", "tep", "gp", "fp" }; private readonly IGpuAccessor _gpuAccessor; @@ -28,11 +25,10 @@ namespace Ryujinx.Graphics.Shader.Translation private uint _sbSlotWritten; private readonly Dictionary _sbSlots; - private readonly Dictionary _sbSlotsReverse; private readonly HashSet _usedConstantBufferBindings; - private readonly record struct TextureInfo(int CbufSlot, int Handle, bool Indexed, TextureFormat Format); + private readonly record struct TextureInfo(int CbufSlot, int Handle, TextureFormat Format); private struct TextureMeta { @@ -73,7 +69,6 @@ namespace Ryujinx.Graphics.Shader.Translation _sbSlotToBindingMap.AsSpan().Fill(-1); _sbSlots = new(); - _sbSlotsReverse = new(); _usedConstantBufferBindings = new(); @@ -147,6 +142,68 @@ namespace Ryujinx.Graphics.Shader.Translation return Properties.AddLocalMemory(new MemoryDefinition(name, type, arrayLength)); } + public void EnsureBindlessBinding(TargetApi targetApi, SamplerType samplerType, bool isImage) + { + if (targetApi == TargetApi.Vulkan) + { + Properties.AddOrUpdateConstantBuffer(new BufferDefinition( + BufferLayout.Std140, + Constants.BindlessTextureSetIndex, + Constants.BindlessTableBinding, + "bindless_table", + new StructureType(new[] { new StructureField(AggregateType.Array | AggregateType.Vector2 | AggregateType.U32, "table", 0x1000) }))); + + Properties.AddOrUpdateStorageBuffer(new BufferDefinition( + BufferLayout.Std430, + Constants.BindlessTextureSetIndex, + Constants.BindlessScalesBinding, + "bindless_scales", + new StructureType(new[] { new StructureField(AggregateType.Array | AggregateType.FP32, "scales", 0) }))); + + if (isImage) + { + string name = $"bindless_{samplerType.ToGlslImageType(AggregateType.FP32)}"; + + if (samplerType == SamplerType.TextureBuffer) + { + AddBindlessDefinition(8, 0, name, SamplerType.TextureBuffer); + } + else + { + AddBindlessDefinition(7, 0, name, samplerType); + } + } + else + { + string name = $"bindless_{(samplerType & ~SamplerType.Shadow).ToGlslSamplerType()}"; + + if (samplerType == SamplerType.TextureBuffer) + { + AddBindlessSeparateDefinition(5, 0, name, SamplerType.TextureBuffer); + } + else + { + AddBindlessSeparateDefinition(4, 2, name, samplerType); + } + + // Sampler + AddBindlessDefinition(6, 0, "bindless_samplers", SamplerType.None); + } + } + } + + private void AddBindlessDefinition(int set, int binding, string name, SamplerType samplerType) + { + TextureDefinition definition = new(set, binding, name, samplerType, TextureFormat.Unknown, TextureUsageFlags.None, 0); + Properties.AddOrUpdateTexture(definition); + } + + private void AddBindlessSeparateDefinition(int set, int binding, string name, SamplerType samplerType) + { + samplerType = (samplerType & ~SamplerType.Shadow) | SamplerType.Separate; + AddBindlessDefinition(set, binding, name, samplerType); + } + public int GetConstantBufferBinding(int slot) { int binding = _cbSlotToBindingMap[slot]; @@ -158,7 +215,7 @@ namespace Ryujinx.Graphics.Shader.Translation AddNewConstantBuffer(binding, $"{_stagePrefix}_c{slotNumber}"); } - return binding; + return SetBindingPair.Pack(Constants.VkConstantBufferSetIndex, binding); } public bool TryGetStorageBufferBinding(int sbCbSlot, int sbCbOffset, bool write, out int binding) @@ -166,6 +223,7 @@ namespace Ryujinx.Graphics.Shader.Translation if (!TryGetSbSlot((byte)sbCbSlot, (ushort)sbCbOffset, out int slot)) { binding = 0; + return false; } @@ -179,6 +237,8 @@ namespace Ryujinx.Graphics.Shader.Translation AddNewStorageBuffer(binding, $"{_stagePrefix}_s{slotNumber}"); } + binding = SetBindingPair.Pack(Constants.VkStorageBufferSetIndex, binding); + if (write) { _sbSlotWritten |= 1u << slot; @@ -201,7 +261,6 @@ namespace Ryujinx.Graphics.Shader.Translation } _sbSlots.Add(key, slot); - _sbSlotsReverse.Add(slot, key); } return true; @@ -211,7 +270,7 @@ namespace Ryujinx.Graphics.Shader.Translation { for (slot = 0; slot < _cbSlotToBindingMap.Length; slot++) { - if (_cbSlotToBindingMap[slot] == binding) + if (SetBindingPair.Pack(Constants.VkConstantBufferSetIndex, _cbSlotToBindingMap[slot]) == binding) { return true; } @@ -245,7 +304,7 @@ namespace Ryujinx.Graphics.Shader.Translation _gpuAccessor.RegisterTexture(handle, cbufSlot); - return binding; + return SetBindingPair.Pack(isImage ? Constants.VkImageSetIndex : Constants.VkTextureSetIndex, binding); } private int GetTextureOrImageBinding( @@ -260,7 +319,6 @@ namespace Ryujinx.Graphics.Shader.Translation bool coherent) { var dimensions = type.GetDimensions(); - var isIndexed = type.HasFlag(SamplerType.Indexed); var dict = isImage ? _usedImages : _usedTextures; var usageFlags = TextureUsageFlags.None; @@ -269,7 +327,7 @@ namespace Ryujinx.Graphics.Shader.Translation { usageFlags |= TextureUsageFlags.NeedsScaleValue; - var canScale = _stage.SupportsRenderScale() && !isIndexed && !write && dimensions == 2; + var canScale = _stage.SupportsRenderScale() && !write && dimensions == 2; if (!canScale) { @@ -289,76 +347,65 @@ namespace Ryujinx.Graphics.Shader.Translation usageFlags |= TextureUsageFlags.ImageCoherent; } - int arraySize = isIndexed ? SamplerArraySize : 1; - int firstBinding = -1; - - for (int layer = 0; layer < arraySize; layer++) + var info = new TextureInfo(cbufSlot, handle, format); + var meta = new TextureMeta() { - var info = new TextureInfo(cbufSlot, handle + layer * 2, isIndexed, format); - var meta = new TextureMeta() - { - AccurateType = accurateType, - Type = type, - UsageFlags = usageFlags, - }; + AccurateType = accurateType, + Type = type, + UsageFlags = usageFlags + }; - int binding; + int binding; - if (dict.TryGetValue(info, out var existingMeta)) - { - dict[info] = MergeTextureMeta(meta, existingMeta); - binding = existingMeta.Binding; - } - else - { - bool isBuffer = (type & SamplerType.Mask) == SamplerType.TextureBuffer; + if (dict.TryGetValue(info, out var existingMeta)) + { + dict[info] = MergeTextureMeta(meta, existingMeta); + binding = existingMeta.Binding; + } + else + { + bool isBuffer = (type & SamplerType.Mask) == SamplerType.TextureBuffer; - binding = isImage - ? _gpuAccessor.QueryBindingImage(dict.Count, isBuffer) - : _gpuAccessor.QueryBindingTexture(dict.Count, isBuffer); + binding = isImage + ? _gpuAccessor.QueryBindingImage(dict.Count, isBuffer) + : _gpuAccessor.QueryBindingTexture(dict.Count, isBuffer); - meta.Binding = binding; + meta.Binding = binding; - dict.Add(info, meta); - } - - string nameSuffix; - - if (isImage) - { - nameSuffix = cbufSlot < 0 - ? $"i_tcb_{handle:X}_{format.ToGlslFormat()}" - : $"i_cb{cbufSlot}_{handle:X}_{format.ToGlslFormat()}"; - } - else - { - nameSuffix = cbufSlot < 0 ? $"t_tcb_{handle:X}" : $"t_cb{cbufSlot}_{handle:X}"; - } - - var definition = new TextureDefinition( - isImage ? 3 : 2, - binding, - $"{_stagePrefix}_{nameSuffix}", - meta.Type, - info.Format, - meta.UsageFlags); - - if (isImage) - { - Properties.AddOrUpdateImage(definition); - } - else - { - Properties.AddOrUpdateTexture(definition); - } - - if (layer == 0) - { - firstBinding = binding; - } + dict.Add(info, meta); } - return firstBinding; + string nameSuffix; + + if (isImage) + { + nameSuffix = cbufSlot < 0 + ? $"i_tcb_{handle:X}_{format.ToGlslFormat()}" + : $"i_cb{cbufSlot}_{handle:X}_{format.ToGlslFormat()}"; + } + else + { + nameSuffix = cbufSlot < 0 ? $"t_tcb_{handle:X}" : $"t_cb{cbufSlot}_{handle:X}"; + } + + var definition = new TextureDefinition( + isImage ? 3 : 2, + binding, + $"{_stagePrefix}_{nameSuffix}", + meta.Type, + info.Format, + meta.UsageFlags); + + if (isImage) + { + Properties.AddOrUpdateImage(definition); + } + else + { + Properties.AddOrUpdateTexture(definition); + } + + return binding; } private static TextureMeta MergeTextureMeta(TextureMeta meta, TextureMeta existingMeta) @@ -385,7 +432,7 @@ namespace Ryujinx.Graphics.Shader.Translation foreach ((TextureInfo info, TextureMeta meta) in _usedTextures) { - if (meta.Binding == binding) + if (SetBindingPair.Pack(Constants.VkTextureSetIndex, meta.Binding) == binding) { selectedInfo = info; selectedMeta = meta; @@ -399,8 +446,7 @@ namespace Ryujinx.Graphics.Shader.Translation selectedMeta.UsageFlags |= TextureUsageFlags.NeedsScaleValue; var dimensions = type.GetDimensions(); - var isIndexed = type.HasFlag(SamplerType.Indexed); - var canScale = _stage.SupportsRenderScale() && !isIndexed && dimensions == 2; + var canScale = _stage.SupportsRenderScale() && dimensions == 2; if (!canScale) { @@ -428,7 +474,7 @@ namespace Ryujinx.Graphics.Shader.Translation { int binding = _cbSlotToBindingMap[slot]; - if (binding >= 0 && _usedConstantBufferBindings.Contains(binding)) + if (binding >= 0 && _usedConstantBufferBindings.Contains(SetBindingPair.Pack(Constants.VkConstantBufferSetIndex, binding))) { descriptors[descriptorIndex++] = new BufferDescriptor(binding, slot); } @@ -502,7 +548,7 @@ namespace Ryujinx.Graphics.Shader.Translation { foreach ((TextureInfo info, TextureMeta meta) in _usedTextures) { - if (meta.Binding == binding) + if (SetBindingPair.Pack(Constants.VkTextureSetIndex, meta.Binding) == binding) { cbufSlot = info.CbufSlot; handle = info.Handle; @@ -516,19 +562,19 @@ namespace Ryujinx.Graphics.Shader.Translation return false; } - private static int FindDescriptorIndex(TextureDescriptor[] array, int binding) + private static int FindDescriptorIndex(TextureDescriptor[] array, int setIndex, int binding) { - return Array.FindIndex(array, x => x.Binding == binding); + return Array.FindIndex(array, x => SetBindingPair.Pack(setIndex, x.Binding) == binding); } public int FindTextureDescriptorIndex(int binding) { - return FindDescriptorIndex(GetTextureDescriptors(), binding); + return FindDescriptorIndex(GetTextureDescriptors(), Constants.VkTextureSetIndex, binding); } public int FindImageDescriptorIndex(int binding) { - return FindDescriptorIndex(GetImageDescriptors(), binding); + return FindDescriptorIndex(GetImageDescriptors(), Constants.VkImageSetIndex, binding); } private void AddNewConstantBuffer(int binding, string name) diff --git a/src/Ryujinx.Graphics.Shader/Translation/ResourceReservations.cs b/src/Ryujinx.Graphics.Shader/Translation/ResourceReservations.cs index d559f6699..5c211e38c 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/ResourceReservations.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/ResourceReservations.cs @@ -32,12 +32,12 @@ namespace Ryujinx.Graphics.Shader.Translation private readonly Dictionary _offsets; internal IReadOnlyDictionary Offsets => _offsets; - internal ResourceReservations(bool isTransformFeedbackEmulated, bool vertexAsCompute) + internal ResourceReservations(TargetApi targetApi, bool isTransformFeedbackEmulated, bool vertexAsCompute) { // All stages reserves the first constant buffer binding for the support buffer. ReservedConstantBuffers = 1; ReservedStorageBuffers = 0; - ReservedTextures = 0; + ReservedTextures = targetApi == TargetApi.OpenGL ? 2 : 0; // Reserve 2 texture bindings on OpenGL for bindless emulation. ReservedImages = 0; if (isTransformFeedbackEmulated) @@ -71,10 +71,11 @@ namespace Ryujinx.Graphics.Shader.Translation internal ResourceReservations( IGpuAccessor gpuAccessor, + TargetApi targetApi, bool isTransformFeedbackEmulated, bool vertexAsCompute, IoUsage? vacInput, - IoUsage vacOutput) : this(isTransformFeedbackEmulated, vertexAsCompute) + IoUsage vacOutput) : this(targetApi, isTransformFeedbackEmulated, vertexAsCompute) { if (vertexAsCompute) { diff --git a/src/Ryujinx.Graphics.Shader/Translation/TransformContext.cs b/src/Ryujinx.Graphics.Shader/Translation/TransformContext.cs index 87ebb8e7c..794b975b0 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/TransformContext.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/TransformContext.cs @@ -9,9 +9,12 @@ namespace Ryujinx.Graphics.Shader.Translation public readonly ShaderDefinitions Definitions; public readonly ResourceManager ResourceManager; public readonly IGpuAccessor GpuAccessor; + public readonly TargetApi TargetApi; public readonly TargetLanguage TargetLanguage; public readonly ShaderStage Stage; public readonly ref FeatureFlags UsedFeatures; + public readonly ref BindlessTextureFlags BindlessTextureFlags; + public readonly ref bool BindlessTexturesAllowed; public TransformContext( HelperFunctionManager hfm, @@ -19,18 +22,24 @@ namespace Ryujinx.Graphics.Shader.Translation ShaderDefinitions definitions, ResourceManager resourceManager, IGpuAccessor gpuAccessor, + TargetApi targetApi, TargetLanguage targetLanguage, ShaderStage stage, - ref FeatureFlags usedFeatures) + ref FeatureFlags usedFeatures, + ref BindlessTextureFlags bindlessTextureFlags, + ref bool bindlessTexturesAllowed) { Hfm = hfm; Blocks = blocks; Definitions = definitions; ResourceManager = resourceManager; GpuAccessor = gpuAccessor; + TargetApi = targetApi; TargetLanguage = targetLanguage; Stage = stage; UsedFeatures = ref usedFeatures; + BindlessTextureFlags = ref bindlessTextureFlags; + BindlessTexturesAllowed = ref bindlessTexturesAllowed; } } } diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs index 495ea8a94..94c20cded 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs @@ -16,12 +16,26 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms { if (node.Value is TextureOperation texOp) { - node = InsertTexelFetchScale(context.Hfm, node, context.ResourceManager, context.Stage); - node = InsertTextureSizeUnscale(context.Hfm, node, context.ResourceManager, context.Stage); + LinkedListNode prevNode = node; + node = TurnIntoBindlessIfExceeding( + node, + context.ResourceManager, + context.TargetApi, + ref context.BindlessTextureFlags, + context.BindlessTexturesAllowed, + context.GpuAccessor.QueryTextureBufferIndex()); + + if (prevNode != node) + { + return node; + } + + node = InsertTexelFetchScale(context.Hfm, node, context.ResourceManager, context.Stage, context.TargetApi); + node = InsertTextureSizeUnscale(context.Hfm, node, context.ResourceManager, context.Stage, context.TargetApi); if (texOp.Inst == Instruction.TextureSample) { - node = InsertCoordNormalization(context.Hfm, node, context.ResourceManager, context.GpuAccessor, context.Stage); + node = InsertCoordNormalization(context.Hfm, node, context.ResourceManager, context.GpuAccessor, context.Stage, context.TargetApi); node = InsertCoordGatherBias(node, context.ResourceManager, context.GpuAccessor); node = InsertConstOffsets(node, context.GpuAccessor, context.Stage); @@ -39,31 +53,41 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms HelperFunctionManager hfm, LinkedListNode node, ResourceManager resourceManager, - ShaderStage stage) + ShaderStage stage, + TargetApi targetApi) { TextureOperation texOp = (TextureOperation)node.Value; bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; int coordsCount = texOp.Type.GetDimensions(); - int coordsIndex = isBindless || isIndexed ? 1 : 0; + int coordsIndex = isBindless ? 1 : 0; bool isImage = IsImageInstructionWithScale(texOp.Inst); if ((texOp.Inst == Instruction.TextureSample || isImage) && (intCoords || isImage) && - !isBindless && - !isIndexed && + (!isBindless || targetApi == TargetApi.Vulkan) && // TODO: OpenGL support. stage.SupportsRenderScale() && TypeSupportsScale(texOp.Type)) { - int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TexelFetchScale); - int samplerIndex = isImage - ? resourceManager.GetTextureDescriptors().Length + resourceManager.FindImageDescriptorIndex(texOp.Binding) - : resourceManager.FindTextureDescriptorIndex(texOp.Binding); + int functionId; + Operand samplerIndex; + + if (isBindless) + { + functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TexelFetchScaleBindless); + samplerIndex = texOp.GetSource(0); + } + else + { + functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TexelFetchScale); + samplerIndex = isImage + ? Const(resourceManager.GetTextureDescriptors().Length + resourceManager.FindImageDescriptorIndex(texOp.Binding)) + : Const(resourceManager.FindTextureDescriptorIndex(texOp.Binding)); + } for (int index = 0; index < coordsCount; index++) { @@ -72,11 +96,11 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms if (stage == ShaderStage.Fragment) { - callArgs = new Operand[] { Const(functionId), texOp.GetSource(coordsIndex + index), Const(samplerIndex), Const(index) }; + callArgs = new Operand[] { Const(functionId), texOp.GetSource(coordsIndex + index), samplerIndex, Const(index) }; } else { - callArgs = new Operand[] { Const(functionId), texOp.GetSource(coordsIndex + index), Const(samplerIndex) }; + callArgs = new Operand[] { Const(functionId), texOp.GetSource(coordsIndex + index), samplerIndex }; } node.List.AddBefore(node, new Operation(Instruction.Call, 0, scaledCoord, callArgs)); @@ -92,22 +116,32 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms HelperFunctionManager hfm, LinkedListNode node, ResourceManager resourceManager, - ShaderStage stage) + ShaderStage stage, + TargetApi targetApi) { TextureOperation texOp = (TextureOperation)node.Value; bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; if (texOp.Inst == Instruction.TextureQuerySize && texOp.Index < 2 && - !isBindless && - !isIndexed && + (!isBindless || targetApi == TargetApi.Vulkan) && // TODO: OpenGL support. stage.SupportsRenderScale() && TypeSupportsScale(texOp.Type)) { - int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TextureSizeUnscale); - int samplerIndex = resourceManager.FindTextureDescriptorIndex(texOp.Binding); + int functionId; + Operand samplerIndex; + + if (isBindless) + { + functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TextureSizeUnscaleBindless); + samplerIndex = texOp.GetSource(0); + } + else + { + functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TextureSizeUnscale); + samplerIndex = Const(resourceManager.FindTextureDescriptorIndex(texOp.Binding)); + } for (int index = texOp.DestsCount - 1; index >= 0; index--) { @@ -128,7 +162,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms } } - Operand[] callArgs = new Operand[] { Const(functionId), dest, Const(samplerIndex) }; + Operand[] callArgs = new Operand[] { Const(functionId), dest, samplerIndex }; node.List.AddAfter(node, new Operation(Instruction.Call, 0, unscaledSize, callArgs)); } @@ -142,7 +176,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms LinkedListNode node, ResourceManager resourceManager, IGpuAccessor gpuAccessor, - ShaderStage stage) + ShaderStage stage, + TargetApi targetApi) { // Emulate non-normalized coordinates by normalizing the coordinates on the shader. // Without normalization, the coordinates are expected to the in the [0, W or H] range, @@ -167,10 +202,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms return node; } - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - int coordsCount = texOp.Type.GetDimensions(); - int coordsIndex = isBindless || isIndexed ? 1 : 0; + int coordsIndex = isBindless ? 1 : 0; int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount; @@ -180,7 +213,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms Operand[] texSizeSources; - if (isBindless || isIndexed) + if (isBindless) { texSizeSources = new Operand[] { texOp.GetSource(0), Const(0) }; } @@ -209,7 +242,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms texOp.SetSource(coordsIndex + index, coordNormalized); - InsertTextureSizeUnscale(hfm, textureSizeNode, resourceManager, stage); + InsertTextureSizeUnscale(hfm, textureSizeNode, resourceManager, stage, targetApi); } return node; @@ -234,10 +267,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms return node; } - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; - int coordsCount = texOp.Type.GetDimensions(); - int coordsIndex = isBindless || isIndexed ? 1 : 0; + int coordsIndex = isBindless ? 1 : 0; int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount; @@ -249,7 +280,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms Operand[] texSizeSources; - if (isBindless || isIndexed) + if (isBindless) { texSizeSources = new Operand[] { texOp.GetSource(0), Const(0) }; } @@ -321,7 +352,6 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms bool hasLodLevel = (texOp.Flags & TextureFlags.LodLevel) != 0; bool isArray = (texOp.Type & SamplerType.Array) != 0; - bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0; bool isShadow = (texOp.Type & SamplerType.Shadow) != 0; @@ -347,7 +377,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms int copyCount = 0; - if (isBindless || isIndexed) + if (isBindless) { copyCount++; } @@ -424,7 +454,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms sources[dstIndex++] = texOp.GetSource(srcIndex++); } - int coordsIndex = isBindless || isIndexed ? 1 : 0; + int coordsIndex = isBindless ? 1 : 0; int componentIndex = texOp.Index; @@ -435,7 +465,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms dests[i] = texOp.GetDest(i); } - Operand bindlessHandle = isBindless || isIndexed ? sources[0] : null; + Operand bindlessHandle = isBindless ? sources[0] : null; LinkedListNode oldNode = node; @@ -748,5 +778,113 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms { return (type & SamplerType.Mask) == SamplerType.Texture2D; } + + private static LinkedListNode TurnIntoBindlessIfExceeding( + LinkedListNode node, + ResourceManager resourceManager, + TargetApi targetApi, + ref BindlessTextureFlags bindlessTextureFlags, + bool bindlessTexturesAllowed, + int textureBufferIndex) + { + if (node.Value is not TextureOperation texOp) + { + return node; + } + + // If it's already bindless, then we have nothing to do. + if (texOp.Flags.HasFlag(TextureFlags.Bindless)) + { + resourceManager.EnsureBindlessBinding(targetApi, texOp.Type, texOp.Inst.IsImage()); + + if (IsIndexedAccess(resourceManager, texOp, textureBufferIndex)) + { + bindlessTextureFlags |= BindlessTextureFlags.BindlessNvn; + return node; + } + + if (bindlessTexturesAllowed) + { + bindlessTextureFlags |= BindlessTextureFlags.BindlessFull; + return node; + } + else + { + // Set any destination operand to zero and remove the texture access. + // This is a case where bindless elimination failed, and we assume + // it's too risky to try using full bindless emulation. + + for (int destIndex = 0; destIndex < texOp.DestsCount; destIndex++) + { + Operand dest = texOp.GetDest(destIndex); + node.List.AddBefore(node, new Operation(Instruction.Copy, dest, Const(0))); + } + + LinkedListNode prevNode = node.Previous; + node.List.Remove(node); + + return prevNode; + } + } + + // If the index is within the host API limits, then we don't need to make it bindless. + int index = resourceManager.FindTextureDescriptorIndex(texOp.Binding); + if (index < TextureHandle.GetMaxTexturesPerStage(targetApi)) + { + return node; + } + + TextureDescriptor descriptor = resourceManager.GetTextureDescriptors()[index]; + + (int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = TextureHandle.UnpackOffsets(descriptor.HandleIndex); + (int textureCbufSlot, int samplerCbufSlot) = TextureHandle.UnpackSlots(descriptor.CbufSlot, textureBufferIndex); + + Operand handle = Cbuf(textureCbufSlot, textureWordOffset); + + if (handleType != TextureHandleType.CombinedSampler) + { + Operand handle2 = Cbuf(samplerCbufSlot, samplerWordOffset); + + if (handleType == TextureHandleType.SeparateSamplerId) + { + Operand temp = Local(); + node.List.AddBefore(node, new Operation(Instruction.ShiftLeft, temp, handle2, Const(20))); + handle2 = temp; + } + + Operand handleCombined = Local(); + node.List.AddBefore(node, new Operation(Instruction.BitwiseOr, handleCombined, handle, handle2)); + handle = handleCombined; + } + + texOp.TurnIntoBindless(handle); + bindlessTextureFlags |= BindlessTextureFlags.BindlessConverted; + + resourceManager.EnsureBindlessBinding(targetApi, texOp.Type, texOp.Inst.IsImage()); + + return node; + } + + private static bool IsIndexedAccess(ResourceManager resourceManager, TextureOperation texOp, int textureBufferIndex) + { + // Try to detect a indexed access. + // The access is considered indexed if the handle is loaded with a LDC instruction + // from the driver reserved constant buffer used for texture handles. + if (!(texOp.GetSource(0).AsgOp is Operation handleAsgOp)) + { + return false; + } + + if (handleAsgOp.Inst != Instruction.Load || handleAsgOp.StorageKind != StorageKind.ConstantBuffer) + { + return false; + } + + Operand ldcSrc0 = handleAsgOp.GetSource(0); + + return ldcSrc0.Type == OperandType.Constant && + resourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int cbSlot) && + cbSlot == textureBufferIndex; + } } } diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/VectorComponentSelect.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/VectorComponentSelect.cs index e55f4355d..2b72ccd2e 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/Transforms/VectorComponentSelect.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/VectorComponentSelect.cs @@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms return node; } - BufferDefinition buffer = context.ResourceManager.Properties.ConstantBuffers[bindingIndex.Value]; + BufferDefinition buffer = context.ResourceManager.Properties.ConstantBuffers[SetBindingPair.Unpack(bindingIndex.Value)]; StructureField field = buffer.Type.Fields[fieldIndex.Value]; int elemCount = (field.Type & AggregateType.ElementCountMask) switch diff --git a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs index a112991e9..0865b1a5e 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs @@ -264,6 +264,9 @@ namespace Ryujinx.Graphics.Shader.Translation HelperFunctionManager hfm = new(funcs, Definitions.Stage); + BindlessTextureFlags bindlessTextureFlags = BindlessTextureFlags.None; + bool bindlessTexturesAllowed = true; + for (int i = 0; i < functions.Length; i++) { var cfg = cfgs[i]; @@ -294,9 +297,12 @@ namespace Ryujinx.Graphics.Shader.Translation Definitions, resourceManager, GpuAccessor, + Options.TargetApi, Options.TargetLanguage, Definitions.Stage, - ref usedFeatures); + ref usedFeatures, + ref bindlessTextureFlags, + ref bindlessTexturesAllowed); Optimizer.RunPass(context); TransformPasses.RunPass(context); @@ -312,6 +318,7 @@ namespace Ryujinx.Graphics.Shader.Translation Definitions, resourceManager, usedFeatures, + bindlessTextureFlags, clipDistancesWritten); } @@ -322,6 +329,7 @@ namespace Ryujinx.Graphics.Shader.Translation ShaderDefinitions originalDefinitions, ResourceManager resourceManager, FeatureFlags usedFeatures, + BindlessTextureFlags bindlessTextureFlags, byte clipDistancesWritten) { var sInfo = StructuredProgram.MakeStructuredProgram( @@ -345,6 +353,7 @@ namespace Ryujinx.Graphics.Shader.Translation resourceManager.GetTextureDescriptors(), resourceManager.GetImageDescriptors(), originalDefinitions.Stage, + bindlessTextureFlags, geometryVerticesPerPrimitive, originalDefinitions.MaxOutputVertices, originalDefinitions.ThreadsPerInputPrimitive, @@ -365,7 +374,14 @@ namespace Ryujinx.Graphics.Shader.Translation GpuAccessor.QueryHostSupportsTextureShadowLod(), GpuAccessor.QueryHostSupportsViewportMask()); - var parameters = new CodeGenParameters(attributeUsage, definitions, resourceManager.Properties, hostCapabilities, GpuAccessor, Options.TargetApi); + var parameters = new CodeGenParameters( + attributeUsage, + definitions, + resourceManager.Properties, + hostCapabilities, + GpuAccessor, + Options.TargetApi, + bindlessTextureFlags); return Options.TargetLanguage switch { @@ -474,7 +490,7 @@ namespace Ryujinx.Graphics.Shader.Translation ioUsage = ioUsage.Combine(_vertexOutput); } - return new ResourceReservations(GpuAccessor, IsTransformFeedbackEmulated, vertexAsCompute: true, _vertexOutput, ioUsage); + return new ResourceReservations(GpuAccessor, Options.TargetApi, IsTransformFeedbackEmulated, vertexAsCompute: true, _vertexOutput, ioUsage); } public void SetVertexOutputMapForGeometryAsCompute(TranslatorContext vertexContext) @@ -569,6 +585,7 @@ namespace Ryujinx.Graphics.Shader.Translation definitions, resourceManager, FeatureFlags.None, + BindlessTextureFlags.None, 0); } @@ -665,6 +682,7 @@ namespace Ryujinx.Graphics.Shader.Translation definitions, resourceManager, FeatureFlags.RtLayer, + BindlessTextureFlags.None, 0); } } diff --git a/src/Ryujinx.Graphics.Vulkan/BindlessManager.cs b/src/Ryujinx.Graphics.Vulkan/BindlessManager.cs new file mode 100644 index 000000000..1389efabb --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/BindlessManager.cs @@ -0,0 +1,360 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Numerics; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Vulkan +{ + class BindlessManager + { + private const int TextureIdBits = 20; + private const int SamplerIdBits = 12; + private const int TextureCapacity = 1 << TextureIdBits; + private const int SamplerCapacity = 1 << SamplerIdBits; + + private const int TextureIdBlockShift = 8; + private const int TextureIdBlockMask = 0xfff; + + // Note that each entry must be aligned to 16 bytes, this is a constant buffer layout restriction. + private const int IdMapElements = 4; + + public const uint MinimumTexturesCount = 256; + public const uint MinimumSamplersCount = 256; + + private readonly Dictionary _textureIdMap; + private readonly Dictionary _samplerIdMap; + private readonly ulong[] _textureBlockBitmap; + private readonly ulong[] _samplerBlockBitmap; + + private ITexture[] _textureRefs; + private float[] _textureScales; + private Auto[] _samplerRefs; + private bool _textureScalesDirty; + + public uint TexturesCount => CalculateTexturesCount(); + public uint SamplersCount => CalculateSamplersCount(); + + private readonly int[] _idMap; + private bool _idMapDataDirty; + + private BufferHolder _idMapBuffer; + private BufferHolder _textureScalesBuffer; + + private bool _dirty; + private bool _hasDescriptors; + + private PipelineLayout _pipelineLayout; + private DescriptorSetCollection _bindlessTextures; + private DescriptorSetCollection _bindlessSamplers; + private DescriptorSetCollection _bindlessImages; + private DescriptorSetCollection _bindlessBufferTextures; + private DescriptorSetCollection _bindlessBufferImages; + + public BindlessManager() + { + _textureIdMap = new Dictionary(); + _samplerIdMap = new Dictionary(); + _textureBlockBitmap = new ulong[((TextureCapacity >> TextureIdBlockShift) + 63) / 64]; + _samplerBlockBitmap = new ulong[((SamplerCapacity >> TextureIdBlockShift) + 63) / 64]; + + _textureRefs = Array.Empty(); + _textureScales = Array.Empty(); + _samplerRefs = Array.Empty>(); + + // This is actually a structure with 2 elements, + // texture index (X) and sampler index (Y). + _idMap = new int[(TextureCapacity >> TextureIdBlockShift) * IdMapElements]; + } + + private uint CalculateTexturesCount() + { + return Math.Max(MinimumTexturesCount, (uint)BitUtils.Pow2RoundUp(_textureRefs.Length)); + } + + private uint CalculateSamplersCount() + { + return Math.Max(MinimumSamplersCount, (uint)BitUtils.Pow2RoundUp(_samplerRefs.Length)); + } + + public void SetBindlessTexture(int textureId, ITexture texture, float scale) + { + int textureIndex = GetTextureBlockId(textureId); + + _textureRefs[textureIndex] = texture; + + if (_textureRefs.Length != _textureScales.Length) + { + Array.Resize(ref _textureScales, _textureRefs.Length); + } + + if (_textureScales[textureIndex] != scale) + { + _textureScales[textureIndex] = scale; + _textureScalesDirty = true; + } + + _dirty = true; + } + + public void SetBindlessSampler(int samplerId, Auto sampler) + { + int samplerIndex = GetSamplerBlockId(samplerId); + + _samplerRefs[samplerIndex] = sampler; + _dirty = true; + } + + private int GetTextureBlockId(int textureId) + { + return GetBlockId(textureId, 0, _textureIdMap, _textureBlockBitmap, ref _textureRefs); + } + + private int GetSamplerBlockId(int samplerId) + { + return GetBlockId(samplerId, 1, _samplerIdMap, _samplerBlockBitmap, ref _samplerRefs); + } + + private int GetBlockId(int id, int idMapOffset, Dictionary idMap, ulong[] bitmap, ref T[] resourceRefs) + { + int blockIndex = (id >> TextureIdBlockShift) & TextureIdBlockMask; + + if (!idMap.TryGetValue(blockIndex, out int mappedIndex)) + { + mappedIndex = AllocateNewBlock(bitmap); + + int minLength = (mappedIndex + 1) << TextureIdBlockShift; + + if (minLength > resourceRefs.Length) + { + Array.Resize(ref resourceRefs, minLength); + } + + _idMap[blockIndex * IdMapElements + idMapOffset] = mappedIndex << TextureIdBlockShift; + _idMapDataDirty = true; + + idMap.Add(blockIndex, mappedIndex); + } + + return (mappedIndex << TextureIdBlockShift) | (id & ~(TextureIdBlockMask << TextureIdBlockShift)); + } + + private static int AllocateNewBlock(ulong[] bitmap) + { + for (int index = 0; index < bitmap.Length; index++) + { + ref ulong v = ref bitmap[index]; + + if (v == ulong.MaxValue) + { + continue; + } + + int firstFreeBit = BitOperations.TrailingZeroCount(~v); + v |= 1UL << firstFreeBit; + return index * 64 + firstFreeBit; + } + + throw new InvalidOperationException("No free space left on the texture or sampler table."); + } + + public void UpdateAndBind( + VulkanRenderer gd, + ShaderCollection program, + CommandBufferScoped cbs, + PipelineBindPoint pbp, + SamplerHolder dummySampler) + { + if (!_dirty) + { + Rebind(gd, program, cbs, pbp); + return; + } + + _dirty = false; + + var plce = program.GetPipelineLayoutCacheEntry(gd, TexturesCount, SamplersCount); + + plce.UpdateCommandBufferIndex(cbs.CommandBufferIndex); + + var btDsc = plce.GetNewDescriptorSetCollection(PipelineBase.BindlessTexturesSetIndex, out _).Get(cbs); + var bbtDsc = plce.GetNewDescriptorSetCollection(PipelineBase.BindlessBufferTextureSetIndex, out _).Get(cbs); + var bsDsc = plce.GetNewDescriptorSetCollection(PipelineBase.BindlessSamplersSetIndex, out _).Get(cbs); + var biDsc = plce.GetNewDescriptorSetCollection(PipelineBase.BindlessImagesSetIndex, out _).Get(cbs); + var bbiDsc = plce.GetNewDescriptorSetCollection(PipelineBase.BindlessBufferImageSetIndex, out _).Get(cbs); + + int idMapBufferSizeInBytes = _idMap.Length * sizeof(int); + + if (_idMapBuffer == null) + { + _idMapBuffer = gd.BufferManager.Create(gd, idMapBufferSizeInBytes); + } + + if (_idMapDataDirty) + { + _idMapBuffer.SetDataUnchecked(0, MemoryMarshal.Cast(_idMap)); + _idMapDataDirty = false; + } + + int textureScalesBufferSizeInBytes = _textureScales.Length * sizeof(float); + + if (_textureScalesDirty) + { + if (_textureScalesBuffer == null || _textureScalesBuffer.Size != textureScalesBufferSizeInBytes) + { + _textureScalesBuffer?.Dispose(); + _textureScalesBuffer = gd.BufferManager.Create(gd, textureScalesBufferSizeInBytes); + } + + _textureScalesBuffer.SetDataUnchecked(0, MemoryMarshal.Cast(_textureScales)); + _textureScalesDirty = false; + } + + Span uniformBuffer = stackalloc DescriptorBufferInfo[1]; + + uniformBuffer[0] = new DescriptorBufferInfo() + { + Offset = 0, + Range = (ulong)idMapBufferSizeInBytes, + Buffer = _idMapBuffer.GetBuffer().Get(cbs, 0, idMapBufferSizeInBytes).Value + }; + + btDsc.UpdateBuffers(0, 0, uniformBuffer, DescriptorType.UniformBuffer); + + if (_textureScalesBuffer != null) + { + Span storageBuffer = stackalloc DescriptorBufferInfo[1]; + + storageBuffer[0] = new DescriptorBufferInfo() + { + Offset = 0, + Range = (ulong)textureScalesBufferSizeInBytes, + Buffer = _textureScalesBuffer.GetBuffer().Get(cbs, 0, textureScalesBufferSizeInBytes).Value + }; + + btDsc.UpdateBuffers(0, 1, storageBuffer, DescriptorType.StorageBuffer); + } + + for (int i = 0; i < _textureRefs.Length; i++) + { + var texture = _textureRefs[i]; + if (texture is TextureView view) + { + var td = new DescriptorImageInfo() + { + ImageLayout = ImageLayout.General, + ImageView = view.GetImageView().Get(cbs).Value + }; + + btDsc.UpdateImage(0, 2, i, td, DescriptorType.SampledImage); + + if (view.Info.Format.IsImageCompatible()) + { + td = new DescriptorImageInfo() + { + ImageLayout = ImageLayout.General, + ImageView = view.GetIdentityImageView().Get(cbs).Value + }; + + biDsc.UpdateImage(0, 0, i, td, DescriptorType.StorageImage); + } + } + else if (texture is TextureBuffer buffer) + { + bool isImageCompatible = buffer.Format.IsImageCompatible(); + var bufferView = buffer.GetBufferView(cbs, isImageCompatible); + + bbtDsc.UpdateBufferImage(0, 0, i, bufferView, DescriptorType.UniformTexelBuffer); + + if (isImageCompatible) + { + bbiDsc.UpdateBufferImage(0, 0, i, bufferView, DescriptorType.StorageTexelBuffer); + } + } + } + + for (int i = 0; i < _samplerRefs.Length; i++) + { + var sampler = _samplerRefs[i]; + if (sampler != null) + { + var sd = new DescriptorImageInfo() + { + Sampler = sampler.Get(cbs).Value + }; + + if (sd.Sampler.Handle == 0) + { + sd.Sampler = dummySampler.GetSampler().Get(cbs).Value; + } + + bsDsc.UpdateImage(0, 0, i, sd, DescriptorType.Sampler); + } + } + + _pipelineLayout = plce.PipelineLayout; + _bindlessTextures = btDsc; + _bindlessSamplers = bsDsc; + _bindlessImages = biDsc; + _bindlessBufferTextures = bbtDsc; + _bindlessBufferImages = bbiDsc; + + _hasDescriptors = true; + + Bind(gd, program, cbs, pbp, plce.PipelineLayout, btDsc, bsDsc, biDsc, bbtDsc, bbiDsc); + } + + private void Rebind(VulkanRenderer gd, ShaderCollection program, CommandBufferScoped cbs, PipelineBindPoint pbp) + { + if (_hasDescriptors) + { + Bind( + gd, + program, + cbs, + pbp, + _pipelineLayout, + _bindlessTextures, + _bindlessSamplers, + _bindlessImages, + _bindlessBufferTextures, + _bindlessBufferImages); + } + } + + private void Bind( + VulkanRenderer gd, + ShaderCollection program, + CommandBufferScoped cbs, + PipelineBindPoint pbp, + PipelineLayout pipelineLayout, + DescriptorSetCollection bindlessTextures, + DescriptorSetCollection bindlessSamplers, + DescriptorSetCollection bindlessImages, + DescriptorSetCollection bindlessBufferTextures, + DescriptorSetCollection bindlessBufferImages) + { + gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, pipelineLayout, PipelineBase.BindlessTexturesSetIndex, 1, bindlessTextures.GetSets(), 0, ReadOnlySpan.Empty); + gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, pipelineLayout, PipelineBase.BindlessBufferTextureSetIndex, 1, bindlessBufferTextures.GetSets(), 0, ReadOnlySpan.Empty); + gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, pipelineLayout, PipelineBase.BindlessSamplersSetIndex, 1, bindlessSamplers.GetSets(), 0, ReadOnlySpan.Empty); + gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, pipelineLayout, PipelineBase.BindlessImagesSetIndex, 1, bindlessImages.GetSets(), 0, ReadOnlySpan.Empty); + gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, pipelineLayout, PipelineBase.BindlessBufferImageSetIndex, 1, bindlessBufferImages.GetSets(), 0, ReadOnlySpan.Empty); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _idMapBuffer?.Dispose(); + _textureScalesBuffer?.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetCollection.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetCollection.cs index 846dd5c7d..832d7607b 100644 --- a/src/Ryujinx.Graphics.Vulkan/DescriptorSetCollection.cs +++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetCollection.cs @@ -88,6 +88,22 @@ namespace Ryujinx.Graphics.Vulkan } } + public unsafe void UpdateImage(int setIndex, int bindingIndex, int elementIndex, DescriptorImageInfo imageInfo, DescriptorType type) + { + var writeDescriptorSet = new WriteDescriptorSet + { + SType = StructureType.WriteDescriptorSet, + DstSet = _descriptorSets[setIndex], + DstBinding = (uint)bindingIndex, + DstArrayElement = (uint)elementIndex, + DescriptorType = type, + DescriptorCount = 1, + PImageInfo = &imageInfo + }; + + _holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null); + } + public unsafe void UpdateImages(int setIndex, int baseBinding, ReadOnlySpan imageInfo, DescriptorType type) { if (imageInfo.Length == 0) @@ -152,7 +168,7 @@ namespace Ryujinx.Graphics.Vulkan } } - public unsafe void UpdateBufferImage(int setIndex, int bindingIndex, BufferView texelBufferView, DescriptorType type) + public unsafe void UpdateBufferImage(int setIndex, int bindingIndex, int elementIndex, BufferView texelBufferView, DescriptorType type) { if (texelBufferView.Handle != 0UL) { @@ -161,6 +177,7 @@ namespace Ryujinx.Graphics.Vulkan SType = StructureType.WriteDescriptorSet, DstSet = _descriptorSets[setIndex], DstBinding = (uint)bindingIndex, + DstArrayElement = (uint)elementIndex, DescriptorType = type, DescriptorCount = 1, PTexelBufferView = &texelBufferView, diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs index a9a92df1d..e158d6ebc 100644 --- a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs +++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs @@ -61,6 +61,11 @@ namespace Ryujinx.Graphics.Vulkan private bool _updateDescriptorCacheCbIndex; + private readonly BindlessManager _bindlessManager; + + public uint BindlessTexturesCount => _bindlessManager.TexturesCount; + public uint BindlessSamplersCount => _bindlessManager.SamplersCount; + [Flags] private enum DirtyFlags { @@ -69,7 +74,8 @@ namespace Ryujinx.Graphics.Vulkan Storage = 1 << 1, Texture = 1 << 2, Image = 1 << 3, - All = Uniform | Storage | Texture | Image, + Bindless = 1 << 4, + All = Uniform | Storage | Texture | Image | Bindless, } private DirtyFlags _dirty; @@ -110,6 +116,8 @@ namespace Ryujinx.Graphics.Vulkan _textures.AsSpan().Fill(initialImageInfo); _images.AsSpan().Fill(initialImageInfo); + _bindlessManager = new BindlessManager(); + if (gd.Capabilities.SupportsNullDescriptors) { // If null descriptors are supported, we can pass null as the handle. @@ -405,6 +413,20 @@ namespace Ryujinx.Graphics.Vulkan SignalDirty(DirtyFlags.Uniform); } + public void SetBindlessTexture(int textureId, ITexture texture, float textureScale) + { + _bindlessManager.SetBindlessTexture(textureId, texture, textureScale); + + SignalDirty(DirtyFlags.Bindless); + } + + public void SetBindlessSampler(int samplerId, ISampler sampler) + { + _bindlessManager.SetBindlessSampler(samplerId, ((SamplerHolder)sampler)?.GetSampler()); + + SignalDirty(DirtyFlags.Bindless); + } + private void SignalDirty(DirtyFlags flag) { _dirty |= flag; @@ -444,7 +466,21 @@ namespace Ryujinx.Graphics.Vulkan UpdateAndBind(cbs, PipelineBase.ImageSetIndex, pbp); } - _dirty = DirtyFlags.None; + DirtyFlags newFlags = DirtyFlags.None; + + if (_dirty.HasFlag(DirtyFlags.Bindless)) + { + if (_program.HasBindless) + { + _bindlessManager.UpdateAndBind(_gd, _program, cbs, pbp, _dummySampler); + } + else + { + newFlags = DirtyFlags.Bindless; + } + } + + _dirty = newFlags; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -623,9 +659,12 @@ namespace Ryujinx.Graphics.Vulkan } } + (uint bindlessTexturesCount, uint bindlessSamplersCount) = GetBindlessCountsForProgram(); + + var pipelineLayout = _program.GetPipelineLayout(_gd, bindlessTexturesCount, bindlessSamplersCount); var sets = dsc.GetSets(); - _gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan.Empty); + _gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, pipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan.Empty); } private unsafe void UpdateBuffers( @@ -640,6 +679,10 @@ namespace Ryujinx.Graphics.Vulkan return; } + (uint bindlessTexturesCount, uint bindlessSamplersCount) = GetBindlessCountsForProgram(); + + var pipelineLayout = _program.GetPipelineLayout(_gd, bindlessTexturesCount, bindlessSamplersCount); + fixed (DescriptorBufferInfo* pBufferInfo = bufferInfo) { var writeDescriptorSet = new WriteDescriptorSet @@ -651,7 +694,7 @@ namespace Ryujinx.Graphics.Vulkan PBufferInfo = pBufferInfo, }; - _gd.PushDescriptorApi.CmdPushDescriptorSet(cbs.CommandBuffer, pbp, _program.PipelineLayout, 0, 1, &writeDescriptorSet); + _gd.PushDescriptorApi.CmdPushDescriptorSet(cbs.CommandBuffer, pbp, pipelineLayout, 0, 1, &writeDescriptorSet); } } @@ -688,6 +731,16 @@ namespace Ryujinx.Graphics.Vulkan } } + private (uint, uint) GetBindlessCountsForProgram() + { + if (_program == null || !_program.HasBindless) + { + return (0, 0); + } + + return (BindlessTexturesCount, BindlessSamplersCount); + } + private void Initialize(CommandBufferScoped cbs, int setIndex, DescriptorSetCollection dsc) { // We don't support clearing texture descriptors currently. @@ -736,6 +789,8 @@ namespace Ryujinx.Graphics.Vulkan { _dummyTexture.Dispose(); _dummySampler.Dispose(); + + _bindlessManager.Dispose(); } } diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs index 7346d7891..8535b5158 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs @@ -19,11 +19,17 @@ namespace Ryujinx.Graphics.Vulkan class PipelineBase : IDisposable { public const int DescriptorSetLayouts = 4; + public const int DescriptorSetLayoutsBindless = 9; public const int UniformSetIndex = 0; public const int StorageSetIndex = 1; public const int TextureSetIndex = 2; public const int ImageSetIndex = 3; + public const int BindlessTexturesSetIndex = 4; + public const int BindlessBufferTextureSetIndex = 5; + public const int BindlessSamplersSetIndex = 6; + public const int BindlessImagesSetIndex = 7; + public const int BindlessBufferImageSetIndex = 8; protected readonly VulkanRenderer Gd; protected readonly Device Device; @@ -714,6 +720,42 @@ namespace Ryujinx.Graphics.Vulkan _tfEnabled = false; } + public void RegisterBindlessSampler(int samplerId, ISampler sampler) + { + _descriptorSetUpdater.SetBindlessSampler(samplerId, sampler); + + bool hasBindless = _program?.HasBindless ?? false; + uint bindlessSamplersCount = hasBindless ? _descriptorSetUpdater.BindlessSamplersCount : 0; + + if (_newState.BindlessSamplersCount != bindlessSamplersCount) + { + _newState.BindlessSamplersCount = bindlessSamplersCount; + + SignalStateChange(); + } + } + + public void RegisterBindlessTexture(int textureId, ITexture texture, float textureScale) + { + _descriptorSetUpdater.SetBindlessTexture(textureId, texture, textureScale); + + bool hasBindless = _program?.HasBindless ?? false; + uint bindlessTexturesCount = hasBindless ? _descriptorSetUpdater.BindlessTexturesCount : 0; + + if (_newState.BindlessTexturesCount != bindlessTexturesCount) + { + _newState.BindlessTexturesCount = bindlessTexturesCount; + + SignalStateChange(); + } + } + + public void RegisterBindlessTextureAndSampler(int textureId, ITexture texture, float textureScale, int samplerId, ISampler sampler) + { + RegisterBindlessSampler(samplerId, sampler); + RegisterBindlessTexture(textureId, texture, textureScale); + } + public bool IsCommandBufferActive(CommandBuffer cb) { return CommandBuffer.Handle == cb.Handle; @@ -967,11 +1009,24 @@ namespace Ryujinx.Graphics.Vulkan _descriptorSetUpdater.SetProgram(internalProgram); - _newState.PipelineLayout = internalProgram.PipelineLayout; _newState.StagesCount = (uint)stages.Length; stages.CopyTo(_newState.Stages.AsSpan()[..stages.Length]); + if (internalProgram.HasBindless) + { + uint bindlessTexturesCount = _descriptorSetUpdater.BindlessTexturesCount; + uint bindlessSamplersCount = _descriptorSetUpdater.BindlessSamplersCount; + + _newState.BindlessTexturesCount = bindlessTexturesCount; + _newState.BindlessSamplersCount = bindlessSamplersCount; + } + else + { + _newState.BindlessTexturesCount = 0; + _newState.BindlessSamplersCount = 0; + } + SignalStateChange(); if (internalProgram.IsCompute) diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCache.cs b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCache.cs index 5d0cada96..bbc01afad 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCache.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCache.cs @@ -11,12 +11,12 @@ namespace Ryujinx.Graphics.Vulkan private readonly struct PlceKey : IEquatable { public readonly ReadOnlyCollection SetDescriptors; - public readonly bool UsePushDescriptors; + public readonly PipelineLayoutUsageInfo UsageInfo; - public PlceKey(ReadOnlyCollection setDescriptors, bool usePushDescriptors) + public PlceKey(ReadOnlyCollection setDescriptors, PipelineLayoutUsageInfo usageInfo) { SetDescriptors = setDescriptors; - UsePushDescriptors = usePushDescriptors; + UsageInfo = usageInfo; } public override int GetHashCode() @@ -31,7 +31,7 @@ namespace Ryujinx.Graphics.Vulkan } } - hasher.Add(UsePushDescriptors); + hasher.Add(UsageInfo); return hasher.ToHashCode(); } @@ -64,7 +64,7 @@ namespace Ryujinx.Graphics.Vulkan } } - return UsePushDescriptors == other.UsePushDescriptors; + return UsageInfo.Equals(other.UsageInfo); } } @@ -72,18 +72,18 @@ namespace Ryujinx.Graphics.Vulkan public PipelineLayoutCache() { - _plces = new ConcurrentDictionary(); + _plces = new(); } public PipelineLayoutCacheEntry GetOrCreate( VulkanRenderer gd, Device device, ReadOnlyCollection setDescriptors, - bool usePushDescriptors) + PipelineLayoutUsageInfo usageInfo) { - var key = new PlceKey(setDescriptors, usePushDescriptors); + var key = new PlceKey(setDescriptors, usageInfo); - return _plces.GetOrAdd(key, newKey => new PipelineLayoutCacheEntry(gd, device, setDescriptors, usePushDescriptors)); + return _plces.GetOrAdd(key, newKey => new PipelineLayoutCacheEntry(gd, device, setDescriptors, usageInfo)); } protected virtual void Dispose(bool disposing) diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs index 2840dda0f..f21d40bfb 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs @@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Vulkan private const uint DefaultTexturePoolCapacity = 128 * DescriptorSetManager.MaxSets; private const uint DefaultImagePoolCapacity = 8 * DescriptorSetManager.MaxSets; - private const int MaxPoolSizesPerSet = 2; + private const int MaxPoolSizesPerSet = 3; private readonly VulkanRenderer _gd; private readonly Device _device; @@ -31,6 +31,9 @@ namespace Ryujinx.Graphics.Vulkan private int _dsLastCbIndex; private int _dsLastSubmissionCount; + private readonly uint _bindlessTexturesCount; + private readonly uint _bindlessSamplersCount; + private PipelineLayoutCacheEntry(VulkanRenderer gd, Device device, int setsCount) { _gd = gd; @@ -44,7 +47,7 @@ namespace Ryujinx.Graphics.Vulkan for (int j = 0; j < _dsCache[i].Length; j++) { - _dsCache[i][j] = new List>(); + _dsCache[i][j] = new(); } } @@ -55,9 +58,9 @@ namespace Ryujinx.Graphics.Vulkan VulkanRenderer gd, Device device, ReadOnlyCollection setDescriptors, - bool usePushDescriptors) : this(gd, device, setDescriptors.Count) + PipelineLayoutUsageInfo usageInfo) : this(gd, device, setDescriptors.Count) { - (DescriptorSetLayouts, PipelineLayout) = PipelineLayoutFactory.Create(gd, device, setDescriptors, usePushDescriptors); + (DescriptorSetLayouts, PipelineLayout) = PipelineLayoutFactory.Create(gd, device, setDescriptors, usageInfo); _consumedDescriptorsPerSet = new int[setDescriptors.Count]; @@ -72,6 +75,9 @@ namespace Ryujinx.Graphics.Vulkan _consumedDescriptorsPerSet[setIndex] = count; } + + _bindlessSamplersCount = usageInfo.BindlessSamplersCount; + _bindlessTexturesCount = usageInfo.BindlessTexturesCount; } public void UpdateCommandBufferIndex(int commandBufferIndex) @@ -99,13 +105,18 @@ namespace Ryujinx.Graphics.Vulkan int consumedDescriptors = _consumedDescriptorsPerSet[setIndex]; + // 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; + var dsc = _gd.DescriptorSetManager.AllocateDescriptorSet( _gd.Api, DescriptorSetLayouts[setIndex], poolSizes, setIndex, consumedDescriptors, - false); + updateAfterBind); list.Add(dsc); isNew = true; @@ -116,7 +127,7 @@ namespace Ryujinx.Graphics.Vulkan return list[index]; } - private static Span GetDescriptorPoolSizes(Span output, int setIndex) + private Span GetDescriptorPoolSizes(Span output, int setIndex) { int count = 1; @@ -138,6 +149,24 @@ namespace Ryujinx.Graphics.Vulkan output[1] = new(DescriptorType.StorageTexelBuffer, DefaultImagePoolCapacity); count = 2; break; + case PipelineBase.BindlessTexturesSetIndex: + output[0] = new(DescriptorType.UniformBuffer, 1); + output[1] = new(DescriptorType.StorageBuffer, 1); + output[2] = new(DescriptorType.SampledImage, _bindlessTexturesCount); + count = 3; + break; + case PipelineBase.BindlessBufferTextureSetIndex: + output[0] = new(DescriptorType.UniformTexelBuffer, _bindlessTexturesCount); + break; + case PipelineBase.BindlessSamplersSetIndex: + output[0] = new(DescriptorType.Sampler, _bindlessSamplersCount); + break; + case PipelineBase.BindlessImagesSetIndex: + output[0] = new(DescriptorType.StorageImage, _bindlessTexturesCount); + break; + case PipelineBase.BindlessBufferImageSetIndex: + output[0] = new(DescriptorType.StorageTexelBuffer, _bindlessTexturesCount); + break; } return output[..count]; diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs index ba93dfadb..9e667fe7c 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs @@ -10,7 +10,7 @@ namespace Ryujinx.Graphics.Vulkan VulkanRenderer gd, Device device, ReadOnlyCollection setDescriptors, - bool usePushDescriptors) + PipelineLayoutUsageInfo usageInfo) { DescriptorSetLayout[] layouts = new DescriptorSetLayout[setDescriptors.Count]; @@ -30,6 +30,8 @@ namespace Ryujinx.Graphics.Vulkan } } + bool hasRuntimeArray = false; + DescriptorSetLayoutBinding[] layoutBindings = new DescriptorSetLayoutBinding[rdc.Descriptors.Count]; for (int descIndex = 0; descIndex < rdc.Descriptors.Count; descIndex++) @@ -45,11 +47,22 @@ namespace Ryujinx.Graphics.Vulkan stages = activeStages; } + uint count = (uint)descriptor.Count; + + if (count == 0) + { + count = descriptor.Type == ResourceType.Sampler + ? usageInfo.BindlessSamplersCount + : usageInfo.BindlessTexturesCount; + + hasRuntimeArray = true; + } + layoutBindings[descIndex] = new DescriptorSetLayoutBinding { Binding = (uint)descriptor.Binding, DescriptorType = descriptor.Type.Convert(), - DescriptorCount = (uint)descriptor.Count, + DescriptorCount = count, StageFlags = stages.Convert(), }; } @@ -61,10 +74,40 @@ namespace Ryujinx.Graphics.Vulkan SType = StructureType.DescriptorSetLayoutCreateInfo, PBindings = pLayoutBindings, BindingCount = (uint)layoutBindings.Length, - Flags = usePushDescriptors && setIndex == 0 ? DescriptorSetLayoutCreateFlags.PushDescriptorBitKhr : DescriptorSetLayoutCreateFlags.None, + Flags = usageInfo.UsePushDescriptors && setIndex == 0 ? DescriptorSetLayoutCreateFlags.PushDescriptorBitKhr : DescriptorSetLayoutCreateFlags.None, }; - gd.Api.CreateDescriptorSetLayout(device, descriptorSetLayoutCreateInfo, null, out layouts[setIndex]).ThrowOnError(); + if (hasRuntimeArray) + { + var bindingFlags = new DescriptorBindingFlags[rdc.Descriptors.Count]; + + for (int descIndex = 0; descIndex < rdc.Descriptors.Count; descIndex++) + { + if (rdc.Descriptors.Count == 0) + { + bindingFlags[descIndex] = DescriptorBindingFlags.UpdateAfterBindBit; + } + } + + fixed (DescriptorBindingFlags* pBindingFlags = bindingFlags) + { + var descriptorSetLayoutFlagsCreateInfo = new DescriptorSetLayoutBindingFlagsCreateInfo() + { + SType = StructureType.DescriptorSetLayoutBindingFlagsCreateInfo, + PBindingFlags = pBindingFlags, + BindingCount = (uint)bindingFlags.Length, + }; + + descriptorSetLayoutCreateInfo.PNext = &descriptorSetLayoutFlagsCreateInfo; + descriptorSetLayoutCreateInfo.Flags |= DescriptorSetLayoutCreateFlags.UpdateAfterBindPoolBit; + + gd.Api.CreateDescriptorSetLayout(device, descriptorSetLayoutCreateInfo, null, out layouts[setIndex]).ThrowOnError(); + } + } + else + { + gd.Api.CreateDescriptorSetLayout(device, descriptorSetLayoutCreateInfo, null, out layouts[setIndex]).ThrowOnError(); + } } } diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutUsageInfo.cs b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutUsageInfo.cs new file mode 100644 index 000000000..2174c2b9d --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutUsageInfo.cs @@ -0,0 +1,35 @@ +using System; + +namespace Ryujinx.Graphics.Vulkan +{ + struct PipelineLayoutUsageInfo : IEquatable + { + public readonly uint BindlessTexturesCount; + public readonly uint BindlessSamplersCount; + public readonly bool UsePushDescriptors; + + public PipelineLayoutUsageInfo(uint bindlessTexturesCount, uint bindlessSamplersCount, bool usePushDescriptors) + { + BindlessTexturesCount = bindlessTexturesCount; + BindlessSamplersCount = bindlessSamplersCount; + UsePushDescriptors = usePushDescriptors; + } + + public override bool Equals(object obj) + { + return obj is PipelineLayoutUsageInfo other && Equals(other); + } + + public bool Equals(PipelineLayoutUsageInfo other) + { + return BindlessTexturesCount == other.BindlessTexturesCount && + BindlessSamplersCount == other.BindlessSamplersCount && + UsePushDescriptors == other.UsePushDescriptors; + } + + public override int GetHashCode() + { + return HashCode.Combine(BindlessTexturesCount, BindlessSamplersCount, UsePushDescriptors); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineState.cs b/src/Ryujinx.Graphics.Vulkan/PipelineState.cs index 5a30cff8e..90893e839 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineState.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineState.cs @@ -311,9 +311,20 @@ namespace Ryujinx.Graphics.Vulkan set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFBF) | ((value ? 1UL : 0UL) << 6); } + public uint BindlessTexturesCount + { + get => (uint)((Internal.Id10 >> 0) & 0xFFFFFFFF); + set => Internal.Id10 = (Internal.Id10 & 0xFFFFFFFF00000000) | ((ulong)value << 0); + } + + public uint BindlessSamplersCount + { + get => (uint)((Internal.Id10 >> 32) & 0xFFFFFFFF); + set => Internal.Id10 = (Internal.Id10 & 0xFFFFFFFF) | ((ulong)value << 32); + } + public NativeArray Stages; public NativeArray StageRequiredSubgroupSizes; - public PipelineLayout PipelineLayout; public SpecData SpecializationData; private Array32 _vertexAttributeDescriptions2; @@ -339,6 +350,7 @@ namespace Ryujinx.Graphics.Vulkan LineWidth = 1f; SamplesCount = 1; DepthMode = true; + Internal.Id11 = 0; // Unused. } public unsafe Auto CreateComputePipeline( @@ -357,7 +369,7 @@ namespace Ryujinx.Graphics.Vulkan SType = StructureType.ComputePipelineCreateInfo, Stage = Stages[0], BasePipelineIndex = -1, - Layout = PipelineLayout, + Layout = program.GetPipelineLayout(gd, BindlessTexturesCount, BindlessSamplersCount), }; Pipeline pipelineHandle = default; @@ -625,7 +637,7 @@ namespace Ryujinx.Graphics.Vulkan PDepthStencilState = &depthStencilState, PColorBlendState = &colorBlendState, PDynamicState = &pipelineDynamicStateCreateInfo, - Layout = PipelineLayout, + Layout = program.GetPipelineLayout(gd, BindlessTexturesCount, BindlessSamplersCount), RenderPass = renderPass, BasePipelineIndex = -1, }; diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineUid.cs b/src/Ryujinx.Graphics.Vulkan/PipelineUid.cs index 460c27d8b..6c7edd569 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineUid.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineUid.cs @@ -21,6 +21,8 @@ namespace Ryujinx.Graphics.Vulkan public ulong Id8; public ulong Id9; + public ulong Id10; + public ulong Id11; private readonly uint VertexAttributeDescriptionsCount => (byte)((Id6 >> 38) & 0xFF); private readonly uint VertexBindingDescriptionsCount => (byte)((Id6 >> 46) & 0xFF); @@ -44,7 +46,7 @@ namespace Ryujinx.Graphics.Vulkan { if (!Unsafe.As>(ref Id0).Equals(Unsafe.As>(ref other.Id0)) || !Unsafe.As>(ref Id4).Equals(Unsafe.As>(ref other.Id4)) || - !Unsafe.As>(ref Id8).Equals(Unsafe.As>(ref other.Id8))) + !Unsafe.As>(ref Id8).Equals(Unsafe.As>(ref other.Id8))) { return false; } @@ -88,7 +90,8 @@ namespace Ryujinx.Graphics.Vulkan Id6 * 23 ^ Id7 * 23 ^ Id8 * 23 ^ - Id9 * 23; + Id9 * 23 ^ + Id10 * 23; for (int i = 0; i < (int)VertexAttributeDescriptionsCount; i++) { diff --git a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs index 0cb80ac71..2a01ec05d 100644 --- a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs +++ b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs @@ -15,9 +15,9 @@ namespace Ryujinx.Graphics.Vulkan private readonly Shader[] _shaders; private readonly PipelineLayoutCacheEntry _plce; + private readonly ReadOnlyCollection _setDescriptors; - public PipelineLayout PipelineLayout => _plce.PipelineLayout; - + public bool HasBindless { get; } public bool HasMinimalLayout { get; } public bool UsePushDescriptors { get; } public bool IsCompute { get; } @@ -62,6 +62,7 @@ namespace Ryujinx.Graphics.Vulkan ShaderSource[] shaders, ResourceLayout resourceLayout, SpecDescription[] specDescription = null, + bool hasBindless = false, bool isMinimal = false) { _gd = gd; @@ -109,8 +110,10 @@ namespace Ryujinx.Graphics.Vulkan bool usePushDescriptors = !isMinimal && VulkanConfiguration.UsePushDescriptors && _gd.Capabilities.SupportsPushDescriptors; - _plce = gd.PipelineLayoutCache.GetOrCreate(gd, device, resourceLayout.Sets, usePushDescriptors); + _plce = gd.PipelineLayoutCache.GetOrCreate(gd, device, resourceLayout.Sets, new PipelineLayoutUsageInfo(0, 0, usePushDescriptors)); + _setDescriptors = resourceLayout.Sets; + HasBindless = hasBindless; HasMinimalLayout = isMinimal; UsePushDescriptors = usePushDescriptors; @@ -129,7 +132,8 @@ namespace Ryujinx.Graphics.Vulkan ShaderSource[] sources, ResourceLayout resourceLayout, ProgramPipelineState state, - bool fromCache) : this(gd, device, sources, resourceLayout) + bool hasBindless, + bool fromCache) : this(gd, device, sources, resourceLayout, null, hasBindless) { _state = state; @@ -325,7 +329,12 @@ namespace Ryujinx.Graphics.Vulkan pipeline.Stages[0] = _shaders[0].GetInfo(); pipeline.StagesCount = 1; - pipeline.PipelineLayout = PipelineLayout; + + if (HasBindless) + { + pipeline.BindlessTexturesCount = BindlessManager.MinimumTexturesCount; + pipeline.BindlessSamplersCount = BindlessManager.MinimumSamplersCount; + } pipeline.CreateComputePipeline(_gd, _device, this, (_gd.Pipeline as PipelineBase).PipelineCache); pipeline.Dispose(); @@ -353,7 +362,12 @@ namespace Ryujinx.Graphics.Vulkan } pipeline.StagesCount = (uint)_shaders.Length; - pipeline.PipelineLayout = PipelineLayout; + + if (HasBindless) + { + pipeline.BindlessTexturesCount = BindlessManager.MinimumTexturesCount; + pipeline.BindlessSamplersCount = BindlessManager.MinimumSamplersCount; + } pipeline.CreateGraphicsPipeline(_gd, _device, this, (_gd.Pipeline as PipelineBase).PipelineCache, renderPass.Value); pipeline.Dispose(); @@ -474,6 +488,26 @@ namespace Ryujinx.Graphics.Vulkan return _plce.GetNewDescriptorSetCollection(setIndex, out isNew); } + public PipelineLayout GetPipelineLayout(VulkanRenderer gd, uint bindlessTextureCount, uint bindlessSamplersCount) + { + return GetPipelineLayoutCacheEntry(gd, bindlessTextureCount, bindlessSamplersCount).PipelineLayout; + } + + public PipelineLayoutCacheEntry GetPipelineLayoutCacheEntry(VulkanRenderer gd, uint bindlessTextureCount, uint bindlessSamplersCount) + { + if ((bindlessTextureCount | bindlessSamplersCount) == 0) + { + return _plce; + } + + var usageInfo = new PipelineLayoutUsageInfo( + bindlessTextureCount, + bindlessSamplersCount, + UsePushDescriptors); + + return gd.PipelineLayoutCache.GetOrCreate(gd, _device, _setDescriptors, usageInfo); + } + protected virtual void Dispose(bool disposing) { if (disposing) diff --git a/src/Ryujinx.Graphics.Vulkan/TextureBuffer.cs b/src/Ryujinx.Graphics.Vulkan/TextureBuffer.cs index 285a56498..a610bca59 100644 --- a/src/Ryujinx.Graphics.Vulkan/TextureBuffer.cs +++ b/src/Ryujinx.Graphics.Vulkan/TextureBuffer.cs @@ -23,6 +23,7 @@ namespace Ryujinx.Graphics.Vulkan public int Width { get; } public int Height { get; } + public GAL.Format Format { get; } public VkFormat VkFormat { get; } public TextureBuffer(VulkanRenderer gd, TextureCreateInfo info) @@ -30,6 +31,7 @@ namespace Ryujinx.Graphics.Vulkan _gd = gd; Width = info.Width; Height = info.Height; + Format = info.Format; VkFormat = FormatTable.GetFormat(info.Format); gd.Textures.Add(this); diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs index 973c6d396..546aa5f2e 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs @@ -299,6 +299,14 @@ namespace Ryujinx.Graphics.Vulkan features2.PNext = &supportedFeaturesVk11; + PhysicalDeviceVulkan12Features supportedFeaturesVk12 = new() + { + SType = StructureType.PhysicalDeviceVulkan12Features, + PNext = features2.PNext + }; + + features2.PNext = &supportedFeaturesVk12; + PhysicalDeviceCustomBorderColorFeaturesEXT supportedFeaturesCustomBorderColor = new() { SType = StructureType.PhysicalDeviceCustomBorderColorFeaturesExt, @@ -451,8 +459,14 @@ namespace Ryujinx.Graphics.Vulkan { SType = StructureType.PhysicalDeviceVulkan12Features, PNext = pExtendedFeatures, + DescriptorBindingSampledImageUpdateAfterBind = supportedFeaturesVk12.DescriptorBindingSampledImageUpdateAfterBind, + DescriptorBindingStorageImageUpdateAfterBind = supportedFeaturesVk12.DescriptorBindingStorageImageUpdateAfterBind, + DescriptorBindingStorageTexelBufferUpdateAfterBind = supportedFeaturesVk12.DescriptorBindingStorageTexelBufferUpdateAfterBind, + DescriptorBindingUniformTexelBufferUpdateAfterBind = supportedFeaturesVk12.DescriptorBindingUniformTexelBufferUpdateAfterBind, DescriptorIndexing = physicalDevice.IsDeviceExtensionPresent("VK_EXT_descriptor_indexing"), DrawIndirectCount = physicalDevice.IsDeviceExtensionPresent(KhrDrawIndirectCount.ExtensionName), + RuntimeDescriptorArray = supportedFeaturesVk12.RuntimeDescriptorArray, + SamplerMirrorClampToEdge = supportedFeaturesVk12.SamplerMirrorClampToEdge, UniformBufferStandardLayout = physicalDevice.IsDeviceExtensionPresent("VK_KHR_uniform_buffer_standard_layout"), }; diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index ab8e61371..84cb38b31 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -347,7 +347,7 @@ namespace Ryujinx.Graphics.Vulkan CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex); - DescriptorSetManager = new DescriptorSetManager(_device, PipelineBase.DescriptorSetLayouts); + DescriptorSetManager = new DescriptorSetManager(_device, PipelineBase.DescriptorSetLayoutsBindless); PipelineLayoutCache = new PipelineLayoutCache(); @@ -418,7 +418,7 @@ namespace Ryujinx.Graphics.Vulkan if (info.State.HasValue || isCompute) { - return new ShaderCollection(this, _device, sources, info.ResourceLayout, info.State ?? default, info.FromCache); + return new ShaderCollection(this, _device, sources, info.ResourceLayout, info.State ?? default, info.HasBindless, info.FromCache); } return new ShaderCollection(this, _device, sources, info.ResourceLayout);