Initial bindless texture support

This commit is contained in:
Gabriel A 2023-07-19 22:01:53 -03:00
parent 82a638230e
commit 29e9e90fe8
88 changed files with 3522 additions and 655 deletions

View file

@ -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);

View file

@ -100,6 +100,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register<DrawTextureCommand>(CommandType.DrawTexture);
Register<EndHostConditionalRenderingCommand>(CommandType.EndHostConditionalRendering);
Register<EndTransformFeedbackCommand>(CommandType.EndTransformFeedback);
Register<RegisterBindlessSamplerCommand>(CommandType.RegisterBindlessSampler);
Register<RegisterBindlessTextureCommand>(CommandType.RegisterBindlessTexture);
Register<RegisterBindlessTextureAndSamplerCommand>(CommandType.RegisterBindlessTextureAndSampler);
Register<SetAlphaTestCommand>(CommandType.SetAlphaTest);
Register<SetBlendStateAdvancedCommand>(CommandType.SetBlendStateAdvanced);
Register<SetBlendStateCommand>(CommandType.SetBlendState);

View file

@ -62,6 +62,9 @@
DrawTexture,
EndHostConditionalRendering,
EndTransformFeedback,
RegisterBindlessSampler,
RegisterBindlessTexture,
RegisterBindlessTextureAndSampler,
SetAlphaTest,
SetBlendStateAdvanced,
SetBlendState,

View file

@ -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<RegisterBindlessSamplerCommand>
{
public CommandType CommandType => CommandType.RegisterBindlessSampler;
private int _samplerId;
private TableRef<ISampler> _sampler;
public void Set(int samplerId, TableRef<ISampler> sampler)
{
_samplerId = samplerId;
_sampler = sampler;
}
public static void Run(ref RegisterBindlessSamplerCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
renderer.Pipeline.RegisterBindlessSampler(command._samplerId, command._sampler.GetAs<ThreadedSampler>(threaded)?.Base);
}
}
}

View file

@ -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<RegisterBindlessTextureAndSamplerCommand>
{
public CommandType CommandType => CommandType.RegisterBindlessTextureAndSampler;
private int _textureId;
private int _samplerId;
private TableRef<ITexture> _texture;
private TableRef<ISampler> _sampler;
private float _textureScale;
public void Set(int textureId, TableRef<ITexture> texture, float textureScale, int samplerId, TableRef<ISampler> 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<ThreadedTexture>(threaded)?.Base,
command._textureScale,
command._samplerId,
command._sampler.GetAs<ThreadedSampler>(threaded)?.Base);
}
}
}

View file

@ -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<RegisterBindlessTextureCommand>
{
public CommandType CommandType => CommandType.RegisterBindlessTexture;
private int _textureId;
private TableRef<ITexture> _texture;
private float _textureScale;
public void Set(int textureId, TableRef<ITexture> 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<ThreadedTexture>(threaded)?.Base, command._textureScale);
}
}
}

View file

@ -123,6 +123,24 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand();
}
public void RegisterBindlessSampler(int samplerId, ISampler sampler)
{
_renderer.New<RegisterBindlessSamplerCommand>().Set(samplerId, Ref(sampler));
_renderer.QueueCommand();
}
public void RegisterBindlessTexture(int textureId, ITexture texture, float textureScale)
{
_renderer.New<RegisterBindlessTextureCommand>().Set(textureId, Ref(texture), textureScale);
_renderer.QueueCommand();
}
public void RegisterBindlessTextureAndSampler(int textureId, ITexture texture, float textureScale, int samplerId, ISampler sampler)
{
_renderer.New<RegisterBindlessTextureAndSamplerCommand>().Set(textureId, Ref(texture), textureScale, samplerId, Ref(sampler));
_renderer.QueueCommand();
}
public void SetAlphaTest(bool enable, float reference, CompareOp op)
{
_renderer.New<SetAlphaTestCommand>().Set(enable, reference, op);

View file

@ -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;

View file

@ -89,5 +89,30 @@ namespace Ryujinx.Graphics.Gpu
/// Maximum size that an storage buffer is assumed to have when the correct size is unknown.
/// </summary>
public const ulong MaxUnknownStorageSize = 0x100000;
/// <summary>
/// Maximum width and height for 1D, 2D and cube textures, including array and multisample variants.
/// </summary>
public const int MaxTextureSize = 0x4000;
/// <summary>
/// Maximum width, height and depth for 3D textures.
/// </summary>
public const int Max3DTextureSize = 0x800;
/// <summary>
/// Maximum layers for array textures.
/// </summary>
public const int MaxArrayTextureLayers = 0x800;
/// <summary>
/// Maximum width (effectively the size in pixels) for buffer textures.
/// </summary>
public const int MaxBufferTextureSize = 0x8000000;
/// <summary>
/// Alignment in bytes for pitch linear textures.
/// </summary>
public const int LinearStrideAlignment = 0x20;
}
}

View file

@ -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);

View file

@ -16,7 +16,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
/// <returns>Texture target value</returns>
public static Target GetTarget(SamplerType type)
{
type &= ~(SamplerType.Indexed | SamplerType.Shadow);
type &= ~SamplerType.Shadow;
switch (type)
{

View file

@ -0,0 +1,197 @@
using System.Numerics;
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// Represents a list of bits.
/// </summary>
class BitMap
{
private const int IntSize = 64;
private const int IntShift = 6;
private const int IntMask = IntSize - 1;
private readonly ulong[] _masks;
/// <summary>
/// Creates a new instance of the bitmap.
/// </summary>
/// <param name="count">Size (in bits) that the bitmap can hold</param>
public BitMap(int count)
{
_masks = new ulong[(count + IntMask) / IntSize];
}
/// <summary>
/// Sets a bit to 1.
/// </summary>
/// <param name="bit">Index of the bit</param>
/// <returns>True if the bit value was modified by this operation, false otherwise</returns>
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;
}
/// <summary>
/// Sets a range of bits to 1.
/// </summary>
/// <param name="start">Inclusive index of the first bit to set</param>
/// <param name="end">Inclusive index of the last bit to set</param>
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;
}
}
/// <summary>
/// Sets a bit to 0.
/// </summary>
/// <param name="bit">Index of the bit</param>
public void Clear(int bit)
{
int wordIndex = bit / IntSize;
int wordBit = bit & IntMask;
ulong wordMask = 1UL << wordBit;
_masks[wordIndex] &= ~wordMask;
}
/// <summary>
/// Finds the first bit with a value of 0.
/// </summary>
/// <returns>Index of the bit with value 0, or -1 if none found</returns>
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;
/// <summary>
/// Starts iterating from bit 0.
/// </summary>
public void BeginIterating()
{
_iterIndex = 0;
_iterMask = _masks.Length != 0 ? _masks[0] : 0;
}
/// <summary>
/// Gets the next bit set to 1.
/// </summary>
/// <returns>Index of the bit, or -1 if none found</returns>
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;
}
/// <summary>
/// Gets the next bit set to 1, while also setting it to 0.
/// </summary>
/// <returns>Index of the bit, or -1 if none found</returns>
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;
}
}
}

View file

@ -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;
/// <summary>
/// The maximum ID value of resources on the pool (inclusive).
/// </summary>
@ -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;
}
/// <summary>
/// Updates the set of entries that have been modified.
/// </summary>
/// <param name="address">Start address of the region of the pool that has been modfied</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)
{
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);
}
/// <summary>
/// Forces all entries as modified, to be updated if any shader uses bindless textures.
/// </summary>
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);

View file

@ -113,6 +113,24 @@ namespace Ryujinx.Graphics.Gpu.Image
return (CompareOp)(((Word0 >> 10) & 7) + 1);
}
/// <summary>
/// Unpacks the font filter width.
/// </summary>
/// <returns>Font filter width</returns>
public int UnpackFontFilterWidth()
{
return (int)(Word0 >> 14) & 7;
}
/// <summary>
/// Unpacks the font filter height.
/// </summary>
/// <returns>Font filter height</returns>
public int UnpackFontFilterHeight()
{
return (int)(Word0 >> 17) & 7;
}
/// <summary>
/// Unpacks and converts the maximum anisotropy value used for texture anisotropic filtering.
/// </summary>

View file

@ -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;
}
/// <summary>
/// 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.
/// </summary>
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));
}
}
}
/// <summary>
/// Gets the sampler at the given <paramref name="id"/> from the cache,
/// or creates a new one if not found.
/// This will return null if the sampler entry is considered invalid.
/// </summary>
/// <param name="id">Index of the sampler on the pool</param>
/// <returns>Sampler for the given pool index</returns>
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);
}
/// <summary>
/// Implementation of the sampler pool range invalidation.
/// </summary>
@ -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);

View file

@ -1462,6 +1462,19 @@ namespace Ryujinx.Graphics.Gpu.Image
DisposeTextures();
HostTexture = hostTexture;
ForceTexturePoolUpdate();
}
/// <summary>
/// Forces the entries on all texture pool where this texture is present to be updated.
/// </summary>
private void ForceTexturePoolUpdate()
{
foreach (TexturePoolOwner poolOwner in _poolOwners)
{
poolOwner.Pool.ForceModifiedEntry(poolOwner.ID);
}
}
/// <summary>

View file

@ -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<int> cachedTextureBuffer = Span<int>.Empty;
ReadOnlySpan<int> cachedSamplerBuffer = Span<int>.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,8 +601,15 @@ namespace Ryujinx.Graphics.Gpu.Image
state.Texture = hostTexture;
state.Sampler = 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;
state.CachedSampler = sampler;
@ -703,6 +766,93 @@ namespace Ryujinx.Graphics.Gpu.Image
return specStateMatches;
}
/// <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="stage">The shader stage using the textures to be bound</param>
/// <param name="stageIndex">The stage number of the specified shader stage</param>
private void 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<int>(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));
}
}
}
/// <summary>
/// Reads a value from a constant buffer.
/// </summary>
/// <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="elementIndex">Index of the element on the constant buffer</param>
/// <returns>The value at the specified buffer and offset</returns>
private unsafe T ReadConstantBuffer<T>(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<T>(baseAddress + (ulong)elementIndex * (ulong)sizeof(T));
}
/// <summary>
/// Gets the texture descriptor for a given texture handle.
/// </summary>
@ -749,14 +899,13 @@ namespace Ryujinx.Graphics.Gpu.Image
}
/// <summary>
/// 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.
/// </summary>
/// <param name="stageIndex">The number of the shader stage where the texture is bound</param>
/// <param name="wordOffset">A word offset of the handle on the buffer (the "fake" shader handle)</param>
/// <param name="wordOffset">The word offset of the handle on the buffer</param>
/// <param name="textureBufferIndex">Index of the constant buffer holding the texture handles</param>
/// <param name="samplerBufferIndex">Index of the constant buffer holding the sampler handles</param>
/// <returns>The packed texture and sampler ID (the real texture handle)</returns>
/// <returns>The combined texture and sampler handle</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int ReadPackedId(int stageIndex, int wordOffset, int textureBufferIndex, int samplerBufferIndex)
{

View file

@ -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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="renderer">Renderer of the current GPU context</param>
/// <param name="activeSamplerPool">The currently active sampler pool</param>
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);
}
}
}
}
/// <summary>
/// Gets the texture at the given <paramref name="id"/> from the cache,
/// or creates a new one if not found.
/// This will return null if the texture entry is considered invalid.
/// </summary>
/// <param name="id">Index of the texture on the pool</param>
/// <returns>Texture for the given pool index</returns>
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;
}
}
/// <summary>
/// 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
}
}
/// <summary>
/// Forces a entry as modified, to be updated if any shader uses bindless textures.
/// </summary>
/// <param name="id">ID of the entry to be updated</param>
public void ForceModifiedEntry(int id)
{
ModifiedEntries.Set(id);
}
/// <summary>
/// Gets texture information from a texture descriptor.
/// </summary>

View file

@ -0,0 +1,107 @@
using Ryujinx.Graphics.GAL;
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// Texture validation result.
/// </summary>
enum TextureValidationResult
{
Valid,
InvalidSize,
InvalidTarget,
InvalidFormat
}
/// <summary>
/// Texture validation utilities.
/// </summary>
static class TextureValidation
{
/// <summary>
/// Checks if the texture parameters are valid.
/// </summary>
/// <param name="info">Texture parameters</param>
/// <returns>Validation result</returns>
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;
}
/// <summary>
/// Checks if a sampler can be used in combination with a given texture.
/// </summary>
/// <param name="info">Texture parameters</param>
/// <param name="sampler">Sampler parameters</param>
/// <returns>True if they can be used together, false otherwise</returns>
public static bool IsSamplerCompatible(TextureInfo info, SamplerDescriptor sampler)
{
if (info.FormatInfo.Format.IsDepthOrStencil() != (sampler.UnpackCompareMode() == CompareMode.CompareRToTexture))
{
return false;
}
return true;
}
}
}

View file

@ -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));
}
/// <summary>
/// Sets the buffer storage of a bindless buffer texture. This will be bound when the buffer manager commits bindings.
/// </summary>
/// <param name="texture">Buffer texture</param>
/// <param name="address">Address of the buffer in memory</param>
/// <param name="size">Size of the buffer in bytes</param>
/// <param name="bindingInfo">Binding info for the buffer texture</param>
/// <param name="format">Format of the buffer texture</param>
/// <param name="isImage">Whether the binding is for an image or a sampler</param>
/// <param name="textureid">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));
}
/// <summary>
/// Force all bound textures and images to be rebound the next time CommitBindings is called.
/// </summary>

View file

@ -44,6 +44,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary>
public bool IsImage { get; }
/// <summary>
/// Indicates if the texture should be bound as a bindless texture.
/// </summary>
public bool AsBindless { get; }
/// <summary>
/// For bindless textures, indicates the texture ID.
/// </summary>
public int TextureId { get; }
/// <summary>
/// Create a new buffer texture binding.
/// </summary>
@ -70,6 +80,31 @@ namespace Ryujinx.Graphics.Gpu.Memory
BindingInfo = bindingInfo;
Format = format;
IsImage = isImage;
AsBindless = false;
TextureId = 0;
}
/// <summary>
/// Create a new bindless buffer texture binding.
/// </summary>
/// <param name="texture">Buffer texture</param>
/// <param name="address">Base address</param>
/// <param name="size">Size in bytes</param>
/// <param name="bindingInfo">Binding info</param>
/// <param name="format">Binding format</param>
/// <param name="isImage">Whether the binding is for an image or a sampler</param>
/// <param name="textureId">ID of the texture on the pool</param>
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;
}
}
}

View file

@ -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;

View file

@ -123,6 +123,14 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return _oldSpecState.GetTextureTarget(_stageIndex, handle, cbufSlot).ConvertSamplerType();
}
/// <inheritdoc/>
public int QueryTextureBufferIndex()
{
byte textureBufferIndex = _oldSpecState.GetTextureBufferIndex();
_newSpecState.RecordTextureBufferIndex(textureBufferIndex);
return textureBufferIndex;
}
/// <inheritdoc/>
public bool QueryTextureCoordNormalized(int handle, int cbufSlot)
{

View file

@ -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.
/// </summary>
public bool UsesDrawParameters;
/// <summary>
/// Flags indicating if and how bindless texture accesses were translated for the shader stage.
/// </summary>
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,

View file

@ -134,6 +134,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
return GetTextureDescriptor(handle, cbufSlot).UnpackTextureTarget().ConvertSamplerType();
}
/// <inheritdoc/>
public int QueryTextureBufferIndex()
{
byte textureBufferIndex = (byte)_state.PoolState.TextureBufferIndex;
_state.SpecializationState?.RecordTextureBufferIndex(textureBufferIndex);
return textureBufferIndex;
}
/// <inheritdoc/>
public bool QueryTextureCoordNormalized(int handle, int cbufSlot)
{

View file

@ -40,7 +40,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="vertexAsCompute">Indicates that the vertex shader will be emulated on a compute shader</param>
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;

View file

@ -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<ResourceDescriptor>[] _resourceDescriptors;
private readonly List<ResourceUsage>[] _resourceUsages;
private List<ResourceDescriptor>[] _resourceDescriptors;
private List<ResourceUsage>[] _resourceUsages;
/// <summary>
/// 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
}
}
/// <summary>
/// Adds an array of resource descriptors to the list of descriptors.
/// </summary>
/// <param name="stages">Shader stages where the resource is used</param>
/// <param name="type">Type of the resource</param>
/// <param name="setIndex">Descriptor set number where the resource will be bound</param>
/// <param name="binding">Binding number where the resource will be bound</param>
/// <param name="count">Number of array elements</param>
private void AddArrayDescriptor(ResourceStages stages, ResourceType type, int setIndex, int binding, int count)
{
_resourceDescriptors[setIndex].Add(new ResourceDescriptor(binding, count, type, stages));
}
/// <summary>
/// Adds two interleaved groups of resources to the list of descriptors.
/// </summary>
@ -235,10 +285,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <returns>Shader information</returns>
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);
}
}

View file

@ -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;
/// <summary>
/// Compute engine state.
@ -323,6 +325,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
state.Value.CoordNormalized = coordNormalized;
}
/// <summary>
/// Records the index of the constant buffer with texture handles.
/// </summary>
/// <param name="index">Index of the constant buffer with texture handles</param>
public void RecordTextureBufferIndex(byte index)
{
_textureBufferIndex = index;
_queriedState |= QueriedStateFlags.TextureBufferIndex;
}
/// <summary>
/// Indicates that the format of a given texture was used during the shader translation process.
/// </summary>
@ -385,18 +397,30 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="stageIndex">Shader stage where the texture is used</param>
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
/// <returns>Format of the given texture, and whether that format is a sRGB format</returns>
public (uint, bool) GetFormat(int stageIndex, int handle, int cbufSlot)
{
TextureSpecializationState state = GetTextureSpecState(stageIndex, handle, cbufSlot).Value;
return (state.Format, state.FormatSrgb);
}
/// <summary>
/// Gets the index of the constant buffer with texture handles.
/// </summary>
/// <returns>Index of the constant buffer with texture handles</returns>
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;
}
/// <summary>
/// Gets the recorded target of a given texture.
/// </summary>
/// <param name="stageIndex">Shader stage where the texture is used</param>
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
/// <returns>Target of the given texture</returns>
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
/// <param name="stageIndex">Shader stage where the texture is used</param>
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
/// <returns>Normalization state of the given texture</returns>
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);

View file

@ -5,6 +5,7 @@ namespace Ryujinx.Graphics.OpenGL
{
static class HwCapabilities
{
private static readonly Lazy<bool> _supportsArbBindlessTexture = new Lazy<bool>(() => HasExtension("GL_ARB_bindless_texture"));
private static readonly Lazy<bool> _supportsAlphaToCoverageDitherControl = new(() => HasExtension("GL_NV_alpha_to_coverage_dither_control"));
private static readonly Lazy<bool> _supportsAstcCompression = new(() => HasExtension("GL_KHR_texture_compression_astc_ldr"));
private static readonly Lazy<bool> _supportsBlendEquationAdvanced = new(() => HasExtension("GL_NV_blend_equation_advanced"));
@ -14,6 +15,7 @@ namespace Ryujinx.Graphics.OpenGL
private static readonly Lazy<bool> _supportsGeometryShaderPassthrough = new(() => HasExtension("GL_NV_geometry_shader_passthrough"));
private static readonly Lazy<bool> _supportsImageLoadFormatted = new(() => HasExtension("GL_EXT_shader_image_load_formatted"));
private static readonly Lazy<bool> _supportsIndirectParameters = new(() => HasExtension("GL_ARB_indirect_parameters"));
private static readonly Lazy<bool> _supportsNvBindlessTexture = new Lazy<bool>(() => HasExtension("GL_NV_bindless_texture"));
private static readonly Lazy<bool> _supportsParallelShaderCompile = new(() => HasExtension("GL_ARB_parallel_shader_compile"));
private static readonly Lazy<bool> _supportsPolygonOffsetClamp = new(() => HasExtension("GL_EXT_polygon_offset_clamp"));
private static readonly Lazy<bool> _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;

View file

@ -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
{
/// <summary>
/// Host bindless texture handle manager.
/// </summary>
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;
/// <summary>
/// Second level block state.
/// </summary>
private struct Block
{
public int Index;
public int ReferenceCount;
}
private readonly Block[] _blocks;
private readonly Dictionary<int, List<int>> _texturesOnBlocks;
/// <summary>
/// Handle entry accessed by the shader.
/// </summary>
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<int> _textureList; // First level.
private readonly TypedBuffer<HandleEntry> _handleList; // Second level.
private readonly ITexture _bufferTextureForTextureList;
private readonly ITexture _bufferTextureForHandleList;
/// <summary>
/// Creates a new instance of the host bindless texture handle manager.
/// </summary>
/// <param name="renderer">Renderer</param>
public BindlessHandleManager(OpenGLRenderer renderer)
{
_freeList = new BitMap();
_blocks = new Block[0x100000];
_texturesOnBlocks = new Dictionary<int, List<int>>();
_textureList = new TypedBuffer<int>(renderer, 0x100000);
_handleList = new TypedBuffer<HandleEntry>(renderer, BlockSize);
_bufferTextureForTextureList = CreateBufferTexture(renderer, _textureList);
_bufferTextureForHandleList = CreateBufferTexture(renderer, _handleList);
}
/// <summary>
/// Creates a buffer texture with the provided buffer.
/// </summary>
/// <typeparam name="T">Type of the data on the buffer</typeparam>
/// <param name="renderer">Renderer</param>
/// <param name="buffer">Buffer</param>
/// <returns>Buffer texture</returns>
private static ITexture CreateBufferTexture<T>(OpenGLRenderer renderer, TypedBuffer<T> buffer) where T : unmanaged
{
int bytesPerPixel = Unsafe.SizeOf<T>();
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;
}
/// <summary>
/// Binds the multi-level handle table buffer textures on the host.
/// </summary>
/// <param name="renderer">Renderer</param>
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);
}
/// <summary>
/// Adds a new host handle to the table.
/// </summary>
/// <param name="textureId">Guest ID of the texture the handle belongs to</param>
/// <param name="samplerId">Guest ID of the sampler the handle belongs to</param>
/// <param name="handle">Host handle</param>
/// <param name="scale">Texture scale factor</param>
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<int> list))
{
_texturesOnBlocks.Add(subIndex, list = new List<int>());
}
list.Add(tableIndex);
if (_handleList.EnsureCapacity((blockIndex + 1) * BlockSize))
{
_bufferTextureForHandleList.SetStorage(_handleList.GetBufferRange());
}
_handleList.Write(blockIndex * BlockSize + subIndex, new HandleEntry(handle, scale));
}
/// <summary>
/// Removes a handle from the table.
/// </summary>
/// <param name="textureId">Guest ID of the texture the handle belongs to</param>
/// <param name="samplerId">Guest ID of the sampler the handle belongs to</param>
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<int> list))
{
for (int i = 0; i < list.Count; i++)
{
PutBlockIndex(list[i]);
}
_texturesOnBlocks.Remove(subIndex);
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="tableIndex">Index pointing inside the first level table, where the other index is located</param>
/// <returns>The index of a block on the second level table</returns>
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;
}
/// <summary>
/// Indicates that a given block was dereferenced, eventually freeing it if no longer in use.
/// </summary>
/// <param name="tableIndex">Index of the block index on the first level table</param>
private void PutBlockIndex(int tableIndex)
{
if (--_blocks[tableIndex].ReferenceCount == 0)
{
_freeList.Clear(_blocks[tableIndex].Index - 1);
_blocks[tableIndex].Index = 0;
}
}
/// <summary>
/// Assembles a index from the low bits of the texture and sampler ID, used for the first level indexing.
/// </summary>
/// <param name="textureId">Texture ID</param>
/// <param name="samplerId">Sampler ID</param>
/// <returns>The first level index</returns>
private static int GetTableIndex(int textureId, int samplerId) => (textureId >> 8) | ((samplerId >> 8) << 12);
/// <summary>
/// Assembles a index from the low bits of the texture and sampler ID, used for the second level indexing.
/// </summary>
/// <param name="textureId">Texture ID</param>
/// <param name="samplerId">Sampler ID</param>
/// <returns>The second level index</returns>
private static int GetSubIndex(int textureId, int samplerId) => (textureId & 0xff) | ((samplerId & 0xff) << 8);
}
}

View file

@ -0,0 +1,166 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Graphics.GAL;
using System.Collections.Generic;
namespace Ryujinx.Graphics.OpenGL.Image
{
/// <summary>
/// Host bindless texture manager.
/// </summary>
class BindlessManager
{
private readonly OpenGLRenderer _renderer;
private BindlessHandleManager _handleManager;
private readonly Dictionary<int, (ITexture, float)> _separateTextures;
private readonly Dictionary<int, ISampler> _separateSamplers;
private readonly HashSet<long> _handles = new HashSet<long>();
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<int> toRemove = new List<int>();
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);
}
}
}
}

View file

@ -0,0 +1,189 @@
using System.Collections.Generic;
using System.Numerics;
namespace Ryujinx.Graphics.OpenGL.Image
{
/// <summary>
/// Represents a list of bits.
/// </summary>
class BitMap
{
private const int IntSize = 64;
private const int IntMask = IntSize - 1;
private readonly List<ulong> _masks;
/// <summary>
/// Creates a new instance of the bitmap.
/// </summary>
public BitMap()
{
_masks = new List<ulong>(0);
}
/// <summary>
/// Creates a new instance of the bitmap.
/// </summary>
/// <param name="initialCapacity">Initial size (in bits) that the bitmap can hold</param>
public BitMap(int initialCapacity)
{
int count = (initialCapacity + IntMask) / IntSize;
_masks = new List<ulong>(count);
while (count-- > 0)
{
_masks.Add(0);
}
}
/// <summary>
/// Sets a bit on the list to 1.
/// </summary>
/// <param name="bit">Index of the bit</param>
/// <returns>True if the bit value was modified by this operation, false otherwise</returns>
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;
}
/// <summary>
/// Sets a bit on the list to 0.
/// </summary>
/// <param name="bit">Index of the bit</param>
public void Clear(int bit)
{
EnsureCapacity(bit + 1);
int wordIndex = bit / IntSize;
int wordBit = bit & IntMask;
ulong wordMask = 1UL << wordBit;
_masks[wordIndex] &= ~wordMask;
}
/// <summary>
/// Finds the first bit on the list with a value of 0.
/// </summary>
/// <returns>Index of the bit with value 0</returns>
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;
}
/// <summary>
/// Ensures that the array can hold a given number of bits, resizing as needed.
/// </summary>
/// <param name="size">Number of bits</param>
private void EnsureCapacity(int size)
{
while (_masks.Count * IntSize < size)
{
_masks.Add(0);
}
}
private int _iterIndex;
private ulong _iterMask;
/// <summary>
/// Starts iterating from bit 0.
/// </summary>
public void BeginIterating()
{
_iterIndex = 0;
_iterMask = _masks.Count != 0 ? _masks[0] : 0;
}
/// <summary>
/// Gets the next bit set to 1 on the list.
/// </summary>
/// <returns>Index of the bit, or -1 if none found</returns>
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;
}
/// <summary>
/// Gets the next bit set to 1 on the list, while also setting it to 0.
/// </summary>
/// <returns>Index of the bit, or -1 if none found</returns>
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;
}
}
}

View file

@ -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<int, (BindlessManager, long, int)> _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;
}
}
}

View file

@ -97,6 +97,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
public void Dispose()
{
RevokeBindlessAccess();
if (Handle != 0)
{
GL.DeleteTexture(Handle);

View file

@ -890,6 +890,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
/// </summary>
public void Release()
{
RevokeBindlessAccess();
bool hadHandle = Handle != 0;
if (_parent.DefaultView != this)

View file

@ -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);

View file

@ -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<BufferRange> buffers)
{
if (_tfEnabled)

View file

@ -0,0 +1,79 @@
using Ryujinx.Graphics.GAL;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.OpenGL
{
/// <summary>
/// Represents a buffer that stores data of a given type.
/// </summary>
/// <typeparam name="T">Type of the buffer data</typeparam>
class TypedBuffer<T> where T : unmanaged
{
private readonly OpenGLRenderer _renderer;
private BufferHandle _buffer;
/// <summary>
/// Size of the buffer in bytes.
/// </summary>
public int Size { get; private set; }
/// <summary>
/// Creates a new instance of the typed buffer.
/// </summary>
/// <param name="renderer">Renderer</param>
/// <param name="count">Number of data elements on the buffer</param>
public TypedBuffer(OpenGLRenderer renderer, int count)
{
_renderer = renderer;
_buffer = renderer.CreateBuffer(Size = count * Unsafe.SizeOf<T>(), BufferHandle.Null);
renderer.SetBufferData(_buffer, 0, new byte[Size]);
}
/// <summary>
/// Writes data into a given buffer index.
/// </summary>
/// <param name="index">Index to write the data</param>
/// <param name="value">Data to be written</param>
public void Write(int index, T value)
{
_renderer.SetBufferData(_buffer, index * Unsafe.SizeOf<T>(), MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1)));
}
/// <summary>
/// Ensures that the buffer can hold a given number of elements.
/// </summary>
/// <param name="count">Number of elements</param>
/// <returns>True if the buffer was resized and needs to be rebound, false otherwise</returns>
public bool EnsureCapacity(int count)
{
int size = count * Unsafe.SizeOf<T>();
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;
}
/// <summary>
/// Gets a buffer range covering the whole buffer.
/// </summary>
/// <returns>The buffer range</returns>
public BufferRange GetBufferRange()
{
return new BufferRange(_buffer, 0, Size);
}
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.Graphics.Shader
{
public enum BindlessTextureFlags : ushort
{
None = 0,
BindlessConverted = 1 << 0,
BindlessNvn = 1 << 1,
BindlessFull = 1 << 2,
}
}

View file

@ -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;
}
}
}

View file

@ -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();

View file

@ -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<TextureDefinition> definitions)
private static void DeclareSamplers(CodeGenContext context, IEnumerable<TextureDefinition> 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;
private static void DeclareImages(CodeGenContext context, IEnumerable<TextureDefinition> definitions)
if (sampler.ArraySize == 0)
{
int arraySize = 0;
foreach (var definition in definitions)
{
string indexExpr = string.Empty;
if (definition.Type.HasFlag(SamplerType.Indexed))
{
if (arraySize == 0)
{
arraySize = ResourceManager.SamplerArraySize;
suffix = "[]";
}
else if (--arraySize != 0)
else if (sampler.ArraySize != 1)
{
continue;
suffix = $"[{(uint)sampler.ArraySize}]";
}
indexExpr = $"[{NumberFormatter.FormatInt(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;
}
string imageTypeName = definition.Type.ToGlslImageType(definition.Format.GetComponentType());
context.AppendLine($"layout ({layout}binding = {sampler.Binding}) uniform {samplerTypeName} {sampler.Name}{suffix};");
}
}
if (definition.Flags.HasFlag(TextureUsageFlags.ImageCoherent))
private static void DeclareImages(CodeGenContext context, IEnumerable<TextureDefinition> images)
{
foreach (var image in images)
{
string imageTypeName = image.Type.ToGlslImageType(image.Format.GetComponentType());
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};");
}
}

View file

@ -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);
}

View file

@ -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)];
}

View file

@ -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";

View file

@ -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})";
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}))";
}
}
return name;
private static string GetBindlessImage(CodeGenContext context, SamplerType type, AggregateType componentType, string bindlessHandle)
{
string imageType = type.ToGlslImageType(componentType);
if (context.TargetApi == TargetApi.Vulkan)
{
string textureIndex = $"{HelperFunctionNames.GetBindlessTextureIndexVk}({bindlessHandle})";
string bindlessImageArrayName = context.OperandManager.BindlessImages[type];
return $"{bindlessImageArrayName}[{textureIndex}]";
}
else
{
return $"{imageType}({HelperFunctionNames.GetBindlessHandle}({bindlessHandle}))";
}
}
private static string GetImageName(ShaderProperties resourceDefinitions, AstTextureOperation texOp, string indexExpr)
private static string GetSamplerName(ShaderProperties resourceDefinitions, AstTextureOperation texOp)
{
string name = resourceDefinitions.Images[texOp.Binding].Name;
if (texOp.Type.HasFlag(SamplerType.Indexed))
{
name = $"{name}[{indexExpr}]";
return resourceDefinitions.Textures[SetBindingPair.Unpack(texOp.Binding)].Name;
}
return name;
private static string GetImageName(ShaderProperties resourceDefinitions, AstTextureOperation texOp)
{
return resourceDefinitions.Images[SetBindingPair.Unpack(texOp.Binding)].Name;
}
private static string GetMask(int index)

View file

@ -13,9 +13,15 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{
private readonly Dictionary<AstOperand, string> _locals;
public Dictionary<SamplerType, string> BindlessTextures { get; }
public Dictionary<SamplerType, string> BindlessImages { get; }
public OperandManager()
{
_locals = new Dictionary<AstOperand, string>();
_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;

View file

@ -29,15 +29,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
public Dictionary<int, Instruction> ConstantBuffers { get; } = new();
public Dictionary<int, Instruction> StorageBuffers { get; } = new();
public Dictionary<int, Instruction> LocalMemories { get; } = new();
public Dictionary<int, Instruction> SharedMemories { get; } = new();
public Dictionary<int, SamplerType> SamplersTypes { get; } = new();
public Dictionary<int, (Instruction, Instruction, Instruction)> Samplers { get; } = new();
public Dictionary<int, (Instruction, Instruction)> Images { get; } = new();
public Dictionary<IoDefinition, Instruction> Inputs { get; } = new();
public Dictionary<SamplerType, (Instruction, Instruction, Instruction, Instruction)> BindlessTextures { get; } = new();
public Dictionary<SamplerType, (Instruction, Instruction, Instruction)> BindlessImages { get; } = new();
public Dictionary<IoDefinition, Instruction> Outputs { get; } = new();
public Dictionary<IoDefinition, Instruction> InputsPerPatch { get; } = new();
public Dictionary<IoDefinition, Instruction> OutputsPerPatch { get; } = new();

View file

@ -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<int, SpvInstruction> 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<BufferDefinition> buffers)
private static void DeclareConstantBuffers(CodeGenContext context, IReadOnlyDictionary<SetBindingPair, BufferDefinition> buffers)
{
DeclareBuffers(context, buffers, isBuffer: false);
}
private static void DeclareStorageBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers)
private static void DeclareStorageBuffers(CodeGenContext context, IReadOnlyDictionary<SetBindingPair, BufferDefinition> buffers)
{
DeclareBuffers(context, buffers, isBuffer: true);
}
private static void DeclareBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers, bool isBuffer)
private static void DeclareBuffers(CodeGenContext context, IReadOnlyDictionary<SetBindingPair, BufferDefinition> buffers, bool isBuffer)
{
HashSet<SpvInstruction> 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,32 +145,29 @@ 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<TextureDefinition> samplers)
private static void DeclareSamplers(CodeGenContext context, IReadOnlyDictionary<SetBindingPair, TextureDefinition> 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
{
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}\"."),
};
SpvInstruction imageType;
SpvInstruction sampledImageType;
var imageType = context.TypeImage(
if (sampler.Type != SamplerType.None)
{
var dim = GetDim(sampler.Type);
imageType = context.TypeImage(
context.TypeFP32(),
dim,
sampler.Type.HasFlag(SamplerType.Shadow),
@ -179,12 +176,40 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
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<TextureDefinition> images)
private static void DeclareImages(CodeGenContext context, IReadOnlyDictionary<SetBindingPair, TextureDefinition> 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);

View file

@ -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);
}
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];
var image = context.Load(imageType, imageVariable);
bindlessIndex = null;
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);
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);
image = context.Load(imageType, imagePointer);
}
else
{
(var imageType, var imageVariable) = context.Images[texOp.Binding];
var image = context.Load(imageType, imageVariable);
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);
}
SpvInstruction image;
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];
var image = context.Load(sampledImageType, sampledImageVariable);
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;

View file

@ -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));

View file

@ -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;
}
}

View file

@ -404,6 +404,15 @@ namespace Ryujinx.Graphics.Shader
return SamplerType.Texture2D;
}
/// <summary>
/// Queries the number of the constant buffer where the texture handles are located.
/// </summary>
/// <returns>Constant buffer where the texture handles are located</returns>
int QueryTextureBufferIndex()
{
return 2; // NVN default.
}
/// <summary>
/// Queries texture coordinate normalization information.
/// </summary>

View file

@ -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;
}
}
}

View file

@ -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;

View file

@ -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 });
}
}
}
}

View file

@ -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;

View file

@ -10,6 +10,8 @@
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\GetBindlessHandle.glsl" />
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\GetBindlessHandleVk.glsl" />
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\MultiplyHighS32.glsl" />
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\MultiplyHighU32.glsl" />
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\SwizzleAdd.glsl" />

View file

@ -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

View file

@ -11,6 +11,7 @@ namespace Ryujinx.Graphics.Shader
public ReadOnlyCollection<TextureDescriptor> 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;

View file

@ -0,0 +1,46 @@
using System;
namespace Ryujinx.Graphics.Shader
{
readonly struct SetBindingPair : IEquatable<SetBindingPair>
{
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));
}
}
}

View file

@ -4,48 +4,48 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{
class ShaderProperties
{
private readonly Dictionary<int, BufferDefinition> _constantBuffers;
private readonly Dictionary<int, BufferDefinition> _storageBuffers;
private readonly Dictionary<int, TextureDefinition> _textures;
private readonly Dictionary<int, TextureDefinition> _images;
private readonly Dictionary<SetBindingPair, BufferDefinition> _constantBuffers;
private readonly Dictionary<SetBindingPair, BufferDefinition> _storageBuffers;
private readonly Dictionary<SetBindingPair, TextureDefinition> _textures;
private readonly Dictionary<SetBindingPair, TextureDefinition> _images;
private readonly Dictionary<int, MemoryDefinition> _localMemories;
private readonly Dictionary<int, MemoryDefinition> _sharedMemories;
public IReadOnlyDictionary<int, BufferDefinition> ConstantBuffers => _constantBuffers;
public IReadOnlyDictionary<int, BufferDefinition> StorageBuffers => _storageBuffers;
public IReadOnlyDictionary<int, TextureDefinition> Textures => _textures;
public IReadOnlyDictionary<int, TextureDefinition> Images => _images;
public IReadOnlyDictionary<SetBindingPair, BufferDefinition> ConstantBuffers => _constantBuffers;
public IReadOnlyDictionary<SetBindingPair, BufferDefinition> StorageBuffers => _storageBuffers;
public IReadOnlyDictionary<SetBindingPair, TextureDefinition> Textures => _textures;
public IReadOnlyDictionary<SetBindingPair, TextureDefinition> Images => _images;
public IReadOnlyDictionary<int, MemoryDefinition> LocalMemories => _localMemories;
public IReadOnlyDictionary<int, MemoryDefinition> SharedMemories => _sharedMemories;
public ShaderProperties()
{
_constantBuffers = new Dictionary<int, BufferDefinition>();
_storageBuffers = new Dictionary<int, BufferDefinition>();
_textures = new Dictionary<int, TextureDefinition>();
_images = new Dictionary<int, TextureDefinition>();
_constantBuffers = new Dictionary<SetBindingPair, BufferDefinition>();
_storageBuffers = new Dictionary<SetBindingPair, BufferDefinition>();
_textures = new Dictionary<SetBindingPair, TextureDefinition>();
_images = new Dictionary<SetBindingPair, TextureDefinition>();
_localMemories = new Dictionary<int, MemoryDefinition>();
_sharedMemories = new Dictionary<int, MemoryDefinition>();
}
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)

View file

@ -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);
}
}
}

View file

@ -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;
}
}
}

View file

@ -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));

View file

@ -15,6 +15,8 @@ namespace Ryujinx.Graphics.Shader.Translation
ShuffleUp,
ShuffleXor,
TexelFetchScale,
TexelFetchScaleBindless,
TextureSizeUnscale,
TextureSizeUnscaleBindless,
}
}

View file

@ -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<INode> 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);
}
}
}

View file

@ -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<INode> 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.

View file

@ -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<int, int> _sbSlots;
private readonly Dictionary<int, int> _sbSlotsReverse;
private readonly HashSet<int> _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,17 +347,12 @@ 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 + layer * 2, isIndexed, format);
var info = new TextureInfo(cbufSlot, handle, format);
var meta = new TextureMeta()
{
AccurateType = accurateType,
Type = type,
UsageFlags = usageFlags,
UsageFlags = usageFlags
};
int binding;
@ -352,13 +405,7 @@ namespace Ryujinx.Graphics.Shader.Translation
Properties.AddOrUpdateTexture(definition);
}
if (layer == 0)
{
firstBinding = binding;
}
}
return firstBinding;
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)

View file

@ -32,12 +32,12 @@ namespace Ryujinx.Graphics.Shader.Translation
private readonly Dictionary<IoDefinition, int> _offsets;
internal IReadOnlyDictionary<IoDefinition, int> 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)
{

View file

@ -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;
}
}
}

View file

@ -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<INode> 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<INode> 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<INode> 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<INode> 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<INode> oldNode = node;
@ -748,5 +778,113 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
{
return (type & SamplerType.Mask) == SamplerType.Texture2D;
}
private static LinkedListNode<INode> TurnIntoBindlessIfExceeding(
LinkedListNode<INode> 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<INode> 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;
}
}
}

View file

@ -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

View file

@ -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);
}
}

View file

@ -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<int, int> _textureIdMap;
private readonly Dictionary<int, int> _samplerIdMap;
private readonly ulong[] _textureBlockBitmap;
private readonly ulong[] _samplerBlockBitmap;
private ITexture[] _textureRefs;
private float[] _textureScales;
private Auto<DisposableSampler>[] _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<int, int>();
_samplerIdMap = new Dictionary<int, int>();
_textureBlockBitmap = new ulong[((TextureCapacity >> TextureIdBlockShift) + 63) / 64];
_samplerBlockBitmap = new ulong[((SamplerCapacity >> TextureIdBlockShift) + 63) / 64];
_textureRefs = Array.Empty<TextureView>();
_textureScales = Array.Empty<float>();
_samplerRefs = Array.Empty<Auto<DisposableSampler>>();
// 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<DisposableSampler> 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<T>(int id, int idMapOffset, Dictionary<int, int> 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<int, byte>(_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<float, byte>(_textureScales));
_textureScalesDirty = false;
}
Span<DescriptorBufferInfo> 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<DescriptorBufferInfo> 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<uint>.Empty);
gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, pipelineLayout, PipelineBase.BindlessBufferTextureSetIndex, 1, bindlessBufferTextures.GetSets(), 0, ReadOnlySpan<uint>.Empty);
gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, pipelineLayout, PipelineBase.BindlessSamplersSetIndex, 1, bindlessSamplers.GetSets(), 0, ReadOnlySpan<uint>.Empty);
gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, pipelineLayout, PipelineBase.BindlessImagesSetIndex, 1, bindlessImages.GetSets(), 0, ReadOnlySpan<uint>.Empty);
gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, pipelineLayout, PipelineBase.BindlessBufferImageSetIndex, 1, bindlessBufferImages.GetSets(), 0, ReadOnlySpan<uint>.Empty);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_idMapBuffer?.Dispose();
_textureScalesBuffer?.Dispose();
}
}
public void Dispose()
{
Dispose(true);
}
}
}

View file

@ -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<DescriptorImageInfo> 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,

View file

@ -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<uint>.Empty);
_gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, pipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan<uint>.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();
}
}

View file

@ -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)

View file

@ -11,12 +11,12 @@ namespace Ryujinx.Graphics.Vulkan
private readonly struct PlceKey : IEquatable<PlceKey>
{
public readonly ReadOnlyCollection<ResourceDescriptorCollection> SetDescriptors;
public readonly bool UsePushDescriptors;
public readonly PipelineLayoutUsageInfo UsageInfo;
public PlceKey(ReadOnlyCollection<ResourceDescriptorCollection> setDescriptors, bool usePushDescriptors)
public PlceKey(ReadOnlyCollection<ResourceDescriptorCollection> 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<PlceKey, PipelineLayoutCacheEntry>();
_plces = new();
}
public PipelineLayoutCacheEntry GetOrCreate(
VulkanRenderer gd,
Device device,
ReadOnlyCollection<ResourceDescriptorCollection> 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)

View file

@ -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<Auto<DescriptorSetCollection>>();
_dsCache[i][j] = new();
}
}
@ -55,9 +58,9 @@ namespace Ryujinx.Graphics.Vulkan
VulkanRenderer gd,
Device device,
ReadOnlyCollection<ResourceDescriptorCollection> 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<DescriptorPoolSize> GetDescriptorPoolSizes(Span<DescriptorPoolSize> output, int setIndex)
private Span<DescriptorPoolSize> GetDescriptorPoolSizes(Span<DescriptorPoolSize> 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];

View file

@ -10,7 +10,7 @@ namespace Ryujinx.Graphics.Vulkan
VulkanRenderer gd,
Device device,
ReadOnlyCollection<ResourceDescriptorCollection> 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,12 +74,42 @@ 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,
};
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();
}
}
}
PipelineLayout layout;

View file

@ -0,0 +1,35 @@
using System;
namespace Ryujinx.Graphics.Vulkan
{
struct PipelineLayoutUsageInfo : IEquatable<PipelineLayoutUsageInfo>
{
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);
}
}
}

View file

@ -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<PipelineShaderStageCreateInfo> Stages;
public NativeArray<PipelineShaderStageRequiredSubgroupSizeCreateInfoEXT> StageRequiredSubgroupSizes;
public PipelineLayout PipelineLayout;
public SpecData SpecializationData;
private Array32<VertexInputAttributeDescription> _vertexAttributeDescriptions2;
@ -339,6 +350,7 @@ namespace Ryujinx.Graphics.Vulkan
LineWidth = 1f;
SamplesCount = 1;
DepthMode = true;
Internal.Id11 = 0; // Unused.
}
public unsafe Auto<DisposablePipeline> 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,
};

View file

@ -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<ulong, Vector256<byte>>(ref Id0).Equals(Unsafe.As<ulong, Vector256<byte>>(ref other.Id0)) ||
!Unsafe.As<ulong, Vector256<byte>>(ref Id4).Equals(Unsafe.As<ulong, Vector256<byte>>(ref other.Id4)) ||
!Unsafe.As<ulong, Vector128<byte>>(ref Id8).Equals(Unsafe.As<ulong, Vector128<byte>>(ref other.Id8)))
!Unsafe.As<ulong, Vector256<byte>>(ref Id8).Equals(Unsafe.As<ulong, Vector256<byte>>(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++)
{

View file

@ -15,9 +15,9 @@ namespace Ryujinx.Graphics.Vulkan
private readonly Shader[] _shaders;
private readonly PipelineLayoutCacheEntry _plce;
private readonly ReadOnlyCollection<ResourceDescriptorCollection> _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)

View file

@ -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);

View file

@ -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"),
};

View file

@ -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);