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