Minor fixes, reduce amount of bindless updates, initialize table, new bindless indexed modes, etc

This commit is contained in:
Gabriel A 2023-11-12 21:19:57 -03:00
parent 53440e2e21
commit dd9134f5f5
19 changed files with 443 additions and 80 deletions

View file

@ -85,6 +85,11 @@ namespace Ryujinx.Graphics.Gpu
/// </summary> /// </summary>
public const int DriverReservedUniformBuffer = 0; public const int DriverReservedUniformBuffer = 0;
/// <summary>
/// Number of the uniform buffer reserved by the driver to store texture binding handles.
/// </summary>
public const int DriverReserveTextureBindingsBuffer = 2;
/// <summary> /// <summary>
/// Maximum size that an storage buffer is assumed to have when the correct size is unknown. /// Maximum size that an storage buffer is assumed to have when the correct size is unknown.
/// </summary> /// </summary>

View file

@ -87,14 +87,22 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Sets a bit to 0. /// Sets a bit to 0.
/// </summary> /// </summary>
/// <param name="bit">Index of the bit</param> /// <param name="bit">Index of the bit</param>
public void Clear(int bit) /// <returns>True if the bit was set, false otherwise</returns>
public bool Clear(int bit)
{ {
int wordIndex = bit / IntSize; int wordIndex = bit / IntSize;
int wordBit = bit & IntMask; int wordBit = bit & IntMask;
ulong wordMask = 1UL << wordBit; ulong wordMask = 1UL << wordBit;
if ((_masks[wordIndex] & wordMask) == 0)
{
return false;
}
_masks[wordIndex] &= ~wordMask; _masks[wordIndex] &= ~wordMask;
return true;
} }
/// <summary> /// <summary>

View file

@ -214,6 +214,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="endAddress">End address of the region of the pool that has been modified, exclusive</param> /// <param name="endAddress">End address of the region of the pool that has been modified, exclusive</param>
protected void UpdateModifiedEntries(ulong address, ulong endAddress) protected void UpdateModifiedEntries(ulong address, ulong endAddress)
{ {
// TODO: Remove this method (it's unused now).
int startId = (int)((address - Address) / DescriptorSize); int startId = (int)((address - Address) / DescriptorSize);
int endId = (int)((endAddress - Address + (DescriptorSize - 1)) / DescriptorSize) - 1; int endId = (int)((endAddress - Address + (DescriptorSize - 1)) / DescriptorSize) - 1;
@ -228,6 +230,18 @@ namespace Ryujinx.Graphics.Gpu.Image
_maximumAccessedId = Math.Max(_maximumAccessedId, endId); _maximumAccessedId = Math.Max(_maximumAccessedId, endId);
} }
/// <summary>
/// Updates a entry that has been modified.
/// </summary>
/// <param name="id">Pool entry index</param>
protected void UpdateModifiedEntry(int id)
{
ModifiedEntries.Set(id);
_minimumAccessedId = Math.Min(_minimumAccessedId, id);
_maximumAccessedId = Math.Max(_maximumAccessedId, id);
}
/// <summary> /// <summary>
/// Forces all entries as modified, to be updated if any shader uses bindless textures. /// Forces all entries as modified, to be updated if any shader uses bindless textures.
/// </summary> /// </summary>

View file

@ -146,6 +146,29 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
} }
/// <summary>
/// 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.
/// </summary>
/// <param name="id">ID of the texture</param>
/// <param name="sampler">Sampler with the specified ID</param>
/// <returns>True if the sampler entry was modified since the last call, false otherwise</returns>
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;
}
/// <summary> /// <summary>
/// Gets the sampler at the given <paramref name="id"/> from the cache, /// Gets the sampler at the given <paramref name="id"/> from the cache,
/// or creates a new one if not found. /// or creates a new one if not found.
@ -174,8 +197,6 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
ulong endAddress = address + size; ulong endAddress = address + size;
UpdateModifiedEntries(address, endAddress);
for (; address < endAddress; address += DescriptorSize) for (; address < endAddress; address += DescriptorSize)
{ {
int id = (int)((address - Address) / DescriptorSize); int id = (int)((address - Address) / DescriptorSize);
@ -192,10 +213,16 @@ namespace Ryujinx.Graphics.Gpu.Image
continue; continue;
} }
UpdateModifiedEntry(id);
sampler.Dispose(); sampler.Dispose();
Items[id] = null; Items[id] = null;
} }
else
{
UpdateModifiedEntry(id);
}
} }
} }

View file

@ -5,7 +5,7 @@ using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.Shader; using Ryujinx.Graphics.Gpu.Shader;
using Ryujinx.Graphics.Shader; using Ryujinx.Graphics.Shader;
using System; using System;
using System.Collections.Generic; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -61,6 +61,7 @@ namespace Ryujinx.Graphics.Gpu.Image
private int _samplerPoolSequence; private int _samplerPoolSequence;
private BindlessTextureFlags[] _bindlessTextureFlags; private BindlessTextureFlags[] _bindlessTextureFlags;
private uint[] _bindlessIndexedBuffersMask;
private int _textureBufferIndex; private int _textureBufferIndex;
@ -97,6 +98,7 @@ namespace Ryujinx.Graphics.Gpu.Image
_imageState = new TextureState[InitialImageStateSize]; _imageState = new TextureState[InitialImageStateSize];
_bindlessTextureFlags = new BindlessTextureFlags[stages]; _bindlessTextureFlags = new BindlessTextureFlags[stages];
_bindlessIndexedBuffersMask = new uint[stages];
for (int stage = 0; stage < stages; stage++) for (int stage = 0; stage < stages; stage++)
{ {
@ -115,6 +117,7 @@ namespace Ryujinx.Graphics.Gpu.Image
_imageBindings = bindings.ImageBindings; _imageBindings = bindings.ImageBindings;
_bindlessTextureFlags = bindings.BindlessTextureFlags; _bindlessTextureFlags = bindings.BindlessTextureFlags;
_bindlessIndexedBuffersMask = bindings.BindlessIndexedBuffersMask;
SetMaxBindings(bindings.MaxTextureBinding, bindings.MaxImageBinding); SetMaxBindings(bindings.MaxTextureBinding, bindings.MaxImageBinding);
} }
@ -366,14 +369,14 @@ namespace Ryujinx.Graphics.Gpu.Image
specStateMatches &= CommitTextureBindings(texturePool, samplerPool, ShaderStage.Compute, 0, poolModified, specState); specStateMatches &= CommitTextureBindings(texturePool, samplerPool, ShaderStage.Compute, 0, poolModified, specState);
specStateMatches &= CommitImageBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState); specStateMatches &= CommitImageBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState);
if (_bindlessTextureFlags[0].HasFlag(BindlessTextureFlags.BindlessNvn)) if (_bindlessTextureFlags[0].HasFlag(BindlessTextureFlags.BindlessFull))
{
CommitBindlessResources(texturePool, ShaderStage.Compute, 0);
}
else if (_bindlessTextureFlags[0].HasFlag(BindlessTextureFlags.BindlessFull))
{ {
texturePool.LoadAll(_context.Renderer, _samplerPool); texturePool.LoadAll(_context.Renderer, _samplerPool);
} }
else if ((_bindlessTextureFlags[0] & BindlessTextureFlags.BindlessNvnAny) != 0)
{
CommitBindlessResources(texturePool, ShaderStage.Compute, 0);
}
} }
else else
{ {
@ -384,14 +387,14 @@ namespace Ryujinx.Graphics.Gpu.Image
specStateMatches &= CommitTextureBindings(texturePool, samplerPool, stage, stageIndex, poolModified, specState); specStateMatches &= CommitTextureBindings(texturePool, samplerPool, stage, stageIndex, poolModified, specState);
specStateMatches &= CommitImageBindings(texturePool, stage, stageIndex, poolModified, specState); specStateMatches &= CommitImageBindings(texturePool, stage, stageIndex, poolModified, specState);
if (_bindlessTextureFlags[stageIndex].HasFlag(BindlessTextureFlags.BindlessNvn)) if (_bindlessTextureFlags[stageIndex].HasFlag(BindlessTextureFlags.BindlessFull))
{
CommitBindlessResources(texturePool, stage, stageIndex);
}
else if (_bindlessTextureFlags[stageIndex].HasFlag(BindlessTextureFlags.BindlessFull))
{ {
texturePool.LoadAll(_context.Renderer, _samplerPool); texturePool.LoadAll(_context.Renderer, _samplerPool);
} }
else if ((_bindlessTextureFlags[stageIndex] & BindlessTextureFlags.BindlessNvnAny) != 0)
{
CommitBindlessResources(texturePool, stage, stageIndex);
}
} }
} }
@ -783,11 +786,50 @@ namespace Ryujinx.Graphics.Gpu.Image
return; return;
} }
for (int index = 0; index < 32; index++) BindlessTextureFlags flags = _bindlessTextureFlags[stageIndex];
{ uint buffersMask = _bindlessIndexedBuffersMask[stageIndex];
int wordOffset = 8 + index * 2;
int packedId = ReadConstantBuffer<int>(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<int> cbData = GetConstantBufferRange<int>(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 textureId = TextureHandle.UnpackTextureId(packedId);
int samplerId; int samplerId;
@ -801,38 +843,115 @@ namespace Ryujinx.Graphics.Gpu.Image
samplerId = TextureHandle.UnpackSamplerId(packedId); samplerId = TextureHandle.UnpackSamplerId(packedId);
} }
Texture texture = pool.Get(textureId); pool.UpdateBindlessCombined(_context.Renderer, samplerPool, textureId, samplerId);
}
}
}
if (texture == null) /// <summary>
/// Ensures that the texture bindings are visible to the host GPU.
/// Note: this actually performs the binding using the host graphics API.
/// </summary>
/// <param name="pool">The current texture pool</param>
/// <param name="stageIndex">The stage number of the specified shader stage</param>
private void CommitBindlessResourcesNvnCombined(TexturePool pool, int stageIndex)
{
var samplerPool = _samplerPool;
ReadOnlySpan<int> cbData = GetConstantBufferRange<int>(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; continue;
} }
if (texture.Target == Target.TextureBuffer) int textureId = TextureHandle.UnpackTextureId(packedId);
int samplerId;
if (_samplerIndex == SamplerIndex.ViaHeaderIndex)
{ {
// Ensure that the buffer texture is using the correct buffer as storage. samplerId = textureId;
// 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);
} }
else else
{ {
Sampler sampler = samplerPool?.Get(samplerId); samplerId = TextureHandle.UnpackSamplerId(packedId);
}
if (sampler == null) pool.UpdateBindlessCombined(_context.Renderer, samplerPool, textureId, samplerId);
}
}
/// <summary>
/// Ensures that the texture bindings are visible to the host GPU.
/// Note: this actually performs the binding using the host graphics API.
/// </summary>
/// <param name="pool">The current texture pool</param>
/// <param name="stageIndex">The stage number of the specified shader stage</param>
private void CommitBindlessResourcesNvnSeparateTexture(TexturePool pool, int stageIndex)
{
ReadOnlySpan<int> cbData = GetConstantBufferRange<int>(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; continue;
} }
_context.Renderer.Pipeline.RegisterBindlessTextureAndSampler( int textureId = TextureHandle.UnpackTextureId(packedId);
textureId,
texture.HostTexture, pool.UpdateBindlessCombined(_context.Renderer, null, textureId, 0);
texture.ScaleFactor, }
samplerId, }
sampler.GetHostSampler(texture));
/// <summary>
/// Ensures that the texture bindings are visible to the host GPU.
/// Note: this actually performs the binding using the host graphics API.
/// </summary>
/// <param name="pool">The current texture pool</param>
/// <param name="stageIndex">The stage number of the specified shader stage</param>
private void CommitBindlessResourcesNvnSeparateSampler(TexturePool pool, int stageIndex)
{
var samplerPool = _samplerPool;
if (samplerPool == null)
{
return;
}
ReadOnlySpan<int> cbData = GetConstantBufferRange<int>(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)
{
continue;
}
int samplerId;
if (_samplerIndex == SamplerIndex.ViaHeaderIndex)
{
samplerId = TextureHandle.UnpackTextureId(packedId);
}
else
{
samplerId = TextureHandle.UnpackSamplerId(packedId);
}
if (samplerPool.TryGetBindlessSampler(samplerId, out Sampler sampler))
{
_context.Renderer.Pipeline.RegisterBindlessSampler(samplerId, sampler.GetHostSampler(null));
} }
} }
} }
@ -842,15 +961,18 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
/// <param name="stageIndex">Index of the shader stage where the constant buffer belongs</param> /// <param name="stageIndex">Index of the shader stage where the constant buffer belongs</param>
/// <param name="bufferIndex">Index of the constant buffer to read from</param> /// <param name="bufferIndex">Index of the constant buffer to read from</param>
/// <param name="elementIndex">Index of the element on the constant buffer</param> /// <param name="startIndex">Index of the first element on the constant buffer</param>
/// <param name="count">Number of elements to access</param>
/// <returns>The value at the specified buffer and offset</returns> /// <returns>The value at the specified buffer and offset</returns>
private unsafe T ReadConstantBuffer<T>(int stageIndex, int bufferIndex, int elementIndex) where T : unmanaged private unsafe ReadOnlySpan<T> GetConstantBufferRange<T>(int stageIndex, int bufferIndex, int startIndex, int count) where T : unmanaged
{ {
ulong baseAddress = _isCompute ulong baseAddress = _isCompute
? _channel.BufferManager.GetComputeUniformBufferAddress(bufferIndex) ? _channel.BufferManager.GetComputeUniformBufferAddress(bufferIndex)
: _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, bufferIndex); : _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, bufferIndex);
return _channel.MemoryManager.Physical.Read<T>(baseAddress + (ulong)elementIndex * (ulong)sizeof(T)); int typeSize = sizeof(T);
return MemoryMarshal.Cast<byte, T>(_channel.MemoryManager.Physical.GetSpan(baseAddress + (ulong)(startIndex * typeSize), count * typeSize));
} }
/// <summary> /// <summary>

View file

@ -235,29 +235,85 @@ namespace Ryujinx.Graphics.Gpu.Image
int id; int id;
while ((id = ModifiedEntries.GetNextAndClear()) >= 0) while ((id = ModifiedEntries.GetNextAndClear()) >= 0)
{
UpdateBindlessInternal(renderer, id);
}
}
/// <summary>
/// Updates the bindless texture with the given pool ID, if the pool entry has been modified since the last call.
/// </summary>
/// <param name="renderer">Renderer of the current GPU context</param>
/// <param name="samplerPool">Optional sampler pool. If null, the sampler ID is ignored</param>
/// <param name="textureId">ID of the texture</param>
/// <param name="samplerId">ID of the sampler</param>
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 (textureModified)
{
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.RegisterBindlessSampler(samplerId, sampler.GetHostSampler(null));
}
}
}
else if (textureModified)
{
UpdateBindlessInternal(renderer, textureId);
}
}
/// <summary>
/// Updates the bindless texture with the given pool ID.
/// </summary>
/// <param name="renderer">Renderer of the current GPU context</param>
/// <param name="id">ID of the texture</param>
private void UpdateBindlessInternal(IRenderer renderer, int id)
{ {
Texture texture = Items[id] ?? GetValidated(id); Texture texture = Items[id] ?? GetValidated(id);
if (texture != null) if (texture != null)
{ {
if (texture.Target == Target.TextureBuffer) 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); renderer.Pipeline.RegisterBindlessTexture(id, texture.HostTexture, texture.ScaleFactor);
} }
} }
} }
}
/// <summary> /// <summary>
/// Gets the texture at the given <paramref name="id"/> from the cache, /// Gets the texture at the given <paramref name="id"/> from the cache,
@ -431,8 +487,6 @@ namespace Ryujinx.Graphics.Gpu.Image
ulong endAddress = address + size; ulong endAddress = address + size;
UpdateModifiedEntries(address, endAddress);
for (; address < endAddress; address += DescriptorSize) for (; address < endAddress; address += DescriptorSize)
{ {
int id = (int)((address - Address) / DescriptorSize); int id = (int)((address - Address) / DescriptorSize);
@ -451,6 +505,8 @@ namespace Ryujinx.Graphics.Gpu.Image
continue; continue;
} }
UpdateModifiedEntry(id);
if (texture.HasOneReference()) if (texture.HasOneReference())
{ {
_channel.MemoryManager.Physical.TextureCache.AddShortCache(texture, ref cachedDescriptor); _channel.MemoryManager.Physical.TextureCache.AddShortCache(texture, ref cachedDescriptor);
@ -461,6 +517,10 @@ namespace Ryujinx.Graphics.Gpu.Image
texture.DecrementReferenceCount(this, id); texture.DecrementReferenceCount(this, id);
} }
} }
else
{
UpdateModifiedEntry(id);
}
} }
} }

View file

@ -429,6 +429,27 @@ namespace Ryujinx.Graphics.Gpu.Memory
return _gpUniformBuffers[stage].Buffers[index].Address; return _gpUniformBuffers[stage].Buffers[index].Address;
} }
/// <summary>
/// Gets the size of the compute uniform buffer currently bound at the given index.
/// </summary>
/// <param name="index">Index of the uniform buffer binding</param>
/// <returns>The uniform buffer size, or an undefined value if the buffer is not currently bound</returns>
public ulong GetComputeUniformBufferSize(int index)
{
return _cpUniformBuffers.Buffers[index].Size;
}
/// <summary>
/// Gets the size of the graphics uniform buffer currently bound at the given index.
/// </summary>
/// <param name="stage">Index of the shader stage</param>
/// <param name="index">Index of the uniform buffer binding</param>
/// <returns>The uniform buffer size, or an undefined value if the buffer is not currently bound</returns>
public ulong GetGraphicsUniformBufferSize(int stage, int index)
{
return _gpUniformBuffers[stage].Buffers[index].Size;
}
/// <summary> /// <summary>
/// Gets the bounds of the uniform buffer currently bound at the given index. /// Gets the bounds of the uniform buffer currently bound at the given index.
/// </summary> /// </summary>

View file

@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
public BufferDescriptor[][] StorageBufferBindings { get; } public BufferDescriptor[][] StorageBufferBindings { get; }
public BindlessTextureFlags[] BindlessTextureFlags { get; } public BindlessTextureFlags[] BindlessTextureFlags { get; }
public uint[] BindlessIndexedBuffersMask { get; }
public int MaxTextureBinding { get; } public int MaxTextureBinding { get; }
public int MaxImageBinding { get; } public int MaxImageBinding { get; }
@ -37,6 +38,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
StorageBufferBindings = new BufferDescriptor[stageCount][]; StorageBufferBindings = new BufferDescriptor[stageCount][];
BindlessTextureFlags = new BindlessTextureFlags[stageCount]; BindlessTextureFlags = new BindlessTextureFlags[stageCount];
BindlessIndexedBuffersMask = new uint[stageCount];
int maxTextureBinding = -1; int maxTextureBinding = -1;
int maxImageBinding = -1; int maxImageBinding = -1;
@ -100,6 +102,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
StorageBufferBindings[i] = stage.Info.SBuffers.ToArray(); StorageBufferBindings[i] = stage.Info.SBuffers.ToArray();
BindlessTextureFlags[i] = stage.Info.BindlessTextureFlags; BindlessTextureFlags[i] = stage.Info.BindlessTextureFlags;
BindlessIndexedBuffersMask[i] = stage.Info.BindlessIndexedBuffersMask;
} }
MaxTextureBinding = maxTextureBinding; MaxTextureBinding = maxTextureBinding;

View file

@ -189,6 +189,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// Flags indicating if and how bindless texture accesses were translated for the shader stage. /// Flags indicating if and how bindless texture accesses were translated for the shader stage.
/// </summary> /// </summary>
public BindlessTextureFlags BindlessTextureFlags; public BindlessTextureFlags BindlessTextureFlags;
/// <summary>
/// Bit mask indicating which constant buffers are accessed on the shader using indexing to load texture handles.
/// </summary>
public uint BindlessIndexedBuffersMask;
} }
private readonly DiskCacheGuestStorage _guestStorage; private readonly DiskCacheGuestStorage _guestStorage;
@ -805,6 +810,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
images, images,
dataInfo.Stage, dataInfo.Stage,
dataInfo.BindlessTextureFlags, dataInfo.BindlessTextureFlags,
dataInfo.BindlessIndexedBuffersMask,
dataInfo.GeometryVerticesPerPrimitive, dataInfo.GeometryVerticesPerPrimitive,
dataInfo.GeometryMaxOutputVertices, dataInfo.GeometryMaxOutputVertices,
dataInfo.ThreadsPerInputPrimitive, dataInfo.ThreadsPerInputPrimitive,
@ -836,6 +842,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
ImagesCount = (ushort)info.Images.Count, ImagesCount = (ushort)info.Images.Count,
Stage = info.Stage, Stage = info.Stage,
BindlessTextureFlags = info.BindlessTextureFlags, BindlessTextureFlags = info.BindlessTextureFlags,
BindlessIndexedBuffersMask = info.BindlessIndexedBuffersMask,
GeometryVerticesPerPrimitive = (byte)info.GeometryVerticesPerPrimitive, GeometryVerticesPerPrimitive = (byte)info.GeometryVerticesPerPrimitive,
GeometryMaxOutputVertices = (ushort)info.GeometryMaxOutputVertices, GeometryMaxOutputVertices = (ushort)info.GeometryMaxOutputVertices,
ThreadsPerInputPrimitive = (ushort)info.ThreadsPerInputPrimitive, ThreadsPerInputPrimitive = (ushort)info.ThreadsPerInputPrimitive,

View file

@ -5,7 +5,10 @@ namespace Ryujinx.Graphics.Shader
None = 0, None = 0,
BindlessConverted = 1 << 0, BindlessConverted = 1 << 0,
BindlessNvn = 1 << 1, BindlessNvnCombined = 1 << 1,
BindlessFull = 1 << 2, BindlessNvnSeparateTexture = 1 << 2,
BindlessNvnSeparateSampler = 1 << 3,
BindlessFull = 1 << 4,
BindlessNvnAny = BindlessNvnCombined | BindlessNvnSeparateTexture | BindlessNvnSeparateSampler,
} }
} }

View file

@ -14,7 +14,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
public static void Declare(CodeGenContext context, StructuredProgramInfo info) public static void Declare(CodeGenContext context, StructuredProgramInfo info)
{ {
context.AppendLine(context.TargetApi == TargetApi.Vulkan ? "#version 460 core" : "#version 450 core"); 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"); context.AppendLine("#extension GL_ARB_gpu_shader_int64 : enable");
if (context.HostCapabilities.SupportsShaderBallot) if (context.HostCapabilities.SupportsShaderBallot)
@ -36,6 +35,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{ {
context.AppendLine("#extension GL_EXT_nonuniform_qualifier : enable"); context.AppendLine("#extension GL_EXT_nonuniform_qualifier : enable");
} }
else
{
context.AppendLine("#extension GL_ARB_bindless_texture : enable");
}
if (context.Definitions.Stage == ShaderStage.Compute) if (context.Definitions.Stage == ShaderStage.Compute)
{ {

View file

@ -1,16 +1,16 @@
uint Helper_GetBindlessTextureIndex(int nvHandle) uint Helper_GetBindlessTextureIndex(int nvHandle)
{ {
int id = nvHandle & 0xfffff; 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) uint Helper_GetBindlessSamplerIndex(int nvHandle)
{ {
int id = (nvHandle >> 20) & 0xfff; 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) float Helper_GetBindlessScale(int nvHandle)
{ {
return bindless_scales[Helper_GetBindlessTextureIndex(nvHandle)]; return bindless_scales.scales[Helper_GetBindlessTextureIndex(nvHandle)];
} }

View file

@ -11,6 +11,12 @@ namespace Ryujinx.Graphics.Shader
public const int NvnBaseInstanceByteOffset = 0x644; public const int NvnBaseInstanceByteOffset = 0x644;
public const int NvnDrawIndexByteOffset = 0x648; 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 VkConstantBufferSetIndex = 0;
public const int VkStorageBufferSetIndex = 1; public const int VkStorageBufferSetIndex = 1;
public const int VkTextureSetIndex = 2; public const int VkTextureSetIndex = 2;

View file

@ -12,6 +12,7 @@ namespace Ryujinx.Graphics.Shader
public ShaderStage Stage { get; } public ShaderStage Stage { get; }
public BindlessTextureFlags BindlessTextureFlags { get; } public BindlessTextureFlags BindlessTextureFlags { get; }
public uint BindlessIndexedBuffersMask { get; }
public int GeometryVerticesPerPrimitive { get; } public int GeometryVerticesPerPrimitive { get; }
public int GeometryMaxOutputVertices { get; } public int GeometryMaxOutputVertices { get; }
public int ThreadsPerInputPrimitive { get; } public int ThreadsPerInputPrimitive { get; }
@ -29,6 +30,7 @@ namespace Ryujinx.Graphics.Shader
TextureDescriptor[] images, TextureDescriptor[] images,
ShaderStage stage, ShaderStage stage,
BindlessTextureFlags bindlessTextureFlags, BindlessTextureFlags bindlessTextureFlags,
uint bindlessIndexedBuffersMask,
int geometryVerticesPerPrimitive, int geometryVerticesPerPrimitive,
int geometryMaxOutputVertices, int geometryMaxOutputVertices,
int threadsPerInputPrimitive, int threadsPerInputPrimitive,
@ -46,6 +48,7 @@ namespace Ryujinx.Graphics.Shader
Stage = stage; Stage = stage;
BindlessTextureFlags = bindlessTextureFlags; BindlessTextureFlags = bindlessTextureFlags;
BindlessIndexedBuffersMask = bindlessIndexedBuffersMask;
GeometryVerticesPerPrimitive = geometryVerticesPerPrimitive; GeometryVerticesPerPrimitive = geometryVerticesPerPrimitive;
GeometryMaxOutputVertices = geometryMaxOutputVertices; GeometryMaxOutputVertices = geometryMaxOutputVertices;
ThreadsPerInputPrimitive = threadsPerInputPrimitive; ThreadsPerInputPrimitive = threadsPerInputPrimitive;

View file

@ -14,6 +14,7 @@ namespace Ryujinx.Graphics.Shader.Translation
public readonly ShaderStage Stage; public readonly ShaderStage Stage;
public readonly ref FeatureFlags UsedFeatures; public readonly ref FeatureFlags UsedFeatures;
public readonly ref BindlessTextureFlags BindlessTextureFlags; public readonly ref BindlessTextureFlags BindlessTextureFlags;
public readonly ref uint BindlessIndexedBuffersMask;
public readonly ref bool BindlessTexturesAllowed; public readonly ref bool BindlessTexturesAllowed;
public TransformContext( public TransformContext(
@ -27,6 +28,7 @@ namespace Ryujinx.Graphics.Shader.Translation
ShaderStage stage, ShaderStage stage,
ref FeatureFlags usedFeatures, ref FeatureFlags usedFeatures,
ref BindlessTextureFlags bindlessTextureFlags, ref BindlessTextureFlags bindlessTextureFlags,
ref uint bindlessIndexedBuffersMask,
ref bool bindlessTexturesAllowed) ref bool bindlessTexturesAllowed)
{ {
Hfm = hfm; Hfm = hfm;
@ -39,6 +41,7 @@ namespace Ryujinx.Graphics.Shader.Translation
Stage = stage; Stage = stage;
UsedFeatures = ref usedFeatures; UsedFeatures = ref usedFeatures;
BindlessTextureFlags = ref bindlessTextureFlags; BindlessTextureFlags = ref bindlessTextureFlags;
BindlessIndexedBuffersMask = ref bindlessIndexedBuffersMask;
BindlessTexturesAllowed = ref bindlessTexturesAllowed; BindlessTexturesAllowed = ref bindlessTexturesAllowed;
} }
} }

View file

@ -22,6 +22,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
context.ResourceManager, context.ResourceManager,
context.TargetApi, context.TargetApi,
ref context.BindlessTextureFlags, ref context.BindlessTextureFlags,
ref context.BindlessIndexedBuffersMask,
context.BindlessTexturesAllowed, context.BindlessTexturesAllowed,
context.GpuAccessor.QueryTextureBufferIndex()); context.GpuAccessor.QueryTextureBufferIndex());
@ -784,6 +785,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
ResourceManager resourceManager, ResourceManager resourceManager,
TargetApi targetApi, TargetApi targetApi,
ref BindlessTextureFlags bindlessTextureFlags, ref BindlessTextureFlags bindlessTextureFlags,
ref uint bindlessIndexedBuffersMask,
bool bindlessTexturesAllowed, bool bindlessTexturesAllowed,
int textureBufferIndex) int textureBufferIndex)
{ {
@ -797,9 +799,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
{ {
resourceManager.EnsureBindlessBinding(targetApi, texOp.Type, texOp.Inst.IsImage()); 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; return node;
} }
@ -865,7 +866,12 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
return node; 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. // Try to detect a indexed access.
// The access is considered indexed if the handle is loaded with a LDC instruction // 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; 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) if (handleAsgOp.Inst != Instruction.Load || handleAsgOp.StorageKind != StorageKind.ConstantBuffer)
{ {
return false; return false;
@ -883,8 +953,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
Operand ldcSrc0 = handleAsgOp.GetSource(0); Operand ldcSrc0 = handleAsgOp.GetSource(0);
return ldcSrc0.Type == OperandType.Constant && return ldcSrc0.Type == OperandType.Constant &&
resourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int cbSlot) && resourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out cbSlot);
cbSlot == textureBufferIndex;
} }
} }
} }

View file

@ -265,6 +265,7 @@ namespace Ryujinx.Graphics.Shader.Translation
HelperFunctionManager hfm = new(funcs, Definitions.Stage); HelperFunctionManager hfm = new(funcs, Definitions.Stage);
BindlessTextureFlags bindlessTextureFlags = BindlessTextureFlags.None; BindlessTextureFlags bindlessTextureFlags = BindlessTextureFlags.None;
uint bindlessIndexedBuffersMask = 0;
bool bindlessTexturesAllowed = true; bool bindlessTexturesAllowed = true;
for (int i = 0; i < functions.Length; i++) for (int i = 0; i < functions.Length; i++)
@ -302,6 +303,7 @@ namespace Ryujinx.Graphics.Shader.Translation
Definitions.Stage, Definitions.Stage,
ref usedFeatures, ref usedFeatures,
ref bindlessTextureFlags, ref bindlessTextureFlags,
ref bindlessIndexedBuffersMask,
ref bindlessTexturesAllowed); ref bindlessTexturesAllowed);
Optimizer.RunPass(context); Optimizer.RunPass(context);
@ -319,6 +321,7 @@ namespace Ryujinx.Graphics.Shader.Translation
resourceManager, resourceManager,
usedFeatures, usedFeatures,
bindlessTextureFlags, bindlessTextureFlags,
bindlessIndexedBuffersMask,
clipDistancesWritten); clipDistancesWritten);
} }
@ -330,6 +333,7 @@ namespace Ryujinx.Graphics.Shader.Translation
ResourceManager resourceManager, ResourceManager resourceManager,
FeatureFlags usedFeatures, FeatureFlags usedFeatures,
BindlessTextureFlags bindlessTextureFlags, BindlessTextureFlags bindlessTextureFlags,
uint bindlessIndexedBuffersMask,
byte clipDistancesWritten) byte clipDistancesWritten)
{ {
var sInfo = StructuredProgram.MakeStructuredProgram( var sInfo = StructuredProgram.MakeStructuredProgram(
@ -354,6 +358,7 @@ namespace Ryujinx.Graphics.Shader.Translation
resourceManager.GetImageDescriptors(), resourceManager.GetImageDescriptors(),
originalDefinitions.Stage, originalDefinitions.Stage,
bindlessTextureFlags, bindlessTextureFlags,
bindlessIndexedBuffersMask,
geometryVerticesPerPrimitive, geometryVerticesPerPrimitive,
originalDefinitions.MaxOutputVertices, originalDefinitions.MaxOutputVertices,
originalDefinitions.ThreadsPerInputPrimitive, originalDefinitions.ThreadsPerInputPrimitive,
@ -586,6 +591,7 @@ namespace Ryujinx.Graphics.Shader.Translation
resourceManager, resourceManager,
FeatureFlags.None, FeatureFlags.None,
BindlessTextureFlags.None, BindlessTextureFlags.None,
0,
0); 0);
} }
@ -683,6 +689,7 @@ namespace Ryujinx.Graphics.Shader.Translation
resourceManager, resourceManager,
FeatureFlags.RtLayer, FeatureFlags.RtLayer,
BindlessTextureFlags.None, BindlessTextureFlags.None,
0,
0); 0);
} }
} }

View file

@ -3,6 +3,7 @@ using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Numerics; using System.Numerics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -270,16 +271,18 @@ namespace Ryujinx.Graphics.Vulkan
bbiDsc.UpdateBufferImage(0, 0, i, bufferView, DescriptorType.StorageTexelBuffer); 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++) for (int i = 0; i < _samplerRefs.Length; i++)
{ {
var sampler = _samplerRefs[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) if (sd.Sampler.Handle == 0)
@ -289,7 +292,6 @@ namespace Ryujinx.Graphics.Vulkan
bsDsc.UpdateImage(0, 0, i, sd, DescriptorType.Sampler); bsDsc.UpdateImage(0, 0, i, sd, DescriptorType.Sampler);
} }
}
_pipelineLayout = plce.PipelineLayout; _pipelineLayout = plce.PipelineLayout;
_bindlessTextures = btDsc; _bindlessTextures = btDsc;

View file

@ -108,7 +108,7 @@ namespace Ryujinx.Graphics.Vulkan
// All bindless resources have the update after bind flag set, // All bindless resources have the update after bind flag set,
// this is required for Intel, otherwise it just crashes when binding the descriptor sets // this is required for Intel, otherwise it just crashes when binding the descriptor sets
// for bindless resources (maybe because it is above the limit?) // 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( var dsc = _gd.DescriptorSetManager.AllocateDescriptorSet(
_gd.Api, _gd.Api,