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 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 SetAlphaTest(bool enable, float reference, CompareOp op);
void SetBlendState(AdvancedBlendDescriptor blend); void SetBlendState(AdvancedBlendDescriptor blend);

View file

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

View file

@ -62,6 +62,9 @@
DrawTexture, DrawTexture,
EndHostConditionalRendering, EndHostConditionalRendering,
EndTransformFeedback, EndTransformFeedback,
RegisterBindlessSampler,
RegisterBindlessTexture,
RegisterBindlessTextureAndSampler,
SetAlphaTest, SetAlphaTest,
SetBlendStateAdvanced, SetBlendStateAdvanced,
SetBlendState, 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(); _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) public void SetAlphaTest(bool enable, float reference, CompareOp op)
{ {
_renderer.New<SetAlphaTestCommand>().Set(enable, reference, op); _renderer.New<SetAlphaTestCommand>().Set(enable, reference, op);

View file

@ -3,21 +3,24 @@ namespace Ryujinx.Graphics.GAL
public struct ShaderInfo public struct ShaderInfo
{ {
public int FragmentOutputMap { get; } public int FragmentOutputMap { get; }
public bool HasBindless { get; }
public ResourceLayout ResourceLayout { get; } public ResourceLayout ResourceLayout { get; }
public ProgramPipelineState? State { get; } public ProgramPipelineState? State { get; }
public bool FromCache { get; set; } 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; FragmentOutputMap = fragmentOutputMap;
HasBindless = hasBindless;
ResourceLayout = resourceLayout; ResourceLayout = resourceLayout;
State = state; State = state;
FromCache = fromCache; FromCache = fromCache;
} }
public ShaderInfo(int fragmentOutputMap, ResourceLayout resourceLayout, bool fromCache = false) public ShaderInfo(int fragmentOutputMap, bool hasBindless, ResourceLayout resourceLayout, bool fromCache = false)
{ {
FragmentOutputMap = fragmentOutputMap; FragmentOutputMap = fragmentOutputMap;
HasBindless = hasBindless;
ResourceLayout = resourceLayout; ResourceLayout = resourceLayout;
State = null; State = null;
FromCache = fromCache; 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. /// Maximum size that an storage buffer is assumed to have when the correct size is unknown.
/// </summary> /// </summary>
public const ulong MaxUnknownStorageSize = 0x100000; 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. // Should never return false for mismatching spec state, since the shader was fetched above.
_channel.TextureManager.CommitComputeBindings(cs.SpecializationState); _channel.TextureManager.CommitComputeBindings(cs.SpecializationState);
_channel.BufferManager.CommitComputeBindings(); _channel.BufferManager.CommitComputeBindings();
_context.Renderer.Pipeline.DispatchCompute(qmd.CtaRasterWidth, qmd.CtaRasterHeight, qmd.CtaRasterDepth); _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> /// <returns>Texture target value</returns>
public static Target GetTarget(SamplerType type) public static Target GetTarget(SamplerType type)
{ {
type &= ~(SamplerType.Indexed | SamplerType.Shadow); type &= ~SamplerType.Shadow;
switch (type) 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 T1[] Items;
protected T2[] DescriptorCache; protected T2[] DescriptorCache;
protected readonly BitMap ModifiedEntries;
private int _minimumAccessedId;
private int _maximumAccessedId;
/// <summary> /// <summary>
/// The maximum ID value of resources on the pool (inclusive). /// The maximum ID value of resources on the pool (inclusive).
/// </summary> /// </summary>
@ -61,6 +66,11 @@ namespace Ryujinx.Graphics.Gpu.Image
int count = maximumId + 1; int count = maximumId + 1;
ModifiedEntries = new BitMap(count);
_minimumAccessedId = int.MaxValue;
_maximumAccessedId = 0;
ulong size = (ulong)(uint)count * DescriptorSize; ulong size = (ulong)(uint)count * DescriptorSize;
Items = new T1[count]; Items = new T1[count];
@ -197,6 +207,41 @@ namespace Ryujinx.Graphics.Gpu.Image
return false; 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 InvalidateRangeImpl(ulong address, ulong size);
protected abstract void Delete(T1 item); protected abstract void Delete(T1 item);

View file

@ -113,6 +113,24 @@ namespace Ryujinx.Graphics.Gpu.Image
return (CompareOp)(((Word0 >> 10) & 7) + 1); 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> /// <summary>
/// Unpacks and converts the maximum anisotropy value used for texture anisotropic filtering. /// Unpacks and converts the maximum anisotropy value used for texture anisotropic filtering.
/// </summary> /// </summary>

View file

@ -1,4 +1,5 @@
using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Gpu.Memory;
using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Ryujinx.Graphics.Gpu.Image namespace Ryujinx.Graphics.Gpu.Image
@ -117,6 +118,53 @@ namespace Ryujinx.Graphics.Gpu.Image
return ModifiedSequenceNumber; 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> /// <summary>
/// Implementation of the sampler pool range invalidation. /// Implementation of the sampler pool range invalidation.
/// </summary> /// </summary>
@ -126,6 +174,8 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
ulong endAddress = address + size; ulong endAddress = address + size;
UpdateModifiedEntries(address, endAddress);
for (; address < endAddress; address += DescriptorSize) for (; address < endAddress; address += DescriptorSize)
{ {
int id = (int)((address - Address) / DescriptorSize); int id = (int)((address - Address) / DescriptorSize);

View file

@ -1462,6 +1462,19 @@ namespace Ryujinx.Graphics.Gpu.Image
DisposeTextures(); DisposeTextures();
HostTexture = hostTexture; 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> /// <summary>

View file

@ -5,6 +5,7 @@ using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.Shader; using Ryujinx.Graphics.Gpu.Shader;
using Ryujinx.Graphics.Shader; using Ryujinx.Graphics.Shader;
using System; using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -59,6 +60,8 @@ namespace Ryujinx.Graphics.Gpu.Image
private int _texturePoolSequence; private int _texturePoolSequence;
private int _samplerPoolSequence; private int _samplerPoolSequence;
private BindlessTextureFlags[] _bindlessTextureFlags;
private int _textureBufferIndex; private int _textureBufferIndex;
private int _lastFragmentTotal; private int _lastFragmentTotal;
@ -93,6 +96,8 @@ namespace Ryujinx.Graphics.Gpu.Image
_textureState = new TextureState[InitialTextureStateSize]; _textureState = new TextureState[InitialTextureStateSize];
_imageState = new TextureState[InitialImageStateSize]; _imageState = new TextureState[InitialImageStateSize];
_bindlessTextureFlags = new BindlessTextureFlags[stages];
for (int stage = 0; stage < stages; stage++) for (int stage = 0; stage < stages; stage++)
{ {
_textureBindings[stage] = new TextureBindingInfo[InitialTextureStateSize]; _textureBindings[stage] = new TextureBindingInfo[InitialTextureStateSize];
@ -109,6 +114,8 @@ namespace Ryujinx.Graphics.Gpu.Image
_textureBindings = bindings.TextureBindings; _textureBindings = bindings.TextureBindings;
_imageBindings = bindings.ImageBindings; _imageBindings = bindings.ImageBindings;
_bindlessTextureFlags = bindings.BindlessTextureFlags;
SetMaxBindings(bindings.MaxTextureBinding, bindings.MaxImageBinding); 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 it wasn't, then it's possible to avoid looking up textures again when the handle remains the same.
if (_cachedTexturePool != texturePool || _cachedSamplerPool != samplerPool) 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(); Rebind();
_cachedTexturePool = texturePool; _cachedTexturePool = texturePool;
@ -341,6 +365,15 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
specStateMatches &= CommitTextureBindings(texturePool, samplerPool, ShaderStage.Compute, 0, poolModified, specState); specStateMatches &= CommitTextureBindings(texturePool, samplerPool, ShaderStage.Compute, 0, poolModified, specState);
specStateMatches &= CommitImageBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState); specStateMatches &= CommitImageBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState);
if (_bindlessTextureFlags[0].HasFlag(BindlessTextureFlags.BindlessNvn))
{
CommitBindlessResources(texturePool, ShaderStage.Compute, 0);
}
else if (_bindlessTextureFlags[0].HasFlag(BindlessTextureFlags.BindlessFull))
{
texturePool.LoadAll(_context.Renderer, _samplerPool);
}
} }
else else
{ {
@ -350,6 +383,15 @@ namespace Ryujinx.Graphics.Gpu.Image
specStateMatches &= CommitTextureBindings(texturePool, samplerPool, stage, stageIndex, poolModified, specState); specStateMatches &= CommitTextureBindings(texturePool, samplerPool, stage, stageIndex, poolModified, specState);
specStateMatches &= CommitImageBindings(texturePool, stage, stageIndex, poolModified, specState); specStateMatches &= CommitImageBindings(texturePool, stage, stageIndex, poolModified, specState);
if (_bindlessTextureFlags[stageIndex].HasFlag(BindlessTextureFlags.BindlessNvn))
{
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> cachedTextureBuffer = Span<int>.Empty;
ReadOnlySpan<int> cachedSamplerBuffer = Span<int>.Empty; ReadOnlySpan<int> cachedSamplerBuffer = Span<int>.Empty;
int maxTexturesPerStage = TextureHandle.GetMaxTexturesPerStage(_context.Capabilities.Api);
for (int index = 0; index < textureCount; index++) for (int index = 0; index < textureCount; index++)
{ {
bool asBindless = index >= maxTexturesPerStage;
TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index]; TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index];
TextureUsageFlags usageFlags = bindingInfo.Flags; 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. // 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 // 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. // 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. // Cache is not used for buffer texture, it must always rebind.
state.CachedTexture = null; state.CachedTexture = null;
@ -545,7 +601,14 @@ namespace Ryujinx.Graphics.Gpu.Image
state.Texture = hostTexture; state.Texture = hostTexture;
state.Sampler = hostSampler; state.Sampler = hostSampler;
_context.Renderer.Pipeline.SetTextureAndSampler(stage, bindingInfo.Binding, hostTexture, hostSampler); if (asBindless)
{
_context.Renderer.Pipeline.RegisterBindlessTextureAndSampler(textureId, hostTexture, texture?.ScaleFactor ?? 1f, samplerId, hostSampler);
}
else
{
_context.Renderer.Pipeline.SetTextureAndSampler(stage, bindingInfo.Binding, hostTexture, hostSampler);
}
} }
state.CachedTexture = texture; state.CachedTexture = texture;
@ -703,6 +766,93 @@ namespace Ryujinx.Graphics.Gpu.Image
return specStateMatches; 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> /// <summary>
/// Gets the texture descriptor for a given texture handle. /// Gets the texture descriptor for a given texture handle.
/// </summary> /// </summary>
@ -749,14 +899,13 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
/// <summary> /// <summary>
/// Reads a packed texture and sampler ID (basically, the real texture handle) /// Reads a combined texture and sampler handle from the texture constant buffer.
/// from the texture constant buffer.
/// </summary> /// </summary>
/// <param name="stageIndex">The number of the shader stage where the texture is bound</param> /// <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="textureBufferIndex">Index of the constant buffer holding the texture handles</param>
/// <param name="samplerBufferIndex">Index of the constant buffer holding the sampler 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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private int ReadPackedId(int stageIndex, int wordOffset, int textureBufferIndex, int samplerBufferIndex) 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.GAL;
using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Texture; using Ryujinx.Graphics.Texture;
using Ryujinx.Memory;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
@ -212,6 +213,94 @@ namespace Ryujinx.Graphics.Gpu.Image
return ModifiedSequenceNumber; 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> /// <summary>
/// Forcibly remove a texture from this pool's items. /// Forcibly remove a texture from this pool's items.
/// If deferred, the dereference will be queued to occur on the render thread. /// 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; ulong endAddress = address + size;
UpdateModifiedEntries(address, endAddress);
for (; address < endAddress; address += DescriptorSize) for (; address < endAddress; address += DescriptorSize)
{ {
int id = (int)((address - Address) / DescriptorSize); int id = (int)((address - Address) / DescriptorSize);
@ -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> /// <summary>
/// Gets texture information from a texture descriptor. /// Gets texture information from a texture descriptor.
/// </summary> /// </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. // 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); _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)); _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> /// <summary>
/// Force all bound textures and images to be rebound the next time CommitBindings is called. /// Force all bound textures and images to be rebound the next time CommitBindings is called.
/// </summary> /// </summary>

View file

@ -44,6 +44,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary> /// </summary>
public bool IsImage { get; } 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> /// <summary>
/// Create a new buffer texture binding. /// Create a new buffer texture binding.
/// </summary> /// </summary>
@ -70,6 +80,31 @@ namespace Ryujinx.Graphics.Gpu.Memory
BindingInfo = bindingInfo; BindingInfo = bindingInfo;
Format = format; Format = format;
IsImage = isImage; 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[][] ConstantBufferBindings { get; }
public BufferDescriptor[][] StorageBufferBindings { get; } public BufferDescriptor[][] StorageBufferBindings { get; }
public BindlessTextureFlags[] BindlessTextureFlags { get; }
public int MaxTextureBinding { get; } public int MaxTextureBinding { get; }
public int MaxImageBinding { get; } public int MaxImageBinding { get; }
@ -34,6 +36,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
ConstantBufferBindings = new BufferDescriptor[stageCount][]; ConstantBufferBindings = new BufferDescriptor[stageCount][];
StorageBufferBindings = new BufferDescriptor[stageCount][]; StorageBufferBindings = new BufferDescriptor[stageCount][];
BindlessTextureFlags = new BindlessTextureFlags[stageCount];
int maxTextureBinding = -1; int maxTextureBinding = -1;
int maxImageBinding = -1; int maxImageBinding = -1;
int offset = isCompute ? 0 : 1; int offset = isCompute ? 0 : 1;
@ -94,6 +98,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
ConstantBufferBindings[i] = stage.Info.CBuffers.ToArray(); ConstantBufferBindings[i] = stage.Info.CBuffers.ToArray();
StorageBufferBindings[i] = stage.Info.SBuffers.ToArray(); StorageBufferBindings[i] = stage.Info.SBuffers.ToArray();
BindlessTextureFlags[i] = stage.Info.BindlessTextureFlags;
} }
MaxTextureBinding = maxTextureBinding; MaxTextureBinding = maxTextureBinding;

View file

@ -123,6 +123,14 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return _oldSpecState.GetTextureTarget(_stageIndex, handle, cbufSlot).ConvertSamplerType(); return _oldSpecState.GetTextureTarget(_stageIndex, handle, cbufSlot).ConvertSamplerType();
} }
/// <inheritdoc/>
public int QueryTextureBufferIndex()
{
byte textureBufferIndex = _oldSpecState.GetTextureBufferIndex();
_newSpecState.RecordTextureBufferIndex(textureBufferIndex);
return textureBufferIndex;
}
/// <inheritdoc/> /// <inheritdoc/>
public bool QueryTextureCoordNormalized(int handle, int cbufSlot) 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 FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2; private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; 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 SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data"; private const string SharedDataFileName = "shared.data";
@ -184,6 +184,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// Indicates if the vertex shader accesses draw parameters. /// Indicates if the vertex shader accesses draw parameters.
/// </summary> /// </summary>
public bool UsesDrawParameters; 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; private readonly DiskCacheGuestStorage _guestStorage;
@ -799,6 +804,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
textures, textures,
images, images,
dataInfo.Stage, dataInfo.Stage,
dataInfo.BindlessTextureFlags,
dataInfo.GeometryVerticesPerPrimitive, dataInfo.GeometryVerticesPerPrimitive,
dataInfo.GeometryMaxOutputVertices, dataInfo.GeometryMaxOutputVertices,
dataInfo.ThreadsPerInputPrimitive, dataInfo.ThreadsPerInputPrimitive,
@ -829,6 +835,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
TexturesCount = (ushort)info.Textures.Count, TexturesCount = (ushort)info.Textures.Count,
ImagesCount = (ushort)info.Images.Count, ImagesCount = (ushort)info.Images.Count,
Stage = info.Stage, Stage = info.Stage,
BindlessTextureFlags = info.BindlessTextureFlags,
GeometryVerticesPerPrimitive = (byte)info.GeometryVerticesPerPrimitive, GeometryVerticesPerPrimitive = (byte)info.GeometryVerticesPerPrimitive,
GeometryMaxOutputVertices = (ushort)info.GeometryMaxOutputVertices, GeometryMaxOutputVertices = (ushort)info.GeometryMaxOutputVertices,
ThreadsPerInputPrimitive = (ushort)info.ThreadsPerInputPrimitive, ThreadsPerInputPrimitive = (ushort)info.ThreadsPerInputPrimitive,

View file

@ -134,6 +134,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
return GetTextureDescriptor(handle, cbufSlot).UnpackTextureTarget().ConvertSamplerType(); return GetTextureDescriptor(handle, cbufSlot).UnpackTextureTarget().ConvertSamplerType();
} }
/// <inheritdoc/>
public int QueryTextureBufferIndex()
{
byte textureBufferIndex = (byte)_state.PoolState.TextureBufferIndex;
_state.SpecializationState?.RecordTextureBufferIndex(textureBufferIndex);
return textureBufferIndex;
}
/// <inheritdoc/> /// <inheritdoc/>
public bool QueryTextureCoordNormalized(int handle, int cbufSlot) 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> /// <param name="vertexAsCompute">Indicates that the vertex shader will be emulated on a compute shader</param>
public void InitializeReservedCounts(bool tfEnabled, bool vertexAsCompute) 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; _reservedConstantBuffers = rrc.ReservedConstantBuffers;
_reservedStorageBuffers = rrc.ReservedStorageBuffers; _reservedStorageBuffers = rrc.ReservedStorageBuffers;

View file

@ -1,5 +1,6 @@
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader; using Ryujinx.Graphics.Shader;
using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Ryujinx.Graphics.Gpu.Shader namespace Ryujinx.Graphics.Gpu.Shader
@ -10,11 +11,17 @@ namespace Ryujinx.Graphics.Gpu.Shader
class ShaderInfoBuilder class ShaderInfoBuilder
{ {
private const int TotalSets = 4; private const int TotalSets = 4;
private const int TotalBindlessSets = 9;
private const int UniformSetIndex = 0; private const int UniformSetIndex = 0;
private const int StorageSetIndex = 1; private const int StorageSetIndex = 1;
private const int TextureSetIndex = 2; private const int TextureSetIndex = 2;
private const int ImageSetIndex = 3; 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 = private const ResourceStages SupportBufferStages =
ResourceStages.Compute | ResourceStages.Compute |
@ -27,17 +34,20 @@ namespace Ryujinx.Graphics.Gpu.Shader
ResourceStages.TessellationEvaluation | ResourceStages.TessellationEvaluation |
ResourceStages.Geometry; ResourceStages.Geometry;
private const ResourceStages GraphicsStages = VtgStages | ResourceStages.Fragment;
private readonly GpuContext _context; private readonly GpuContext _context;
private int _fragmentOutputMap; private int _fragmentOutputMap;
private bool _anyBindless;
private readonly int _reservedConstantBuffers; private readonly int _reservedConstantBuffers;
private readonly int _reservedStorageBuffers; private readonly int _reservedStorageBuffers;
private readonly int _reservedTextures; private readonly int _reservedTextures;
private readonly int _reservedImages; private readonly int _reservedImages;
private readonly List<ResourceDescriptor>[] _resourceDescriptors; private List<ResourceDescriptor>[] _resourceDescriptors;
private readonly List<ResourceUsage>[] _resourceUsages; private List<ResourceUsage>[] _resourceUsages;
/// <summary> /// <summary>
/// Creates a new shader info builder. /// Creates a new shader info builder.
@ -63,7 +73,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
AddDescriptor(SupportBufferStages, ResourceType.UniformBuffer, UniformSetIndex, 0, 1); AddDescriptor(SupportBufferStages, ResourceType.UniformBuffer, UniformSetIndex, 0, 1);
AddUsage(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; _reservedConstantBuffers = rrc.ReservedConstantBuffers;
_reservedStorageBuffers = rrc.ReservedStorageBuffers; _reservedStorageBuffers = rrc.ReservedStorageBuffers;
@ -97,6 +110,30 @@ namespace Ryujinx.Graphics.Gpu.Shader
_fragmentOutputMap = info.FragmentOutputMap; _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 int stageIndex = GpuAccessorBase.GetStageIndex(info.Stage switch
{ {
ShaderStage.TessellationControl => 1, 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> /// <summary>
/// Adds two interleaved groups of resources to the list of descriptors. /// Adds two interleaved groups of resources to the list of descriptors.
/// </summary> /// </summary>
@ -235,10 +285,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <returns>Shader information</returns> /// <returns>Shader information</returns>
public ShaderInfo Build(ProgramPipelineState? pipeline, bool fromCache = false) public ShaderInfo Build(ProgramPipelineState? pipeline, bool fromCache = false)
{ {
var descriptors = new ResourceDescriptorCollection[TotalSets]; var descriptors = new ResourceDescriptorCollection[_resourceDescriptors.Length];
var usages = new ResourceUsageCollection[TotalSets]; 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()); descriptors[index] = new ResourceDescriptorCollection(_resourceDescriptors[index].ToArray().AsReadOnly());
usages[index] = new ResourceUsageCollection(_resourceUsages[index].ToArray().AsReadOnly()); usages[index] = new ResourceUsageCollection(_resourceUsages[index].ToArray().AsReadOnly());
@ -248,11 +298,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
if (pipeline.HasValue) if (pipeline.HasValue)
{ {
return new ShaderInfo(_fragmentOutputMap, resourceLayout, pipeline.Value, fromCache); return new ShaderInfo(_fragmentOutputMap, _anyBindless, resourceLayout, pipeline.Value, fromCache);
} }
else 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, PrimitiveTopology = 1 << 1,
TransformFeedback = 1 << 3, TransformFeedback = 1 << 3,
TextureBufferIndex = 1 << 4,
} }
private QueriedStateFlags _queriedState; private QueriedStateFlags _queriedState;
private bool _compute; private bool _compute;
private byte _constantBufferUsePerStage; private byte _constantBufferUsePerStage;
private byte _textureBufferIndex;
/// <summary> /// <summary>
/// Compute engine state. /// Compute engine state.
@ -323,6 +325,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
state.Value.CoordNormalized = coordNormalized; 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> /// <summary>
/// Indicates that the format of a given texture was used during the shader translation process. /// Indicates that the format of a given texture was used during the shader translation process.
/// </summary> /// </summary>
@ -385,18 +397,30 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="stageIndex">Shader stage where the texture is used</param> /// <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="handle">Offset in words of the texture handle on the texture buffer</param>
/// <param name="cbufSlot">Slot of the texture buffer constant 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) public (uint, bool) GetFormat(int stageIndex, int handle, int cbufSlot)
{ {
TextureSpecializationState state = GetTextureSpecState(stageIndex, handle, cbufSlot).Value; TextureSpecializationState state = GetTextureSpecState(stageIndex, handle, cbufSlot).Value;
return (state.Format, state.FormatSrgb); 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> /// <summary>
/// Gets the recorded target of a given texture. /// Gets the recorded target of a given texture.
/// </summary> /// </summary>
/// <param name="stageIndex">Shader stage where the texture is used</param> /// <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="handle">Offset in words of the texture handle on the texture buffer</param>
/// <param name="cbufSlot">Slot of the texture buffer constant 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) public TextureTarget GetTextureTarget(int stageIndex, int handle, int cbufSlot)
{ {
return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.TextureTarget; 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="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="handle">Offset in words of the texture handle on the texture buffer</param>
/// <param name="cbufSlot">Slot of the texture buffer constant 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) public bool GetCoordNormalized(int stageIndex, int handle, int cbufSlot)
{ {
return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.CoordNormalized; return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.CoordNormalized;
@ -799,6 +824,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
constantBufferUsePerStageMask &= ~(1 << index); constantBufferUsePerStageMask &= ~(1 << index);
} }
if (specState._queriedState.HasFlag(QueriedStateFlags.TextureBufferIndex))
{
dataReader.Read(ref specState._textureBufferIndex);
}
bool hasPipelineState = false; bool hasPipelineState = false;
dataReader.Read(ref hasPipelineState); dataReader.Read(ref hasPipelineState);
@ -870,6 +900,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
constantBufferUsePerStageMask &= ~(1 << index); constantBufferUsePerStageMask &= ~(1 << index);
} }
if (_queriedState.HasFlag(QueriedStateFlags.TextureBufferIndex))
{
dataWriter.Write(ref _textureBufferIndex);
}
bool hasPipelineState = PipelineState.HasValue; bool hasPipelineState = PipelineState.HasValue;
dataWriter.Write(ref hasPipelineState); dataWriter.Write(ref hasPipelineState);

View file

@ -5,6 +5,7 @@ namespace Ryujinx.Graphics.OpenGL
{ {
static class HwCapabilities 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> _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> _supportsAstcCompression = new(() => HasExtension("GL_KHR_texture_compression_astc_ldr"));
private static readonly Lazy<bool> _supportsBlendEquationAdvanced = new(() => HasExtension("GL_NV_blend_equation_advanced")); 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> _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> _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> _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> _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> _supportsPolygonOffsetClamp = new(() => HasExtension("GL_EXT_polygon_offset_clamp"));
private static readonly Lazy<bool> _supportsQuads = new(SupportsQuadsCheck); 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 UsePersistentBufferForFlush => _gpuVendor.Value == GpuVendor.AmdWindows || _gpuVendor.Value == GpuVendor.Nvidia;
public static bool SupportsArbBindlessTexture => _supportsArbBindlessTexture.Value;
public static bool SupportsAlphaToCoverageDitherControl => _supportsAlphaToCoverageDitherControl.Value; public static bool SupportsAlphaToCoverageDitherControl => _supportsAlphaToCoverageDitherControl.Value;
public static bool SupportsAstcCompression => _supportsAstcCompression.Value; public static bool SupportsAstcCompression => _supportsAstcCompression.Value;
public static bool SupportsBlendEquationAdvanced => _supportsBlendEquationAdvanced.Value; public static bool SupportsBlendEquationAdvanced => _supportsBlendEquationAdvanced.Value;
@ -60,6 +63,7 @@ namespace Ryujinx.Graphics.OpenGL
public static bool SupportsGeometryShaderPassthrough => _supportsGeometryShaderPassthrough.Value; public static bool SupportsGeometryShaderPassthrough => _supportsGeometryShaderPassthrough.Value;
public static bool SupportsImageLoadFormatted => _supportsImageLoadFormatted.Value; public static bool SupportsImageLoadFormatted => _supportsImageLoadFormatted.Value;
public static bool SupportsIndirectParameters => _supportsIndirectParameters.Value; public static bool SupportsIndirectParameters => _supportsIndirectParameters.Value;
public static bool SupportsNvBindlessTexture => _supportsNvBindlessTexture.Value;
public static bool SupportsParallelShaderCompile => _supportsParallelShaderCompile.Value; public static bool SupportsParallelShaderCompile => _supportsParallelShaderCompile.Value;
public static bool SupportsPolygonOffsetClamp => _supportsPolygonOffsetClamp.Value; public static bool SupportsPolygonOffsetClamp => _supportsPolygonOffsetClamp.Value;
public static bool SupportsQuads => _supportsQuads.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 OpenTK.Graphics.OpenGL;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using System.Collections.Generic;
namespace Ryujinx.Graphics.OpenGL.Image namespace Ryujinx.Graphics.OpenGL.Image
{ {
@ -15,6 +16,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
public Target Target => Info.Target; public Target Target => Info.Target;
public Format Format => Info.Format; public Format Format => Info.Format;
private Dictionary<int, (BindlessManager, long, int)> _bindlessHandles;
public TextureBase(TextureCreateInfo info) public TextureBase(TextureCreateInfo info)
{ {
Info = info; Info = info;
@ -35,8 +38,32 @@ namespace Ryujinx.Graphics.OpenGL.Image
public static void ClearBinding(int unit) public static void ClearBinding(int unit)
{ {
GL.ActiveTexture(TextureUnit.Texture0 + unit);
GL.BindTextureUnit(unit, 0); 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() public void Dispose()
{ {
RevokeBindlessAccess();
if (Handle != 0) if (Handle != 0)
{ {
GL.DeleteTexture(Handle); GL.DeleteTexture(Handle);

View file

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

View file

@ -45,7 +45,7 @@ namespace Ryujinx.Graphics.OpenGL
public OpenGLRenderer() public OpenGLRenderer()
{ {
_pipeline = new Pipeline(); _pipeline = new Pipeline(this);
_counters = new Counters(); _counters = new Counters();
_window = new Window(this); _window = new Window(this);
_textureCopy = new TextureCopy(this); _textureCopy = new TextureCopy(this);

View file

@ -26,7 +26,6 @@ namespace Ryujinx.Graphics.OpenGL
private IntPtr _indexBaseOffset; private IntPtr _indexBaseOffset;
private DrawElementsType _elementsType; private DrawElementsType _elementsType;
private PrimitiveType _primitiveType; private PrimitiveType _primitiveType;
private int _stencilFrontMask; private int _stencilFrontMask;
@ -66,9 +65,11 @@ namespace Ryujinx.Graphics.OpenGL
private readonly BufferHandle[] _tfbs; private readonly BufferHandle[] _tfbs;
private readonly BufferRange[] _tfbTargets; private readonly BufferRange[] _tfbTargets;
private readonly BindlessManager _bindlessManager;
private ColorF _blendConstant; private ColorF _blendConstant;
internal Pipeline() internal Pipeline(OpenGLRenderer renderer)
{ {
_drawTexture = new DrawTextureEmulation(); _drawTexture = new DrawTextureEmulation();
_rasterizerDiscard = false; _rasterizerDiscard = false;
@ -82,6 +83,8 @@ namespace Ryujinx.Graphics.OpenGL
_tfbs = new BufferHandle[Constants.MaxTransformFeedbackBuffers]; _tfbs = new BufferHandle[Constants.MaxTransformFeedbackBuffers];
_tfbTargets = new BufferRange[Constants.MaxTransformFeedbackBuffers]; _tfbTargets = new BufferRange[Constants.MaxTransformFeedbackBuffers];
_bindlessManager = new BindlessManager(renderer);
} }
public void Barrier() public void Barrier()
@ -759,6 +762,21 @@ namespace Ryujinx.Graphics.OpenGL
_tfEnabled = false; _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) public void SetAlphaTest(bool enable, float reference, CompareOp op)
{ {
if (!enable) if (!enable)
@ -1302,7 +1320,6 @@ namespace Ryujinx.Graphics.OpenGL
} }
} }
public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers) public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers)
{ {
if (_tfEnabled) 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 HostCapabilities HostCapabilities;
public readonly ILogger Logger; public readonly ILogger Logger;
public readonly TargetApi TargetApi; public readonly TargetApi TargetApi;
public readonly BindlessTextureFlags BindlessTextureFlags;
public CodeGenParameters( public CodeGenParameters(
AttributeUsage attributeUsage, AttributeUsage attributeUsage,
@ -18,7 +19,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen
ShaderProperties properties, ShaderProperties properties,
HostCapabilities hostCapabilities, HostCapabilities hostCapabilities,
ILogger logger, ILogger logger,
TargetApi targetApi) TargetApi targetApi,
BindlessTextureFlags bindlessTextureFlags)
{ {
AttributeUsage = attributeUsage; AttributeUsage = attributeUsage;
Definitions = definitions; Definitions = definitions;
@ -26,6 +28,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen
HostCapabilities = hostCapabilities; HostCapabilities = hostCapabilities;
Logger = logger; Logger = logger;
TargetApi = targetApi; TargetApi = targetApi;
BindlessTextureFlags = bindlessTextureFlags;
} }
} }
} }

View file

@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
public HostCapabilities HostCapabilities { get; } public HostCapabilities HostCapabilities { get; }
public ILogger Logger { get; } public ILogger Logger { get; }
public TargetApi TargetApi { get; } public TargetApi TargetApi { get; }
public BindlessTextureFlags BindlessTextureFlags { get; }
public OperandManager OperandManager { get; } public OperandManager OperandManager { get; }
@ -36,6 +37,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
HostCapabilities = parameters.HostCapabilities; HostCapabilities = parameters.HostCapabilities;
Logger = parameters.Logger; Logger = parameters.Logger;
TargetApi = parameters.TargetApi; TargetApi = parameters.TargetApi;
BindlessTextureFlags = parameters.BindlessTextureFlags;
OperandManager = new OperandManager(); OperandManager = new OperandManager();

View file

@ -6,7 +6,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Numerics;
namespace Ryujinx.Graphics.Shader.CodeGen.Glsl namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{ {
@ -15,6 +14,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
public static void Declare(CodeGenContext context, StructuredProgramInfo info) public static void Declare(CodeGenContext context, StructuredProgramInfo info)
{ {
context.AppendLine(context.TargetApi == TargetApi.Vulkan ? "#version 460 core" : "#version 450 core"); context.AppendLine(context.TargetApi == TargetApi.Vulkan ? "#version 460 core" : "#version 450 core");
context.AppendLine("#extension GL_ARB_bindless_texture : enable");
context.AppendLine("#extension GL_ARB_gpu_shader_int64 : enable"); context.AppendLine("#extension GL_ARB_gpu_shader_int64 : enable");
if (context.HostCapabilities.SupportsShaderBallot) if (context.HostCapabilities.SupportsShaderBallot)
@ -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_shader_image_load_formatted : enable");
context.AppendLine("#extension GL_EXT_texture_shadow_lod : 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) if (context.Definitions.Stage == ShaderStage.Compute)
{ {
context.AppendLine("#extension GL_ARB_compute_shader : enable"); 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) if ((info.HelperFunctionsMask & HelperFunctionsMask.MultiplyHighS32) != 0)
{ {
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighS32.glsl"); 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 sampler in samplers)
foreach (var definition in definitions)
{ {
string indexExpr = string.Empty; string samplerTypeName = sampler.Type.HasFlag(SamplerType.Separate)
? (sampler.Type & ~SamplerType.Separate).ToGlslTextureType()
if (definition.Type.HasFlag(SamplerType.Indexed)) : sampler.Type.ToGlslSamplerType();
{
if (arraySize == 0)
{
arraySize = ResourceManager.SamplerArraySize;
}
else if (--arraySize != 0)
{
continue;
}
indexExpr = $"[{NumberFormatter.FormatInt(arraySize)}]";
}
string samplerTypeName = definition.Type.ToGlslSamplerType();
string layout = string.Empty; string layout = string.Empty;
if (context.TargetApi == TargetApi.Vulkan) if (context.TargetApi == TargetApi.Vulkan)
{ {
layout = $", set = {definition.Set}"; layout = $"set = {sampler.Set}, ";
} }
context.AppendLine($"layout (binding = {definition.Binding}{layout}) uniform {samplerTypeName} {definition.Name}{indexExpr};"); string suffix = string.Empty;
if (sampler.ArraySize == 0)
{
suffix = "[]";
}
else if (sampler.ArraySize != 1)
{
suffix = $"[{(uint)sampler.ArraySize}]";
}
if (sampler.ArraySize != 1)
{
Console.WriteLine($"found bindless {sampler.Name} {(sampler.Type & ~SamplerType.Shadow)}");
context.OperandManager.BindlessTextures[sampler.Type & ~(SamplerType.Shadow | SamplerType.Separate)] = sampler.Name;
}
context.AppendLine($"layout ({layout}binding = {sampler.Binding}) uniform {samplerTypeName} {sampler.Name}{suffix};");
} }
} }
private static void DeclareImages(CodeGenContext context, IEnumerable<TextureDefinition> definitions) private static void DeclareImages(CodeGenContext context, IEnumerable<TextureDefinition> images)
{ {
int arraySize = 0; foreach (var image in images)
foreach (var definition in definitions)
{ {
string indexExpr = string.Empty; string imageTypeName = image.Type.ToGlslImageType(image.Format.GetComponentType());
if (definition.Type.HasFlag(SamplerType.Indexed)) if (image.Flags.HasFlag(TextureUsageFlags.ImageCoherent))
{
if (arraySize == 0)
{
arraySize = ResourceManager.SamplerArraySize;
}
else if (--arraySize != 0)
{
continue;
}
indexExpr = $"[{NumberFormatter.FormatInt(arraySize)}]";
}
string imageTypeName = definition.Type.ToGlslImageType(definition.Format.GetComponentType());
if (definition.Flags.HasFlag(TextureUsageFlags.ImageCoherent))
{ {
imageTypeName = "coherent " + imageTypeName; imageTypeName = "coherent " + imageTypeName;
} }
string layout = definition.Format.ToGlslFormat(); string layout = string.Empty;
if (!string.IsNullOrEmpty(layout))
{
layout = ", " + layout;
}
if (context.TargetApi == TargetApi.Vulkan) 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 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 MultiplyHighS32 = "Helper_MultiplyHighS32";
public static string MultiplyHighU32 = "Helper_MultiplyHighU32"; 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; 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 isArray = (texOp.Type & SamplerType.Array) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
var texCallBuilder = new StringBuilder(); var texCallBuilder = new StringBuilder();
@ -70,21 +44,27 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
texCallBuilder.Append(texOp.Inst == Instruction.ImageLoad ? "imageLoad" : "imageStore"); texCallBuilder.Append(texOp.Inst == Instruction.ImageLoad ? "imageLoad" : "imageStore");
} }
int srcIndex = isBindless ? 1 : 0; int srcIndex = 0;
string Src(AggregateType type) string Src(AggregateType type)
{ {
return GetSoureExpr(context, texOp.GetSource(srcIndex++), 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('(');
texCallBuilder.Append(imageName); texCallBuilder.Append(imageName);
@ -117,8 +97,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
if (texOp.Inst == Instruction.ImageStore) if (texOp.Inst == Instruction.ImageStore)
{ {
AggregateType type = texOp.Format.GetComponentType();
string[] cElems = new string[4]; string[] cElems = new string[4];
for (int index = 0; index < 4; index++) for (int index = 0; index < 4; index++)
@ -150,8 +128,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
if (texOp.Inst == Instruction.ImageAtomic) if (texOp.Inst == Instruction.ImageAtomic)
{ {
AggregateType type = texOp.Format.GetComponentType();
if ((texOp.Flags & TextureFlags.AtomicMask) == TextureFlags.CAS) if ((texOp.Flags & TextureFlags.AtomicMask) == TextureFlags.CAS)
{ {
Append(Src(type)); // Compare value. Append(Src(type)); // Compare value.
@ -207,18 +183,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return NumberFormatter.FormatFloat(0); return NumberFormatter.FormatFloat(0);
} }
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; string samplerName = GetSamplerName(context.Properties, texOp);
string indexExpr = null; int coordsIndex = isBindless ? 1 : 0;
if (isIndexed)
{
indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32);
}
string samplerName = GetSamplerName(context.Properties, texOp, indexExpr);
int coordsIndex = isBindless || isIndexed ? 1 : 0;
string coordsExpr; string coordsExpr;
@ -260,7 +227,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0; bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0;
bool isArray = (texOp.Type & SamplerType.Array) != 0; bool isArray = (texOp.Type & SamplerType.Array) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0; bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0;
bool isShadow = (texOp.Type & SamplerType.Shadow) != 0; bool isShadow = (texOp.Type & SamplerType.Shadow) != 0;
@ -286,24 +252,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
hasLodLevel = false; 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"; string texCall = intCoords ? "texelFetch" : "texture";
if (isGather) if (isGather)
@ -328,23 +276,24 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
texCall += "Offsets"; texCall += "Offsets";
} }
int srcIndex = isBindless ? 1 : 0; int srcIndex = 0;
string Src(AggregateType type) string Src(AggregateType type)
{ {
return GetSoureExpr(context, texOp.GetSource(srcIndex++), 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(); int coordsCount = texOp.Type.GetDimensions();
@ -523,22 +472,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
// TODO: Bindless texture support. For now we just return 0. string bindlessHandle = isBindless ? GetSoureExpr(context, operation.GetSource(0), AggregateType.S32) : null;
if (isBindless)
{
return NumberFormatter.FormatInt(0);
}
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; string samplerName = isBindless
? GetBindlessSampler(context, texOp.Type, bindlessHandle)
string indexExpr = null; : GetSamplerName(context.Properties, texOp);
if (isIndexed)
{
indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32);
}
string samplerName = GetSamplerName(context.Properties, texOp, indexExpr);
return $"textureSamples({samplerName})"; return $"textureSamples({samplerName})";
} }
@ -549,22 +487,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
// TODO: Bindless texture support. For now we just return 0. string bindlessHandle = isBindless ? GetSoureExpr(context, operation.GetSource(0), AggregateType.S32) : null;
if (isBindless)
{
return NumberFormatter.FormatInt(0);
}
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; string samplerName = isBindless
? GetBindlessSampler(context, texOp.Type, bindlessHandle)
string indexExpr = null; : GetSamplerName(context.Properties, texOp);
if (isIndexed)
{
indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32);
}
string samplerName = GetSamplerName(context.Properties, texOp, indexExpr);
if (texOp.Index == 3) if (texOp.Index == 3)
{ {
@ -572,13 +499,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
} }
else 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; bool hasLod = !definition.Type.HasFlag(SamplerType.Multisample) && (definition.Type & SamplerType.Mask) != SamplerType.TextureBuffer;
string texCall; string texCall;
if (hasLod) if (hasLod)
{ {
int lodSrcIndex = isBindless || isIndexed ? 1 : 0; int lodSrcIndex = isBindless ? 1 : 0;
IAstNode lod = operation.GetSource(lodSrcIndex); IAstNode lod = operation.GetSource(lodSrcIndex);
string lodExpr = GetSoureExpr(context, lod, GetSrcVarType(operation.Inst, 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; int binding = bindingIndex.Value;
BufferDefinition buffer = storageKind == StorageKind.ConstantBuffer BufferDefinition buffer = storageKind == StorageKind.ConstantBuffer
? context.Properties.ConstantBuffers[binding] ? context.Properties.ConstantBuffers[SetBindingPair.Unpack(binding)]
: context.Properties.StorageBuffers[binding]; : context.Properties.StorageBuffers[SetBindingPair.Unpack(binding)];
if (operation.GetSource(srcIndex++) is not AstOperand fieldIndex || fieldIndex.Type != OperandType.Constant) 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; return varName;
} }
private static string GetSamplerName(ShaderProperties resourceDefinitions, AstTextureOperation texOp, string indexExpr) private static string GetBindlessSampler(CodeGenContext context, SamplerType type, string bindlessHandle)
{ {
string name = resourceDefinitions.Textures[texOp.Binding].Name; string samplerType = type.ToGlslSamplerType();
if (texOp.Type.HasFlag(SamplerType.Indexed)) if (context.TargetApi == TargetApi.Vulkan)
{ {
name = $"{name}[{indexExpr}]"; string textureIndex = $"{HelperFunctionNames.GetBindlessTextureIndexVk}({bindlessHandle})";
} string samplerIndex = $"{HelperFunctionNames.GetBindlessSamplerIndexVk}({bindlessHandle})";
return name; string bindlessTextureArrayName = context.OperandManager.BindlessTextures[type & ~SamplerType.Shadow];
string bindlessSamplerArrayName = context.OperandManager.BindlessTextures[SamplerType.None];
return $"{samplerType}({bindlessTextureArrayName}[{textureIndex}], {bindlessSamplerArrayName}[{samplerIndex}])";
}
else
{
return $"{samplerType}({HelperFunctionNames.GetBindlessHandle}({bindlessHandle}))";
}
} }
private static string GetImageName(ShaderProperties resourceDefinitions, AstTextureOperation texOp, string indexExpr) private static string GetBindlessImage(CodeGenContext context, SamplerType type, AggregateType componentType, string bindlessHandle)
{ {
string name = resourceDefinitions.Images[texOp.Binding].Name; string imageType = type.ToGlslImageType(componentType);
if (texOp.Type.HasFlag(SamplerType.Indexed)) if (context.TargetApi == TargetApi.Vulkan)
{ {
name = $"{name}[{indexExpr}]"; string textureIndex = $"{HelperFunctionNames.GetBindlessTextureIndexVk}({bindlessHandle})";
}
return name; string bindlessImageArrayName = context.OperandManager.BindlessImages[type];
return $"{bindlessImageArrayName}[{textureIndex}]";
}
else
{
return $"{imageType}({HelperFunctionNames.GetBindlessHandle}({bindlessHandle}))";
}
}
private static string GetSamplerName(ShaderProperties resourceDefinitions, AstTextureOperation texOp)
{
return resourceDefinitions.Textures[SetBindingPair.Unpack(texOp.Binding)].Name;
}
private static string GetImageName(ShaderProperties resourceDefinitions, AstTextureOperation texOp)
{
return resourceDefinitions.Images[SetBindingPair.Unpack(texOp.Binding)].Name;
} }
private static string GetMask(int index) private static string GetMask(int index)

View file

@ -13,9 +13,15 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{ {
private readonly Dictionary<AstOperand, string> _locals; private readonly Dictionary<AstOperand, string> _locals;
public Dictionary<SamplerType, string> BindlessTextures { get; }
public Dictionary<SamplerType, string> BindlessImages { get; }
public OperandManager() public OperandManager()
{ {
_locals = new Dictionary<AstOperand, string>(); _locals = new();
BindlessTextures = new();
BindlessImages = new();
} }
public string DeclareLocal(AstOperand operand) public string DeclareLocal(AstOperand operand)
@ -68,8 +74,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
} }
BufferDefinition buffer = operation.StorageKind == StorageKind.ConstantBuffer BufferDefinition buffer = operation.StorageKind == StorageKind.ConstantBuffer
? context.Properties.ConstantBuffers[bindingIndex.Value] ? context.Properties.ConstantBuffers[SetBindingPair.Unpack(bindingIndex.Value)]
: context.Properties.StorageBuffers[bindingIndex.Value]; : context.Properties.StorageBuffers[SetBindingPair.Unpack(bindingIndex.Value)];
StructureField field = buffer.Type.Fields[fieldIndex.Value]; StructureField field = buffer.Type.Fields[fieldIndex.Value];
return field.Type & AggregateType.ElementTypeMask; 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> ConstantBuffers { get; } = new();
public Dictionary<int, Instruction> StorageBuffers { get; } = new(); public Dictionary<int, Instruction> StorageBuffers { get; } = new();
public Dictionary<int, Instruction> LocalMemories { get; } = new(); public Dictionary<int, Instruction> LocalMemories { get; } = new();
public Dictionary<int, Instruction> SharedMemories { get; } = new(); public Dictionary<int, Instruction> SharedMemories { get; } = new();
public Dictionary<int, SamplerType> SamplersTypes { get; } = new(); public Dictionary<int, SamplerType> SamplersTypes { get; } = new();
public Dictionary<int, (Instruction, Instruction, Instruction)> Samplers { get; } = new(); public Dictionary<int, (Instruction, Instruction, Instruction)> Samplers { get; } = new();
public Dictionary<int, (Instruction, Instruction)> Images { get; } = new(); public Dictionary<int, (Instruction, Instruction)> Images { get; } = new();
public Dictionary<IoDefinition, Instruction> Inputs { 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> Outputs { get; } = new();
public Dictionary<IoDefinition, Instruction> InputsPerPatch { get; } = new(); public Dictionary<IoDefinition, Instruction> InputsPerPatch { get; } = new();
public Dictionary<IoDefinition, Instruction> OutputsPerPatch { 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) public static void DeclareAll(CodeGenContext context, StructuredProgramInfo info)
{ {
DeclareConstantBuffers(context, context.Properties.ConstantBuffers.Values); DeclareConstantBuffers(context, context.Properties.ConstantBuffers);
DeclareStorageBuffers(context, context.Properties.StorageBuffers.Values); DeclareStorageBuffers(context, context.Properties.StorageBuffers);
DeclareMemories(context, context.Properties.LocalMemories, context.LocalMemories, StorageClass.Private); DeclareMemories(context, context.Properties.LocalMemories, context.LocalMemories, StorageClass.Private);
DeclareMemories(context, context.Properties.SharedMemories, context.SharedMemories, StorageClass.Workgroup); DeclareMemories(context, context.Properties.SharedMemories, context.SharedMemories, StorageClass.Workgroup);
DeclareSamplers(context, context.Properties.Textures.Values); DeclareSamplers(context, context.Properties.Textures);
DeclareImages(context, context.Properties.Images.Values); DeclareImages(context, context.Properties.Images);
DeclareInputsAndOutputs(context, info); DeclareInputsAndOutputs(context, info);
} }
@ -58,7 +58,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
Dictionary<int, SpvInstruction> dict, Dictionary<int, SpvInstruction> dict,
StorageClass storage) 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 pointerType = context.TypePointer(storage, context.GetType(memory.Type, memory.ArrayLength));
var variable = context.Variable(pointerType, storage); 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); 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); 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(); 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 setIndex = context.TargetApi == TargetApi.Vulkan ? buffer.Set : 0;
int alignment = buffer.Layout == BufferLayout.Std140 ? 16 : 4; int alignment = buffer.Layout == BufferLayout.Std140 ? 16 : 4;
@ -145,46 +145,71 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (isBuffer) if (isBuffer)
{ {
context.StorageBuffers.Add(buffer.Binding, variable); context.StorageBuffers.Add(sbPair.Pack(), variable);
} }
else 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; int setIndex = context.TargetApi == TargetApi.Vulkan ? sampler.Set : 0;
var dim = (sampler.Type & SamplerType.Mask) switch SpvInstruction imageType;
SpvInstruction sampledImageType;
if (sampler.Type != SamplerType.None)
{ {
SamplerType.Texture1D => Dim.Dim1D, var dim = GetDim(sampler.Type);
SamplerType.Texture2D => Dim.Dim2D,
SamplerType.Texture3D => Dim.Dim3D,
SamplerType.TextureCube => Dim.Cube,
SamplerType.TextureBuffer => Dim.Buffer,
_ => throw new InvalidOperationException($"Invalid sampler type \"{sampler.Type & SamplerType.Mask}\"."),
};
var imageType = context.TypeImage( imageType = context.TypeImage(
context.TypeFP32(), context.TypeFP32(),
dim, dim,
sampler.Type.HasFlag(SamplerType.Shadow), sampler.Type.HasFlag(SamplerType.Shadow),
sampler.Type.HasFlag(SamplerType.Array), sampler.Type.HasFlag(SamplerType.Array),
sampler.Type.HasFlag(SamplerType.Multisample), sampler.Type.HasFlag(SamplerType.Multisample),
1, 1,
ImageFormat.Unknown); ImageFormat.Unknown);
var sampledImageType = context.TypeSampledImage(imageType); sampledImageType = context.TypeSampledImage(imageType);
var sampledImagePointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageType); }
var sampledImageVariable = context.Variable(sampledImagePointerType, StorageClass.UniformConstant); else
{
imageType = sampledImageType = context.TypeSampler();
}
context.Samplers.Add(sampler.Binding, (imageType, sampledImageType, sampledImageVariable)); var imageTypeForSampler = sampler.Type.HasFlag(SamplerType.Separate) ? imageType : sampledImageType;
context.SamplersTypes.Add(sampler.Binding, sampler.Type); 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.Name(sampledImageVariable, sampler.Name);
context.Decorate(sampledImageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex); 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; int setIndex = context.TargetApi == TargetApi.Vulkan ? image.Set : 0;
@ -211,9 +236,30 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
GetImageFormat(image.Format)); GetImageFormat(image.Format));
var imagePointerType = context.TypePointer(StorageClass.UniformConstant, imageType); 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.Name(imageVariable, image.Name);
context.Decorate(imageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex); context.Decorate(imageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex);

View file

@ -595,31 +595,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var componentType = texOp.Format.GetComponentType(); 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 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) SpvInstruction Src(AggregateType type)
{ {
return context.Get(type, texOp.GetSource(srcIndex++)); return context.Get(type, texOp.GetSource(srcIndex++));
} }
if (isIndexed) SpvInstruction bindlessHandle = isBindless ? Src(AggregateType.S32) : null;
{
Src(AggregateType.S32);
}
int coordsCount = texOp.Type.GetDimensions(); int coordsCount = texOp.Type.GetDimensions();
@ -646,9 +631,19 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
SpvInstruction value = Src(componentType); 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 resultType = context.GetType(componentType);
SpvInstruction imagePointerType = context.TypePointer(StorageClass.Image, resultType); SpvInstruction imagePointerType = context.TypePointer(StorageClass.Image, resultType);
@ -687,26 +682,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var componentType = texOp.Format.GetComponentType(); 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 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) SpvInstruction Src(AggregateType type)
{ {
return context.Get(type, texOp.GetSource(srcIndex++)); return context.Get(type, texOp.GetSource(srcIndex++));
} }
if (isIndexed) SpvInstruction bindlessHandle = isBindless ? Src(AggregateType.S32) : null;
{
Src(AggregateType.S32);
}
int coordsCount = texOp.Type.GetDimensions(); int coordsCount = texOp.Type.GetDimensions();
@ -731,9 +716,27 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
pCoords = Src(AggregateType.S32); pCoords = Src(AggregateType.S32);
} }
(var imageType, var imageVariable) = context.Images[texOp.Binding]; SpvInstruction bindlessIndex;
SpvInstruction image;
if (isBindless)
{
(var imageType, var imagePointerType, var imageVariable) = context.BindlessImages[texOp.Type];
var imageIndex = GenerateBindlessTextureHandleToIndex(context, bindlessHandle);
var imagePointer = context.AccessChain(imagePointerType, imageVariable, imageIndex);
bindlessIndex = imageIndex;
image = context.Load(imageType, imagePointer);
}
else
{
(var imageType, var imageVariable) = context.Images[texOp.Binding];
bindlessIndex = null;
image = context.Load(imageType, imageVariable);
}
var image = context.Load(imageType, imageVariable);
var imageComponentType = context.GetType(componentType); var imageComponentType = context.GetType(componentType);
var swizzledResultType = texOp.GetVectorType(componentType); var swizzledResultType = texOp.GetVectorType(componentType);
@ -749,26 +752,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; 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 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) SpvInstruction Src(AggregateType type)
{ {
return context.Get(type, texOp.GetSource(srcIndex++)); return context.Get(type, texOp.GetSource(srcIndex++));
} }
if (isIndexed) SpvInstruction bindlessHandle = isBindless ? Src(AggregateType.S32) : null;
{
Src(AggregateType.S32);
}
int coordsCount = texOp.Type.GetDimensions(); int coordsCount = texOp.Type.GetDimensions();
@ -818,9 +811,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var texel = context.CompositeConstruct(context.TypeVector(context.GetType(componentType), ComponentsCount), cElems); var texel = context.CompositeConstruct(context.TypeVector(context.GetType(componentType), ComponentsCount), cElems);
(var imageType, var imageVariable) = context.Images[texOp.Binding]; SpvInstruction image;
var image = context.Load(imageType, imageVariable); if (isBindless)
{
(var imageType, var imagePointerType, var imageVariable) = context.BindlessImages[texOp.Type];
var imageIndex = GenerateBindlessTextureHandleToIndex(context, bindlessHandle);
var imagePointer = context.AccessChain(imagePointerType, imageVariable, imageIndex);
image = context.Load(imageType, imagePointer);
}
else
{
(var imageType, var imageVariable) = context.Images[texOp.Binding];
image = context.Load(imageType, imageVariable);
}
context.ImageWrite(image, pCoords, texel, ImageOperandsMask.MaskNone); 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 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; int srcIndex = 0;
SpvInstruction Src(AggregateType type) SpvInstruction Src(AggregateType type)
@ -871,10 +870,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return context.Get(type, texOp.GetSource(srcIndex++)); return context.Get(type, texOp.GetSource(srcIndex++));
} }
if (isIndexed) SpvInstruction bindlessHandle = isBindless ? Src(AggregateType.S32) : null;
{
Src(AggregateType.S32);
}
int pCount = texOp.Type.GetDimensions(); int pCount = texOp.Type.GetDimensions();
@ -897,9 +893,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
pCoords = Src(AggregateType.FP32); pCoords = Src(AggregateType.FP32);
} }
(_, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding]; SpvInstruction image;
var image = context.Load(sampledImageType, sampledImageVariable); if (isBindless)
{
(var imageType, _, var imagePointerType, var imageVariable) = context.BindlessTextures[texOp.Type & ~SamplerType.Shadow];
var imageIndex = GenerateBindlessTextureHandleToIndex(context, bindlessHandle);
var imagePointer = context.AccessChain(imagePointerType, imageVariable, imageIndex);
image = context.Load(imageType, imagePointer);
}
else
{
(_, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding];
image = context.Load(sampledImageType, sampledImageVariable);
}
var resultType = context.TypeVector(context.TypeFP32(), 2); var resultType = context.TypeVector(context.TypeFP32(), 2);
var packed = context.ImageQueryLod(resultType, image, pCoords); 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 hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0;
bool isArray = (texOp.Type & SamplerType.Array) != 0; bool isArray = (texOp.Type & SamplerType.Array) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0; bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0;
bool isShadow = (texOp.Type & SamplerType.Shadow) != 0; bool isShadow = (texOp.Type & SamplerType.Shadow) != 0;
bool colorIsVector = isGather || !isShadow; bool colorIsVector = isGather || !isShadow;
// TODO: Bindless texture support. For now we just return 0. int srcIndex = 0;
if (isBindless)
{
return GetZeroOperationResult(context, texOp, AggregateType.FP32, colorIsVector);
}
int srcIndex = isBindless ? 1 : 0;
SpvInstruction Src(AggregateType type) SpvInstruction Src(AggregateType type)
{ {
return context.Get(type, texOp.GetSource(srcIndex++)); return context.Get(type, texOp.GetSource(srcIndex++));
} }
if (isIndexed) SpvInstruction bindlessHandle = isBindless ? Src(AggregateType.S32) : null;
{
Src(AggregateType.S32);
}
int coordsCount = texOp.Type.GetDimensions(); int coordsCount = texOp.Type.GetDimensions();
@ -1262,6 +1262,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
} }
SpvInstruction pCoords = AssemblePVector(pCount); SpvInstruction pCoords = AssemblePVector(pCount);
SpvInstruction bindlessIndex = isBindless ? GenerateBindlessTextureHandleToIndex(context, bindlessHandle) : null;
SpvInstruction AssembleDerivativesVector(int count) 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 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) if (intCoords)
{ {
@ -1493,13 +1518,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return new OperationResult(AggregateType.S32, context.Constant(context.TypeS32(), 0)); 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 imageType, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding];
var image = context.Load(sampledImageType, sampledImageVariable); var image = context.Load(sampledImageType, sampledImageVariable);
@ -1516,22 +1534,30 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; 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) 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);
} }
else
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
if (isIndexed)
{ {
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); image = context.Image(imageType, image);
if (texOp.Index == 3) if (texOp.Index == 3)
@ -1540,7 +1566,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
} }
else 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; bool hasLod = !type.HasFlag(SamplerType.Multisample) && type != SamplerType.TextureBuffer;
int dimensions = (type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : type.GetDimensions(); int dimensions = (type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : type.GetDimensions();
@ -1556,7 +1582,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (hasLod) if (hasLod)
{ {
int lodSrcIndex = isBindless || isIndexed ? 1 : 0; int lodSrcIndex = isBindless ? 1 : 0;
var lod = context.GetS32(operation.GetSource(lodSrcIndex)); var lod = context.GetS32(operation.GetSource(lodSrcIndex));
result = context.ImageQuerySizeLod(resultType, image, lod); result = context.ImageQuerySizeLod(resultType, image, lod);
} }
@ -1638,6 +1664,51 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return new OperationResult(AggregateType.Bool, result); 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( private static OperationResult GenerateCompare(
CodeGenContext context, CodeGenContext context,
AstOperation operation, AstOperation operation,
@ -1746,8 +1817,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
} }
BufferDefinition buffer = storageKind == StorageKind.ConstantBuffer BufferDefinition buffer = storageKind == StorageKind.ConstantBuffer
? context.Properties.ConstantBuffers[bindingIndex.Value] ? context.Properties.ConstantBuffers[SetBindingPair.Unpack(bindingIndex.Value)]
: context.Properties.StorageBuffers[bindingIndex.Value]; : context.Properties.StorageBuffers[SetBindingPair.Unpack(bindingIndex.Value)];
StructureField field = buffer.Type.Fields[fieldIndex.Value]; StructureField field = buffer.Type.Fields[fieldIndex.Value];
storageClass = StorageClass.Uniform; storageClass = StorageClass.Uniform;

View file

@ -105,6 +105,17 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
context.AddCapability(Capability.ShaderViewportMaskNV); 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) if ((info.HelperFunctionsMask & NeedsInvocationIdMask) != 0)
{ {
info.IoDefinitions.Add(new IoDefinition(StorageKind.Input, IoVariable.SubgroupLaneId)); 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 NvnBaseVertexByteOffset = 0x640;
public const int NvnBaseInstanceByteOffset = 0x644; public const int NvnBaseInstanceByteOffset = 0x644;
public const int NvnDrawIndexByteOffset = 0x648; 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; 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> /// <summary>
/// Queries texture coordinate normalization information. /// Queries texture coordinate normalization information.
/// </summary> /// </summary>

View file

@ -161,5 +161,13 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
inst &= Instruction.Mask; inst &= Instruction.Mask;
return inst == Instruction.Lod || inst == Instruction.TextureQuerySamples || inst == Instruction.TextureQuerySize; 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) public void AppendSources(Operand[] operands)
{ {
int startIndex = _sources.Length; int startIndex = _sources.Length;

View file

@ -26,19 +26,11 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
Binding = binding; Binding = binding;
} }
public void TurnIntoIndexed(int binding)
{
Type |= SamplerType.Indexed;
Flags &= ~TextureFlags.Bindless;
Binding = binding;
}
public void SetBinding(int binding) public void SetBinding(int binding)
{ {
if ((Flags & TextureFlags.Bindless) != 0) if ((Flags & TextureFlags.Bindless) != 0)
{ {
Flags &= ~TextureFlags.Bindless; Flags &= ~TextureFlags.Bindless;
RemoveSource(0); RemoveSource(0);
} }
@ -49,5 +41,14 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
{ {
Flags |= TextureFlags.LodLevel; 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 ReservedTextures { get; }
public readonly int ReservedImages { 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; ReservedConstantBuffers = reservations.ReservedConstantBuffers;
ReservedStorageBuffers = reservations.ReservedStorageBuffers; ReservedStorageBuffers = reservations.ReservedStorageBuffers;

View file

@ -10,6 +10,8 @@
</ItemGroup> </ItemGroup>
<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\MultiplyHighS32.glsl" />
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\MultiplyHighU32.glsl" /> <EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\MultiplyHighU32.glsl" />
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\SwizzleAdd.glsl" /> <EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\SwizzleAdd.glsl" />

View file

@ -16,9 +16,9 @@ namespace Ryujinx.Graphics.Shader
Mask = 0xff, Mask = 0xff,
Array = 1 << 8, Array = 1 << 8,
Indexed = 1 << 9, Multisample = 1 << 9,
Multisample = 1 << 10, Shadow = 1 << 10,
Shadow = 1 << 11, Separate = 1 << 11,
} }
static class SamplerTypeExtensions static class SamplerTypeExtensions
@ -40,6 +40,7 @@ namespace Ryujinx.Graphics.Shader
{ {
string typeName = (type & SamplerType.Mask) switch string typeName = (type & SamplerType.Mask) switch
{ {
SamplerType.None => "sampler",
SamplerType.Texture1D => "sampler1D", SamplerType.Texture1D => "sampler1D",
SamplerType.TextureBuffer => "samplerBuffer", SamplerType.TextureBuffer => "samplerBuffer",
SamplerType.Texture2D => "sampler2D", SamplerType.Texture2D => "sampler2D",
@ -66,6 +67,31 @@ namespace Ryujinx.Graphics.Shader
return typeName; 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) public static string ToGlslImageType(this SamplerType type, AggregateType componentType)
{ {
string typeName = (type & SamplerType.Mask) switch string typeName = (type & SamplerType.Mask) switch

View file

@ -11,6 +11,7 @@ namespace Ryujinx.Graphics.Shader
public ReadOnlyCollection<TextureDescriptor> Images { get; } public ReadOnlyCollection<TextureDescriptor> Images { get; }
public ShaderStage Stage { get; } public ShaderStage Stage { get; }
public BindlessTextureFlags BindlessTextureFlags { get; }
public int GeometryVerticesPerPrimitive { get; } public int GeometryVerticesPerPrimitive { get; }
public int GeometryMaxOutputVertices { get; } public int GeometryMaxOutputVertices { get; }
public int ThreadsPerInputPrimitive { get; } public int ThreadsPerInputPrimitive { get; }
@ -27,6 +28,7 @@ namespace Ryujinx.Graphics.Shader
TextureDescriptor[] textures, TextureDescriptor[] textures,
TextureDescriptor[] images, TextureDescriptor[] images,
ShaderStage stage, ShaderStage stage,
BindlessTextureFlags bindlessTextureFlags,
int geometryVerticesPerPrimitive, int geometryVerticesPerPrimitive,
int geometryMaxOutputVertices, int geometryMaxOutputVertices,
int threadsPerInputPrimitive, int threadsPerInputPrimitive,
@ -43,6 +45,7 @@ namespace Ryujinx.Graphics.Shader
Images = Array.AsReadOnly(images); Images = Array.AsReadOnly(images);
Stage = stage; Stage = stage;
BindlessTextureFlags = bindlessTextureFlags;
GeometryVerticesPerPrimitive = geometryVerticesPerPrimitive; GeometryVerticesPerPrimitive = geometryVerticesPerPrimitive;
GeometryMaxOutputVertices = geometryMaxOutputVertices; GeometryMaxOutputVertices = geometryMaxOutputVertices;
ThreadsPerInputPrimitive = threadsPerInputPrimitive; 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 class ShaderProperties
{ {
private readonly Dictionary<int, BufferDefinition> _constantBuffers; private readonly Dictionary<SetBindingPair, BufferDefinition> _constantBuffers;
private readonly Dictionary<int, BufferDefinition> _storageBuffers; private readonly Dictionary<SetBindingPair, BufferDefinition> _storageBuffers;
private readonly Dictionary<int, TextureDefinition> _textures; private readonly Dictionary<SetBindingPair, TextureDefinition> _textures;
private readonly Dictionary<int, TextureDefinition> _images; private readonly Dictionary<SetBindingPair, TextureDefinition> _images;
private readonly Dictionary<int, MemoryDefinition> _localMemories; private readonly Dictionary<int, MemoryDefinition> _localMemories;
private readonly Dictionary<int, MemoryDefinition> _sharedMemories; private readonly Dictionary<int, MemoryDefinition> _sharedMemories;
public IReadOnlyDictionary<int, BufferDefinition> ConstantBuffers => _constantBuffers; public IReadOnlyDictionary<SetBindingPair, BufferDefinition> ConstantBuffers => _constantBuffers;
public IReadOnlyDictionary<int, BufferDefinition> StorageBuffers => _storageBuffers; public IReadOnlyDictionary<SetBindingPair, BufferDefinition> StorageBuffers => _storageBuffers;
public IReadOnlyDictionary<int, TextureDefinition> Textures => _textures; public IReadOnlyDictionary<SetBindingPair, TextureDefinition> Textures => _textures;
public IReadOnlyDictionary<int, TextureDefinition> Images => _images; public IReadOnlyDictionary<SetBindingPair, TextureDefinition> Images => _images;
public IReadOnlyDictionary<int, MemoryDefinition> LocalMemories => _localMemories; public IReadOnlyDictionary<int, MemoryDefinition> LocalMemories => _localMemories;
public IReadOnlyDictionary<int, MemoryDefinition> SharedMemories => _sharedMemories; public IReadOnlyDictionary<int, MemoryDefinition> SharedMemories => _sharedMemories;
public ShaderProperties() public ShaderProperties()
{ {
_constantBuffers = new Dictionary<int, BufferDefinition>(); _constantBuffers = new Dictionary<SetBindingPair, BufferDefinition>();
_storageBuffers = new Dictionary<int, BufferDefinition>(); _storageBuffers = new Dictionary<SetBindingPair, BufferDefinition>();
_textures = new Dictionary<int, TextureDefinition>(); _textures = new Dictionary<SetBindingPair, TextureDefinition>();
_images = new Dictionary<int, TextureDefinition>(); _images = new Dictionary<SetBindingPair, TextureDefinition>();
_localMemories = new Dictionary<int, MemoryDefinition>(); _localMemories = new Dictionary<int, MemoryDefinition>();
_sharedMemories = new Dictionary<int, MemoryDefinition>(); _sharedMemories = new Dictionary<int, MemoryDefinition>();
} }
public void AddOrUpdateConstantBuffer(BufferDefinition definition) public void AddOrUpdateConstantBuffer(BufferDefinition definition)
{ {
_constantBuffers[definition.Binding] = definition; _constantBuffers[new(definition.Set, definition.Binding)] = definition;
} }
public void AddOrUpdateStorageBuffer(BufferDefinition definition) public void AddOrUpdateStorageBuffer(BufferDefinition definition)
{ {
_storageBuffers[definition.Binding] = definition; _storageBuffers[new(definition.Set, definition.Binding)] = definition;
} }
public void AddOrUpdateTexture(TextureDefinition definition) public void AddOrUpdateTexture(TextureDefinition definition)
{ {
_textures[definition.Binding] = definition; _textures[new(definition.Set, definition.Binding)] = definition;
} }
public void AddOrUpdateImage(TextureDefinition definition) public void AddOrUpdateImage(TextureDefinition definition)
{ {
_images[definition.Binding] = definition; _images[new(definition.Set, definition.Binding)] = definition;
} }
public int AddLocalMemory(MemoryDefinition definition) public int AddLocalMemory(MemoryDefinition definition)

View file

@ -8,8 +8,9 @@ namespace Ryujinx.Graphics.Shader
public SamplerType Type { get; } public SamplerType Type { get; }
public TextureFormat Format { get; } public TextureFormat Format { get; }
public TextureUsageFlags Flags { 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; Set = set;
Binding = binding; Binding = binding;
@ -17,11 +18,12 @@ namespace Ryujinx.Graphics.Shader
Type = type; Type = type;
Format = format; Format = format;
Flags = flags; Flags = flags;
ArraySize = arraySize;
} }
public TextureDefinition SetFlag(TextureUsageFlags flag) 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;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -13,6 +14,11 @@ namespace Ryujinx.Graphics.Shader
public static class TextureHandle 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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int PackSlots(int cbufSlot0, int cbufSlot1) public static int PackSlots(int cbufSlot0, int cbufSlot1)
{ {
@ -120,5 +126,11 @@ namespace Ryujinx.Graphics.Shader
return handle; 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 Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection.Metadata;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
namespace Ryujinx.Graphics.Shader.Translation namespace Ryujinx.Graphics.Shader.Translation
@ -77,7 +78,9 @@ namespace Ryujinx.Graphics.Shader.Translation
HelperFunctionName.ConvertDoubleToFloat => GenerateConvertDoubleToFloatFunction(), HelperFunctionName.ConvertDoubleToFloat => GenerateConvertDoubleToFloatFunction(),
HelperFunctionName.ConvertFloatToDouble => GenerateConvertFloatToDoubleFunction(), HelperFunctionName.ConvertFloatToDouble => GenerateConvertFloatToDoubleFunction(),
HelperFunctionName.TexelFetchScale => GenerateTexelFetchScaleFunction(), HelperFunctionName.TexelFetchScale => GenerateTexelFetchScaleFunction(),
HelperFunctionName.TexelFetchScaleBindless => GenerateTexelFetchScaleBindlessFunction(),
HelperFunctionName.TextureSizeUnscale => GenerateTextureSizeUnscaleFunction(), HelperFunctionName.TextureSizeUnscale => GenerateTextureSizeUnscaleFunction(),
HelperFunctionName.TextureSizeUnscaleBindless => GenerateTextureSizeUnscaleBindlessFunction(),
_ => throw new ArgumentException($"Invalid function name {functionName}"), _ => 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); 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() private Function GenerateTextureSizeUnscaleFunction()
{ {
EmitterContext context = new(); EmitterContext context = new();
@ -436,6 +462,29 @@ namespace Ryujinx.Graphics.Shader.Translation
return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "TextureSizeUnscale", true, 2, 0); 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) private Operand GetScaleIndex(EmitterContext context, Operand index)
{ {
switch (_stage) 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) public static Operand GetBitOffset(EmitterContext context, Operand offset)
{ {
return context.ShiftLeft(context.BitwiseAnd(offset, Const(3)), Const(3)); return context.ShiftLeft(context.BitwiseAnd(offset, Const(3)), Const(3));

View file

@ -15,6 +15,8 @@ namespace Ryujinx.Graphics.Shader.Translation
ShuffleUp, ShuffleUp,
ShuffleXor, ShuffleXor,
TexelFetchScale, TexelFetchScale,
TexelFetchScaleBindless,
TextureSizeUnscale, 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); GlobalToStorage.RunPass(context.Hfm, context.Blocks, context.ResourceManager, context.GpuAccessor, context.TargetLanguage);
bool hostSupportsShaderFloat64 = context.GpuAccessor.QueryHostSupportsShaderFloat64(); bool hostSupportsShaderFloat64 = context.GpuAccessor.QueryHostSupportsShaderFloat64();
int textureBufferIndex = context.GpuAccessor.QueryTextureBufferIndex();
// Those passes are looking for specific patterns and only needs to run once. // Those passes are looking for specific patterns and only needs to run once.
for (int blkIndex = 0; blkIndex < context.Blocks.Length; blkIndex++) 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); BindlessElimination.RunPass(context.Blocks[blkIndex], context.ResourceManager, context.GpuAccessor);
// FragmentCoord only exists on fragment shaders, so we don't need to check other stages. // 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 DefaultLocalMemorySize = 128;
private const int DefaultSharedMemorySize = 4096; 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 static readonly string[] _stagePrefixes = new string[] { "cp", "vp", "tcp", "tep", "gp", "fp" };
private readonly IGpuAccessor _gpuAccessor; private readonly IGpuAccessor _gpuAccessor;
@ -28,11 +25,10 @@ namespace Ryujinx.Graphics.Shader.Translation
private uint _sbSlotWritten; private uint _sbSlotWritten;
private readonly Dictionary<int, int> _sbSlots; private readonly Dictionary<int, int> _sbSlots;
private readonly Dictionary<int, int> _sbSlotsReverse;
private readonly HashSet<int> _usedConstantBufferBindings; 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 private struct TextureMeta
{ {
@ -73,7 +69,6 @@ namespace Ryujinx.Graphics.Shader.Translation
_sbSlotToBindingMap.AsSpan().Fill(-1); _sbSlotToBindingMap.AsSpan().Fill(-1);
_sbSlots = new(); _sbSlots = new();
_sbSlotsReverse = new();
_usedConstantBufferBindings = new(); _usedConstantBufferBindings = new();
@ -147,6 +142,68 @@ namespace Ryujinx.Graphics.Shader.Translation
return Properties.AddLocalMemory(new MemoryDefinition(name, type, arrayLength)); 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) public int GetConstantBufferBinding(int slot)
{ {
int binding = _cbSlotToBindingMap[slot]; int binding = _cbSlotToBindingMap[slot];
@ -158,7 +215,7 @@ namespace Ryujinx.Graphics.Shader.Translation
AddNewConstantBuffer(binding, $"{_stagePrefix}_c{slotNumber}"); 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) 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)) if (!TryGetSbSlot((byte)sbCbSlot, (ushort)sbCbOffset, out int slot))
{ {
binding = 0; binding = 0;
return false; return false;
} }
@ -179,6 +237,8 @@ namespace Ryujinx.Graphics.Shader.Translation
AddNewStorageBuffer(binding, $"{_stagePrefix}_s{slotNumber}"); AddNewStorageBuffer(binding, $"{_stagePrefix}_s{slotNumber}");
} }
binding = SetBindingPair.Pack(Constants.VkStorageBufferSetIndex, binding);
if (write) if (write)
{ {
_sbSlotWritten |= 1u << slot; _sbSlotWritten |= 1u << slot;
@ -201,7 +261,6 @@ namespace Ryujinx.Graphics.Shader.Translation
} }
_sbSlots.Add(key, slot); _sbSlots.Add(key, slot);
_sbSlotsReverse.Add(slot, key);
} }
return true; return true;
@ -211,7 +270,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{ {
for (slot = 0; slot < _cbSlotToBindingMap.Length; slot++) for (slot = 0; slot < _cbSlotToBindingMap.Length; slot++)
{ {
if (_cbSlotToBindingMap[slot] == binding) if (SetBindingPair.Pack(Constants.VkConstantBufferSetIndex, _cbSlotToBindingMap[slot]) == binding)
{ {
return true; return true;
} }
@ -245,7 +304,7 @@ namespace Ryujinx.Graphics.Shader.Translation
_gpuAccessor.RegisterTexture(handle, cbufSlot); _gpuAccessor.RegisterTexture(handle, cbufSlot);
return binding; return SetBindingPair.Pack(isImage ? Constants.VkImageSetIndex : Constants.VkTextureSetIndex, binding);
} }
private int GetTextureOrImageBinding( private int GetTextureOrImageBinding(
@ -260,7 +319,6 @@ namespace Ryujinx.Graphics.Shader.Translation
bool coherent) bool coherent)
{ {
var dimensions = type.GetDimensions(); var dimensions = type.GetDimensions();
var isIndexed = type.HasFlag(SamplerType.Indexed);
var dict = isImage ? _usedImages : _usedTextures; var dict = isImage ? _usedImages : _usedTextures;
var usageFlags = TextureUsageFlags.None; var usageFlags = TextureUsageFlags.None;
@ -269,7 +327,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{ {
usageFlags |= TextureUsageFlags.NeedsScaleValue; usageFlags |= TextureUsageFlags.NeedsScaleValue;
var canScale = _stage.SupportsRenderScale() && !isIndexed && !write && dimensions == 2; var canScale = _stage.SupportsRenderScale() && !write && dimensions == 2;
if (!canScale) if (!canScale)
{ {
@ -289,76 +347,65 @@ namespace Ryujinx.Graphics.Shader.Translation
usageFlags |= TextureUsageFlags.ImageCoherent; usageFlags |= TextureUsageFlags.ImageCoherent;
} }
int arraySize = isIndexed ? SamplerArraySize : 1; var info = new TextureInfo(cbufSlot, handle, format);
int firstBinding = -1; var meta = new TextureMeta()
for (int layer = 0; layer < arraySize; layer++)
{ {
var info = new TextureInfo(cbufSlot, handle + layer * 2, isIndexed, format); AccurateType = accurateType,
var meta = new TextureMeta() Type = type,
{ UsageFlags = usageFlags
AccurateType = accurateType, };
Type = type,
UsageFlags = usageFlags,
};
int binding; int binding;
if (dict.TryGetValue(info, out var existingMeta)) if (dict.TryGetValue(info, out var existingMeta))
{ {
dict[info] = MergeTextureMeta(meta, existingMeta); dict[info] = MergeTextureMeta(meta, existingMeta);
binding = existingMeta.Binding; binding = existingMeta.Binding;
} }
else else
{ {
bool isBuffer = (type & SamplerType.Mask) == SamplerType.TextureBuffer; bool isBuffer = (type & SamplerType.Mask) == SamplerType.TextureBuffer;
binding = isImage binding = isImage
? _gpuAccessor.QueryBindingImage(dict.Count, isBuffer) ? _gpuAccessor.QueryBindingImage(dict.Count, isBuffer)
: _gpuAccessor.QueryBindingTexture(dict.Count, isBuffer); : _gpuAccessor.QueryBindingTexture(dict.Count, isBuffer);
meta.Binding = binding; meta.Binding = binding;
dict.Add(info, meta); dict.Add(info, meta);
}
string nameSuffix;
if (isImage)
{
nameSuffix = cbufSlot < 0
? $"i_tcb_{handle:X}_{format.ToGlslFormat()}"
: $"i_cb{cbufSlot}_{handle:X}_{format.ToGlslFormat()}";
}
else
{
nameSuffix = cbufSlot < 0 ? $"t_tcb_{handle:X}" : $"t_cb{cbufSlot}_{handle:X}";
}
var definition = new TextureDefinition(
isImage ? 3 : 2,
binding,
$"{_stagePrefix}_{nameSuffix}",
meta.Type,
info.Format,
meta.UsageFlags);
if (isImage)
{
Properties.AddOrUpdateImage(definition);
}
else
{
Properties.AddOrUpdateTexture(definition);
}
if (layer == 0)
{
firstBinding = binding;
}
} }
return firstBinding; string nameSuffix;
if (isImage)
{
nameSuffix = cbufSlot < 0
? $"i_tcb_{handle:X}_{format.ToGlslFormat()}"
: $"i_cb{cbufSlot}_{handle:X}_{format.ToGlslFormat()}";
}
else
{
nameSuffix = cbufSlot < 0 ? $"t_tcb_{handle:X}" : $"t_cb{cbufSlot}_{handle:X}";
}
var definition = new TextureDefinition(
isImage ? 3 : 2,
binding,
$"{_stagePrefix}_{nameSuffix}",
meta.Type,
info.Format,
meta.UsageFlags);
if (isImage)
{
Properties.AddOrUpdateImage(definition);
}
else
{
Properties.AddOrUpdateTexture(definition);
}
return binding;
} }
private static TextureMeta MergeTextureMeta(TextureMeta meta, TextureMeta existingMeta) private static TextureMeta MergeTextureMeta(TextureMeta meta, TextureMeta existingMeta)
@ -385,7 +432,7 @@ namespace Ryujinx.Graphics.Shader.Translation
foreach ((TextureInfo info, TextureMeta meta) in _usedTextures) foreach ((TextureInfo info, TextureMeta meta) in _usedTextures)
{ {
if (meta.Binding == binding) if (SetBindingPair.Pack(Constants.VkTextureSetIndex, meta.Binding) == binding)
{ {
selectedInfo = info; selectedInfo = info;
selectedMeta = meta; selectedMeta = meta;
@ -399,8 +446,7 @@ namespace Ryujinx.Graphics.Shader.Translation
selectedMeta.UsageFlags |= TextureUsageFlags.NeedsScaleValue; selectedMeta.UsageFlags |= TextureUsageFlags.NeedsScaleValue;
var dimensions = type.GetDimensions(); var dimensions = type.GetDimensions();
var isIndexed = type.HasFlag(SamplerType.Indexed); var canScale = _stage.SupportsRenderScale() && dimensions == 2;
var canScale = _stage.SupportsRenderScale() && !isIndexed && dimensions == 2;
if (!canScale) if (!canScale)
{ {
@ -428,7 +474,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{ {
int binding = _cbSlotToBindingMap[slot]; 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); descriptors[descriptorIndex++] = new BufferDescriptor(binding, slot);
} }
@ -502,7 +548,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{ {
foreach ((TextureInfo info, TextureMeta meta) in _usedTextures) foreach ((TextureInfo info, TextureMeta meta) in _usedTextures)
{ {
if (meta.Binding == binding) if (SetBindingPair.Pack(Constants.VkTextureSetIndex, meta.Binding) == binding)
{ {
cbufSlot = info.CbufSlot; cbufSlot = info.CbufSlot;
handle = info.Handle; handle = info.Handle;
@ -516,19 +562,19 @@ namespace Ryujinx.Graphics.Shader.Translation
return false; 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) public int FindTextureDescriptorIndex(int binding)
{ {
return FindDescriptorIndex(GetTextureDescriptors(), binding); return FindDescriptorIndex(GetTextureDescriptors(), Constants.VkTextureSetIndex, binding);
} }
public int FindImageDescriptorIndex(int binding) public int FindImageDescriptorIndex(int binding)
{ {
return FindDescriptorIndex(GetImageDescriptors(), binding); return FindDescriptorIndex(GetImageDescriptors(), Constants.VkImageSetIndex, binding);
} }
private void AddNewConstantBuffer(int binding, string name) private void AddNewConstantBuffer(int binding, string name)

View file

@ -32,12 +32,12 @@ namespace Ryujinx.Graphics.Shader.Translation
private readonly Dictionary<IoDefinition, int> _offsets; private readonly Dictionary<IoDefinition, int> _offsets;
internal IReadOnlyDictionary<IoDefinition, int> Offsets => _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. // All stages reserves the first constant buffer binding for the support buffer.
ReservedConstantBuffers = 1; ReservedConstantBuffers = 1;
ReservedStorageBuffers = 0; ReservedStorageBuffers = 0;
ReservedTextures = 0; ReservedTextures = targetApi == TargetApi.OpenGL ? 2 : 0; // Reserve 2 texture bindings on OpenGL for bindless emulation.
ReservedImages = 0; ReservedImages = 0;
if (isTransformFeedbackEmulated) if (isTransformFeedbackEmulated)
@ -71,10 +71,11 @@ namespace Ryujinx.Graphics.Shader.Translation
internal ResourceReservations( internal ResourceReservations(
IGpuAccessor gpuAccessor, IGpuAccessor gpuAccessor,
TargetApi targetApi,
bool isTransformFeedbackEmulated, bool isTransformFeedbackEmulated,
bool vertexAsCompute, bool vertexAsCompute,
IoUsage? vacInput, IoUsage? vacInput,
IoUsage vacOutput) : this(isTransformFeedbackEmulated, vertexAsCompute) IoUsage vacOutput) : this(targetApi, isTransformFeedbackEmulated, vertexAsCompute)
{ {
if (vertexAsCompute) if (vertexAsCompute)
{ {

View file

@ -9,9 +9,12 @@ namespace Ryujinx.Graphics.Shader.Translation
public readonly ShaderDefinitions Definitions; public readonly ShaderDefinitions Definitions;
public readonly ResourceManager ResourceManager; public readonly ResourceManager ResourceManager;
public readonly IGpuAccessor GpuAccessor; public readonly IGpuAccessor GpuAccessor;
public readonly TargetApi TargetApi;
public readonly TargetLanguage TargetLanguage; public readonly TargetLanguage TargetLanguage;
public readonly ShaderStage Stage; public readonly ShaderStage Stage;
public readonly ref FeatureFlags UsedFeatures; public readonly ref FeatureFlags UsedFeatures;
public readonly ref BindlessTextureFlags BindlessTextureFlags;
public readonly ref bool BindlessTexturesAllowed;
public TransformContext( public TransformContext(
HelperFunctionManager hfm, HelperFunctionManager hfm,
@ -19,18 +22,24 @@ namespace Ryujinx.Graphics.Shader.Translation
ShaderDefinitions definitions, ShaderDefinitions definitions,
ResourceManager resourceManager, ResourceManager resourceManager,
IGpuAccessor gpuAccessor, IGpuAccessor gpuAccessor,
TargetApi targetApi,
TargetLanguage targetLanguage, TargetLanguage targetLanguage,
ShaderStage stage, ShaderStage stage,
ref FeatureFlags usedFeatures) ref FeatureFlags usedFeatures,
ref BindlessTextureFlags bindlessTextureFlags,
ref bool bindlessTexturesAllowed)
{ {
Hfm = hfm; Hfm = hfm;
Blocks = blocks; Blocks = blocks;
Definitions = definitions; Definitions = definitions;
ResourceManager = resourceManager; ResourceManager = resourceManager;
GpuAccessor = gpuAccessor; GpuAccessor = gpuAccessor;
TargetApi = targetApi;
TargetLanguage = targetLanguage; TargetLanguage = targetLanguage;
Stage = stage; Stage = stage;
UsedFeatures = ref usedFeatures; 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) if (node.Value is TextureOperation texOp)
{ {
node = InsertTexelFetchScale(context.Hfm, node, context.ResourceManager, context.Stage); LinkedListNode<INode> prevNode = node;
node = InsertTextureSizeUnscale(context.Hfm, node, context.ResourceManager, context.Stage); 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) 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 = InsertCoordGatherBias(node, context.ResourceManager, context.GpuAccessor);
node = InsertConstOffsets(node, context.GpuAccessor, context.Stage); node = InsertConstOffsets(node, context.GpuAccessor, context.Stage);
@ -39,31 +53,41 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
HelperFunctionManager hfm, HelperFunctionManager hfm,
LinkedListNode<INode> node, LinkedListNode<INode> node,
ResourceManager resourceManager, ResourceManager resourceManager,
ShaderStage stage) ShaderStage stage,
TargetApi targetApi)
{ {
TextureOperation texOp = (TextureOperation)node.Value; TextureOperation texOp = (TextureOperation)node.Value;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
int coordsCount = texOp.Type.GetDimensions(); int coordsCount = texOp.Type.GetDimensions();
int coordsIndex = isBindless || isIndexed ? 1 : 0; int coordsIndex = isBindless ? 1 : 0;
bool isImage = IsImageInstructionWithScale(texOp.Inst); bool isImage = IsImageInstructionWithScale(texOp.Inst);
if ((texOp.Inst == Instruction.TextureSample || isImage) && if ((texOp.Inst == Instruction.TextureSample || isImage) &&
(intCoords || isImage) && (intCoords || isImage) &&
!isBindless && (!isBindless || targetApi == TargetApi.Vulkan) && // TODO: OpenGL support.
!isIndexed &&
stage.SupportsRenderScale() && stage.SupportsRenderScale() &&
TypeSupportsScale(texOp.Type)) TypeSupportsScale(texOp.Type))
{ {
int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TexelFetchScale); int functionId;
int samplerIndex = isImage Operand samplerIndex;
? resourceManager.GetTextureDescriptors().Length + resourceManager.FindImageDescriptorIndex(texOp.Binding)
: resourceManager.FindTextureDescriptorIndex(texOp.Binding); 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++) for (int index = 0; index < coordsCount; index++)
{ {
@ -72,11 +96,11 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
if (stage == ShaderStage.Fragment) 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 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)); node.List.AddBefore(node, new Operation(Instruction.Call, 0, scaledCoord, callArgs));
@ -92,22 +116,32 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
HelperFunctionManager hfm, HelperFunctionManager hfm,
LinkedListNode<INode> node, LinkedListNode<INode> node,
ResourceManager resourceManager, ResourceManager resourceManager,
ShaderStage stage) ShaderStage stage,
TargetApi targetApi)
{ {
TextureOperation texOp = (TextureOperation)node.Value; TextureOperation texOp = (TextureOperation)node.Value;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
if (texOp.Inst == Instruction.TextureQuerySize && if (texOp.Inst == Instruction.TextureQuerySize &&
texOp.Index < 2 && texOp.Index < 2 &&
!isBindless && (!isBindless || targetApi == TargetApi.Vulkan) && // TODO: OpenGL support.
!isIndexed &&
stage.SupportsRenderScale() && stage.SupportsRenderScale() &&
TypeSupportsScale(texOp.Type)) TypeSupportsScale(texOp.Type))
{ {
int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TextureSizeUnscale); int functionId;
int samplerIndex = resourceManager.FindTextureDescriptorIndex(texOp.Binding); 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--) 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)); node.List.AddAfter(node, new Operation(Instruction.Call, 0, unscaledSize, callArgs));
} }
@ -142,7 +176,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
LinkedListNode<INode> node, LinkedListNode<INode> node,
ResourceManager resourceManager, ResourceManager resourceManager,
IGpuAccessor gpuAccessor, IGpuAccessor gpuAccessor,
ShaderStage stage) ShaderStage stage,
TargetApi targetApi)
{ {
// Emulate non-normalized coordinates by normalizing the coordinates on the shader. // 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, // 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; return node;
} }
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
int coordsCount = texOp.Type.GetDimensions(); 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; int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount;
@ -180,7 +213,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
Operand[] texSizeSources; Operand[] texSizeSources;
if (isBindless || isIndexed) if (isBindless)
{ {
texSizeSources = new Operand[] { texOp.GetSource(0), Const(0) }; texSizeSources = new Operand[] { texOp.GetSource(0), Const(0) };
} }
@ -209,7 +242,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
texOp.SetSource(coordsIndex + index, coordNormalized); texOp.SetSource(coordsIndex + index, coordNormalized);
InsertTextureSizeUnscale(hfm, textureSizeNode, resourceManager, stage); InsertTextureSizeUnscale(hfm, textureSizeNode, resourceManager, stage, targetApi);
} }
return node; return node;
@ -234,10 +267,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
return node; return node;
} }
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
int coordsCount = texOp.Type.GetDimensions(); 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; int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount;
@ -249,7 +280,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
Operand[] texSizeSources; Operand[] texSizeSources;
if (isBindless || isIndexed) if (isBindless)
{ {
texSizeSources = new Operand[] { texOp.GetSource(0), Const(0) }; 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 hasLodLevel = (texOp.Flags & TextureFlags.LodLevel) != 0;
bool isArray = (texOp.Type & SamplerType.Array) != 0; bool isArray = (texOp.Type & SamplerType.Array) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0; bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0;
bool isShadow = (texOp.Type & SamplerType.Shadow) != 0; bool isShadow = (texOp.Type & SamplerType.Shadow) != 0;
@ -347,7 +377,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
int copyCount = 0; int copyCount = 0;
if (isBindless || isIndexed) if (isBindless)
{ {
copyCount++; copyCount++;
} }
@ -424,7 +454,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
sources[dstIndex++] = texOp.GetSource(srcIndex++); sources[dstIndex++] = texOp.GetSource(srcIndex++);
} }
int coordsIndex = isBindless || isIndexed ? 1 : 0; int coordsIndex = isBindless ? 1 : 0;
int componentIndex = texOp.Index; int componentIndex = texOp.Index;
@ -435,7 +465,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
dests[i] = texOp.GetDest(i); dests[i] = texOp.GetDest(i);
} }
Operand bindlessHandle = isBindless || isIndexed ? sources[0] : null; Operand bindlessHandle = isBindless ? sources[0] : null;
LinkedListNode<INode> oldNode = node; LinkedListNode<INode> oldNode = node;
@ -748,5 +778,113 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
{ {
return (type & SamplerType.Mask) == SamplerType.Texture2D; 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; 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]; StructureField field = buffer.Type.Fields[fieldIndex.Value];
int elemCount = (field.Type & AggregateType.ElementCountMask) switch int elemCount = (field.Type & AggregateType.ElementCountMask) switch

View file

@ -264,6 +264,9 @@ namespace Ryujinx.Graphics.Shader.Translation
HelperFunctionManager hfm = new(funcs, Definitions.Stage); HelperFunctionManager hfm = new(funcs, Definitions.Stage);
BindlessTextureFlags bindlessTextureFlags = BindlessTextureFlags.None;
bool bindlessTexturesAllowed = true;
for (int i = 0; i < functions.Length; i++) for (int i = 0; i < functions.Length; i++)
{ {
var cfg = cfgs[i]; var cfg = cfgs[i];
@ -294,9 +297,12 @@ namespace Ryujinx.Graphics.Shader.Translation
Definitions, Definitions,
resourceManager, resourceManager,
GpuAccessor, GpuAccessor,
Options.TargetApi,
Options.TargetLanguage, Options.TargetLanguage,
Definitions.Stage, Definitions.Stage,
ref usedFeatures); ref usedFeatures,
ref bindlessTextureFlags,
ref bindlessTexturesAllowed);
Optimizer.RunPass(context); Optimizer.RunPass(context);
TransformPasses.RunPass(context); TransformPasses.RunPass(context);
@ -312,6 +318,7 @@ namespace Ryujinx.Graphics.Shader.Translation
Definitions, Definitions,
resourceManager, resourceManager,
usedFeatures, usedFeatures,
bindlessTextureFlags,
clipDistancesWritten); clipDistancesWritten);
} }
@ -322,6 +329,7 @@ namespace Ryujinx.Graphics.Shader.Translation
ShaderDefinitions originalDefinitions, ShaderDefinitions originalDefinitions,
ResourceManager resourceManager, ResourceManager resourceManager,
FeatureFlags usedFeatures, FeatureFlags usedFeatures,
BindlessTextureFlags bindlessTextureFlags,
byte clipDistancesWritten) byte clipDistancesWritten)
{ {
var sInfo = StructuredProgram.MakeStructuredProgram( var sInfo = StructuredProgram.MakeStructuredProgram(
@ -345,6 +353,7 @@ namespace Ryujinx.Graphics.Shader.Translation
resourceManager.GetTextureDescriptors(), resourceManager.GetTextureDescriptors(),
resourceManager.GetImageDescriptors(), resourceManager.GetImageDescriptors(),
originalDefinitions.Stage, originalDefinitions.Stage,
bindlessTextureFlags,
geometryVerticesPerPrimitive, geometryVerticesPerPrimitive,
originalDefinitions.MaxOutputVertices, originalDefinitions.MaxOutputVertices,
originalDefinitions.ThreadsPerInputPrimitive, originalDefinitions.ThreadsPerInputPrimitive,
@ -365,7 +374,14 @@ namespace Ryujinx.Graphics.Shader.Translation
GpuAccessor.QueryHostSupportsTextureShadowLod(), GpuAccessor.QueryHostSupportsTextureShadowLod(),
GpuAccessor.QueryHostSupportsViewportMask()); 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 return Options.TargetLanguage switch
{ {
@ -474,7 +490,7 @@ namespace Ryujinx.Graphics.Shader.Translation
ioUsage = ioUsage.Combine(_vertexOutput); 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) public void SetVertexOutputMapForGeometryAsCompute(TranslatorContext vertexContext)
@ -569,6 +585,7 @@ namespace Ryujinx.Graphics.Shader.Translation
definitions, definitions,
resourceManager, resourceManager,
FeatureFlags.None, FeatureFlags.None,
BindlessTextureFlags.None,
0); 0);
} }
@ -665,6 +682,7 @@ namespace Ryujinx.Graphics.Shader.Translation
definitions, definitions,
resourceManager, resourceManager,
FeatureFlags.RtLayer, FeatureFlags.RtLayer,
BindlessTextureFlags.None,
0); 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) public unsafe void UpdateImages(int setIndex, int baseBinding, ReadOnlySpan<DescriptorImageInfo> imageInfo, DescriptorType type)
{ {
if (imageInfo.Length == 0) 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) if (texelBufferView.Handle != 0UL)
{ {
@ -161,6 +177,7 @@ namespace Ryujinx.Graphics.Vulkan
SType = StructureType.WriteDescriptorSet, SType = StructureType.WriteDescriptorSet,
DstSet = _descriptorSets[setIndex], DstSet = _descriptorSets[setIndex],
DstBinding = (uint)bindingIndex, DstBinding = (uint)bindingIndex,
DstArrayElement = (uint)elementIndex,
DescriptorType = type, DescriptorType = type,
DescriptorCount = 1, DescriptorCount = 1,
PTexelBufferView = &texelBufferView, PTexelBufferView = &texelBufferView,

View file

@ -61,6 +61,11 @@ namespace Ryujinx.Graphics.Vulkan
private bool _updateDescriptorCacheCbIndex; private bool _updateDescriptorCacheCbIndex;
private readonly BindlessManager _bindlessManager;
public uint BindlessTexturesCount => _bindlessManager.TexturesCount;
public uint BindlessSamplersCount => _bindlessManager.SamplersCount;
[Flags] [Flags]
private enum DirtyFlags private enum DirtyFlags
{ {
@ -69,7 +74,8 @@ namespace Ryujinx.Graphics.Vulkan
Storage = 1 << 1, Storage = 1 << 1,
Texture = 1 << 2, Texture = 1 << 2,
Image = 1 << 3, Image = 1 << 3,
All = Uniform | Storage | Texture | Image, Bindless = 1 << 4,
All = Uniform | Storage | Texture | Image | Bindless,
} }
private DirtyFlags _dirty; private DirtyFlags _dirty;
@ -110,6 +116,8 @@ namespace Ryujinx.Graphics.Vulkan
_textures.AsSpan().Fill(initialImageInfo); _textures.AsSpan().Fill(initialImageInfo);
_images.AsSpan().Fill(initialImageInfo); _images.AsSpan().Fill(initialImageInfo);
_bindlessManager = new BindlessManager();
if (gd.Capabilities.SupportsNullDescriptors) if (gd.Capabilities.SupportsNullDescriptors)
{ {
// If null descriptors are supported, we can pass null as the handle. // If null descriptors are supported, we can pass null as the handle.
@ -405,6 +413,20 @@ namespace Ryujinx.Graphics.Vulkan
SignalDirty(DirtyFlags.Uniform); 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) private void SignalDirty(DirtyFlags flag)
{ {
_dirty |= flag; _dirty |= flag;
@ -444,7 +466,21 @@ namespace Ryujinx.Graphics.Vulkan
UpdateAndBind(cbs, PipelineBase.ImageSetIndex, pbp); 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)] [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(); 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( private unsafe void UpdateBuffers(
@ -640,6 +679,10 @@ namespace Ryujinx.Graphics.Vulkan
return; return;
} }
(uint bindlessTexturesCount, uint bindlessSamplersCount) = GetBindlessCountsForProgram();
var pipelineLayout = _program.GetPipelineLayout(_gd, bindlessTexturesCount, bindlessSamplersCount);
fixed (DescriptorBufferInfo* pBufferInfo = bufferInfo) fixed (DescriptorBufferInfo* pBufferInfo = bufferInfo)
{ {
var writeDescriptorSet = new WriteDescriptorSet var writeDescriptorSet = new WriteDescriptorSet
@ -651,7 +694,7 @@ namespace Ryujinx.Graphics.Vulkan
PBufferInfo = pBufferInfo, 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) private void Initialize(CommandBufferScoped cbs, int setIndex, DescriptorSetCollection dsc)
{ {
// We don't support clearing texture descriptors currently. // We don't support clearing texture descriptors currently.
@ -736,6 +789,8 @@ namespace Ryujinx.Graphics.Vulkan
{ {
_dummyTexture.Dispose(); _dummyTexture.Dispose();
_dummySampler.Dispose(); _dummySampler.Dispose();
_bindlessManager.Dispose();
} }
} }

View file

@ -19,11 +19,17 @@ namespace Ryujinx.Graphics.Vulkan
class PipelineBase : IDisposable class PipelineBase : IDisposable
{ {
public const int DescriptorSetLayouts = 4; public const int DescriptorSetLayouts = 4;
public const int DescriptorSetLayoutsBindless = 9;
public const int UniformSetIndex = 0; public const int UniformSetIndex = 0;
public const int StorageSetIndex = 1; public const int StorageSetIndex = 1;
public const int TextureSetIndex = 2; public const int TextureSetIndex = 2;
public const int ImageSetIndex = 3; 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 VulkanRenderer Gd;
protected readonly Device Device; protected readonly Device Device;
@ -714,6 +720,42 @@ namespace Ryujinx.Graphics.Vulkan
_tfEnabled = false; _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) public bool IsCommandBufferActive(CommandBuffer cb)
{ {
return CommandBuffer.Handle == cb.Handle; return CommandBuffer.Handle == cb.Handle;
@ -967,11 +1009,24 @@ namespace Ryujinx.Graphics.Vulkan
_descriptorSetUpdater.SetProgram(internalProgram); _descriptorSetUpdater.SetProgram(internalProgram);
_newState.PipelineLayout = internalProgram.PipelineLayout;
_newState.StagesCount = (uint)stages.Length; _newState.StagesCount = (uint)stages.Length;
stages.CopyTo(_newState.Stages.AsSpan()[..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(); SignalStateChange();
if (internalProgram.IsCompute) if (internalProgram.IsCompute)

View file

@ -11,12 +11,12 @@ namespace Ryujinx.Graphics.Vulkan
private readonly struct PlceKey : IEquatable<PlceKey> private readonly struct PlceKey : IEquatable<PlceKey>
{ {
public readonly ReadOnlyCollection<ResourceDescriptorCollection> SetDescriptors; 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; SetDescriptors = setDescriptors;
UsePushDescriptors = usePushDescriptors; UsageInfo = usageInfo;
} }
public override int GetHashCode() public override int GetHashCode()
@ -31,7 +31,7 @@ namespace Ryujinx.Graphics.Vulkan
} }
} }
hasher.Add(UsePushDescriptors); hasher.Add(UsageInfo);
return hasher.ToHashCode(); 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() public PipelineLayoutCache()
{ {
_plces = new ConcurrentDictionary<PlceKey, PipelineLayoutCacheEntry>(); _plces = new();
} }
public PipelineLayoutCacheEntry GetOrCreate( public PipelineLayoutCacheEntry GetOrCreate(
VulkanRenderer gd, VulkanRenderer gd,
Device device, Device device,
ReadOnlyCollection<ResourceDescriptorCollection> setDescriptors, 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) 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 DefaultTexturePoolCapacity = 128 * DescriptorSetManager.MaxSets;
private const uint DefaultImagePoolCapacity = 8 * 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 VulkanRenderer _gd;
private readonly Device _device; private readonly Device _device;
@ -31,6 +31,9 @@ namespace Ryujinx.Graphics.Vulkan
private int _dsLastCbIndex; private int _dsLastCbIndex;
private int _dsLastSubmissionCount; private int _dsLastSubmissionCount;
private readonly uint _bindlessTexturesCount;
private readonly uint _bindlessSamplersCount;
private PipelineLayoutCacheEntry(VulkanRenderer gd, Device device, int setsCount) private PipelineLayoutCacheEntry(VulkanRenderer gd, Device device, int setsCount)
{ {
_gd = gd; _gd = gd;
@ -44,7 +47,7 @@ namespace Ryujinx.Graphics.Vulkan
for (int j = 0; j < _dsCache[i].Length; j++) 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, VulkanRenderer gd,
Device device, Device device,
ReadOnlyCollection<ResourceDescriptorCollection> setDescriptors, 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]; _consumedDescriptorsPerSet = new int[setDescriptors.Count];
@ -72,6 +75,9 @@ namespace Ryujinx.Graphics.Vulkan
_consumedDescriptorsPerSet[setIndex] = count; _consumedDescriptorsPerSet[setIndex] = count;
} }
_bindlessSamplersCount = usageInfo.BindlessSamplersCount;
_bindlessTexturesCount = usageInfo.BindlessTexturesCount;
} }
public void UpdateCommandBufferIndex(int commandBufferIndex) public void UpdateCommandBufferIndex(int commandBufferIndex)
@ -99,13 +105,18 @@ namespace Ryujinx.Graphics.Vulkan
int consumedDescriptors = _consumedDescriptorsPerSet[setIndex]; 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( var dsc = _gd.DescriptorSetManager.AllocateDescriptorSet(
_gd.Api, _gd.Api,
DescriptorSetLayouts[setIndex], DescriptorSetLayouts[setIndex],
poolSizes, poolSizes,
setIndex, setIndex,
consumedDescriptors, consumedDescriptors,
false); updateAfterBind);
list.Add(dsc); list.Add(dsc);
isNew = true; isNew = true;
@ -116,7 +127,7 @@ namespace Ryujinx.Graphics.Vulkan
return list[index]; 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; int count = 1;
@ -138,6 +149,24 @@ namespace Ryujinx.Graphics.Vulkan
output[1] = new(DescriptorType.StorageTexelBuffer, DefaultImagePoolCapacity); output[1] = new(DescriptorType.StorageTexelBuffer, DefaultImagePoolCapacity);
count = 2; count = 2;
break; 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]; return output[..count];

View file

@ -10,7 +10,7 @@ namespace Ryujinx.Graphics.Vulkan
VulkanRenderer gd, VulkanRenderer gd,
Device device, Device device,
ReadOnlyCollection<ResourceDescriptorCollection> setDescriptors, ReadOnlyCollection<ResourceDescriptorCollection> setDescriptors,
bool usePushDescriptors) PipelineLayoutUsageInfo usageInfo)
{ {
DescriptorSetLayout[] layouts = new DescriptorSetLayout[setDescriptors.Count]; DescriptorSetLayout[] layouts = new DescriptorSetLayout[setDescriptors.Count];
@ -30,6 +30,8 @@ namespace Ryujinx.Graphics.Vulkan
} }
} }
bool hasRuntimeArray = false;
DescriptorSetLayoutBinding[] layoutBindings = new DescriptorSetLayoutBinding[rdc.Descriptors.Count]; DescriptorSetLayoutBinding[] layoutBindings = new DescriptorSetLayoutBinding[rdc.Descriptors.Count];
for (int descIndex = 0; descIndex < rdc.Descriptors.Count; descIndex++) for (int descIndex = 0; descIndex < rdc.Descriptors.Count; descIndex++)
@ -45,11 +47,22 @@ namespace Ryujinx.Graphics.Vulkan
stages = activeStages; 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 layoutBindings[descIndex] = new DescriptorSetLayoutBinding
{ {
Binding = (uint)descriptor.Binding, Binding = (uint)descriptor.Binding,
DescriptorType = descriptor.Type.Convert(), DescriptorType = descriptor.Type.Convert(),
DescriptorCount = (uint)descriptor.Count, DescriptorCount = count,
StageFlags = stages.Convert(), StageFlags = stages.Convert(),
}; };
} }
@ -61,10 +74,40 @@ namespace Ryujinx.Graphics.Vulkan
SType = StructureType.DescriptorSetLayoutCreateInfo, SType = StructureType.DescriptorSetLayoutCreateInfo,
PBindings = pLayoutBindings, PBindings = pLayoutBindings,
BindingCount = (uint)layoutBindings.Length, BindingCount = (uint)layoutBindings.Length,
Flags = usePushDescriptors && setIndex == 0 ? DescriptorSetLayoutCreateFlags.PushDescriptorBitKhr : DescriptorSetLayoutCreateFlags.None, Flags = usageInfo.UsePushDescriptors && setIndex == 0 ? DescriptorSetLayoutCreateFlags.PushDescriptorBitKhr : DescriptorSetLayoutCreateFlags.None,
}; };
gd.Api.CreateDescriptorSetLayout(device, descriptorSetLayoutCreateInfo, null, out layouts[setIndex]).ThrowOnError(); if (hasRuntimeArray)
{
var bindingFlags = new DescriptorBindingFlags[rdc.Descriptors.Count];
for (int descIndex = 0; descIndex < rdc.Descriptors.Count; descIndex++)
{
if (rdc.Descriptors.Count == 0)
{
bindingFlags[descIndex] = DescriptorBindingFlags.UpdateAfterBindBit;
}
}
fixed (DescriptorBindingFlags* pBindingFlags = bindingFlags)
{
var descriptorSetLayoutFlagsCreateInfo = new DescriptorSetLayoutBindingFlagsCreateInfo()
{
SType = StructureType.DescriptorSetLayoutBindingFlagsCreateInfo,
PBindingFlags = pBindingFlags,
BindingCount = (uint)bindingFlags.Length,
};
descriptorSetLayoutCreateInfo.PNext = &descriptorSetLayoutFlagsCreateInfo;
descriptorSetLayoutCreateInfo.Flags |= DescriptorSetLayoutCreateFlags.UpdateAfterBindPoolBit;
gd.Api.CreateDescriptorSetLayout(device, descriptorSetLayoutCreateInfo, null, out layouts[setIndex]).ThrowOnError();
}
}
else
{
gd.Api.CreateDescriptorSetLayout(device, descriptorSetLayoutCreateInfo, null, out layouts[setIndex]).ThrowOnError();
}
} }
} }

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); 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<PipelineShaderStageCreateInfo> Stages;
public NativeArray<PipelineShaderStageRequiredSubgroupSizeCreateInfoEXT> StageRequiredSubgroupSizes; public NativeArray<PipelineShaderStageRequiredSubgroupSizeCreateInfoEXT> StageRequiredSubgroupSizes;
public PipelineLayout PipelineLayout;
public SpecData SpecializationData; public SpecData SpecializationData;
private Array32<VertexInputAttributeDescription> _vertexAttributeDescriptions2; private Array32<VertexInputAttributeDescription> _vertexAttributeDescriptions2;
@ -339,6 +350,7 @@ namespace Ryujinx.Graphics.Vulkan
LineWidth = 1f; LineWidth = 1f;
SamplesCount = 1; SamplesCount = 1;
DepthMode = true; DepthMode = true;
Internal.Id11 = 0; // Unused.
} }
public unsafe Auto<DisposablePipeline> CreateComputePipeline( public unsafe Auto<DisposablePipeline> CreateComputePipeline(
@ -357,7 +369,7 @@ namespace Ryujinx.Graphics.Vulkan
SType = StructureType.ComputePipelineCreateInfo, SType = StructureType.ComputePipelineCreateInfo,
Stage = Stages[0], Stage = Stages[0],
BasePipelineIndex = -1, BasePipelineIndex = -1,
Layout = PipelineLayout, Layout = program.GetPipelineLayout(gd, BindlessTexturesCount, BindlessSamplersCount),
}; };
Pipeline pipelineHandle = default; Pipeline pipelineHandle = default;
@ -625,7 +637,7 @@ namespace Ryujinx.Graphics.Vulkan
PDepthStencilState = &depthStencilState, PDepthStencilState = &depthStencilState,
PColorBlendState = &colorBlendState, PColorBlendState = &colorBlendState,
PDynamicState = &pipelineDynamicStateCreateInfo, PDynamicState = &pipelineDynamicStateCreateInfo,
Layout = PipelineLayout, Layout = program.GetPipelineLayout(gd, BindlessTexturesCount, BindlessSamplersCount),
RenderPass = renderPass, RenderPass = renderPass,
BasePipelineIndex = -1, BasePipelineIndex = -1,
}; };

View file

@ -21,6 +21,8 @@ namespace Ryujinx.Graphics.Vulkan
public ulong Id8; public ulong Id8;
public ulong Id9; public ulong Id9;
public ulong Id10;
public ulong Id11;
private readonly uint VertexAttributeDescriptionsCount => (byte)((Id6 >> 38) & 0xFF); private readonly uint VertexAttributeDescriptionsCount => (byte)((Id6 >> 38) & 0xFF);
private readonly uint VertexBindingDescriptionsCount => (byte)((Id6 >> 46) & 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)) || 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, 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; return false;
} }
@ -88,7 +90,8 @@ namespace Ryujinx.Graphics.Vulkan
Id6 * 23 ^ Id6 * 23 ^
Id7 * 23 ^ Id7 * 23 ^
Id8 * 23 ^ Id8 * 23 ^
Id9 * 23; Id9 * 23 ^
Id10 * 23;
for (int i = 0; i < (int)VertexAttributeDescriptionsCount; i++) for (int i = 0; i < (int)VertexAttributeDescriptionsCount; i++)
{ {

View file

@ -15,9 +15,9 @@ namespace Ryujinx.Graphics.Vulkan
private readonly Shader[] _shaders; private readonly Shader[] _shaders;
private readonly PipelineLayoutCacheEntry _plce; private readonly PipelineLayoutCacheEntry _plce;
private readonly ReadOnlyCollection<ResourceDescriptorCollection> _setDescriptors;
public PipelineLayout PipelineLayout => _plce.PipelineLayout; public bool HasBindless { get; }
public bool HasMinimalLayout { get; } public bool HasMinimalLayout { get; }
public bool UsePushDescriptors { get; } public bool UsePushDescriptors { get; }
public bool IsCompute { get; } public bool IsCompute { get; }
@ -62,6 +62,7 @@ namespace Ryujinx.Graphics.Vulkan
ShaderSource[] shaders, ShaderSource[] shaders,
ResourceLayout resourceLayout, ResourceLayout resourceLayout,
SpecDescription[] specDescription = null, SpecDescription[] specDescription = null,
bool hasBindless = false,
bool isMinimal = false) bool isMinimal = false)
{ {
_gd = gd; _gd = gd;
@ -109,8 +110,10 @@ namespace Ryujinx.Graphics.Vulkan
bool usePushDescriptors = !isMinimal && VulkanConfiguration.UsePushDescriptors && _gd.Capabilities.SupportsPushDescriptors; 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; HasMinimalLayout = isMinimal;
UsePushDescriptors = usePushDescriptors; UsePushDescriptors = usePushDescriptors;
@ -129,7 +132,8 @@ namespace Ryujinx.Graphics.Vulkan
ShaderSource[] sources, ShaderSource[] sources,
ResourceLayout resourceLayout, ResourceLayout resourceLayout,
ProgramPipelineState state, ProgramPipelineState state,
bool fromCache) : this(gd, device, sources, resourceLayout) bool hasBindless,
bool fromCache) : this(gd, device, sources, resourceLayout, null, hasBindless)
{ {
_state = state; _state = state;
@ -325,7 +329,12 @@ namespace Ryujinx.Graphics.Vulkan
pipeline.Stages[0] = _shaders[0].GetInfo(); pipeline.Stages[0] = _shaders[0].GetInfo();
pipeline.StagesCount = 1; 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.CreateComputePipeline(_gd, _device, this, (_gd.Pipeline as PipelineBase).PipelineCache);
pipeline.Dispose(); pipeline.Dispose();
@ -353,7 +362,12 @@ namespace Ryujinx.Graphics.Vulkan
} }
pipeline.StagesCount = (uint)_shaders.Length; 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.CreateGraphicsPipeline(_gd, _device, this, (_gd.Pipeline as PipelineBase).PipelineCache, renderPass.Value);
pipeline.Dispose(); pipeline.Dispose();
@ -474,6 +488,26 @@ namespace Ryujinx.Graphics.Vulkan
return _plce.GetNewDescriptorSetCollection(setIndex, out isNew); 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) protected virtual void Dispose(bool disposing)
{ {
if (disposing) if (disposing)

View file

@ -23,6 +23,7 @@ namespace Ryujinx.Graphics.Vulkan
public int Width { get; } public int Width { get; }
public int Height { get; } public int Height { get; }
public GAL.Format Format { get; }
public VkFormat VkFormat { get; } public VkFormat VkFormat { get; }
public TextureBuffer(VulkanRenderer gd, TextureCreateInfo info) public TextureBuffer(VulkanRenderer gd, TextureCreateInfo info)
@ -30,6 +31,7 @@ namespace Ryujinx.Graphics.Vulkan
_gd = gd; _gd = gd;
Width = info.Width; Width = info.Width;
Height = info.Height; Height = info.Height;
Format = info.Format;
VkFormat = FormatTable.GetFormat(info.Format); VkFormat = FormatTable.GetFormat(info.Format);
gd.Textures.Add(this); gd.Textures.Add(this);

View file

@ -299,6 +299,14 @@ namespace Ryujinx.Graphics.Vulkan
features2.PNext = &supportedFeaturesVk11; features2.PNext = &supportedFeaturesVk11;
PhysicalDeviceVulkan12Features supportedFeaturesVk12 = new()
{
SType = StructureType.PhysicalDeviceVulkan12Features,
PNext = features2.PNext
};
features2.PNext = &supportedFeaturesVk12;
PhysicalDeviceCustomBorderColorFeaturesEXT supportedFeaturesCustomBorderColor = new() PhysicalDeviceCustomBorderColorFeaturesEXT supportedFeaturesCustomBorderColor = new()
{ {
SType = StructureType.PhysicalDeviceCustomBorderColorFeaturesExt, SType = StructureType.PhysicalDeviceCustomBorderColorFeaturesExt,
@ -451,8 +459,14 @@ namespace Ryujinx.Graphics.Vulkan
{ {
SType = StructureType.PhysicalDeviceVulkan12Features, SType = StructureType.PhysicalDeviceVulkan12Features,
PNext = pExtendedFeatures, PNext = pExtendedFeatures,
DescriptorBindingSampledImageUpdateAfterBind = supportedFeaturesVk12.DescriptorBindingSampledImageUpdateAfterBind,
DescriptorBindingStorageImageUpdateAfterBind = supportedFeaturesVk12.DescriptorBindingStorageImageUpdateAfterBind,
DescriptorBindingStorageTexelBufferUpdateAfterBind = supportedFeaturesVk12.DescriptorBindingStorageTexelBufferUpdateAfterBind,
DescriptorBindingUniformTexelBufferUpdateAfterBind = supportedFeaturesVk12.DescriptorBindingUniformTexelBufferUpdateAfterBind,
DescriptorIndexing = physicalDevice.IsDeviceExtensionPresent("VK_EXT_descriptor_indexing"), DescriptorIndexing = physicalDevice.IsDeviceExtensionPresent("VK_EXT_descriptor_indexing"),
DrawIndirectCount = physicalDevice.IsDeviceExtensionPresent(KhrDrawIndirectCount.ExtensionName), DrawIndirectCount = physicalDevice.IsDeviceExtensionPresent(KhrDrawIndirectCount.ExtensionName),
RuntimeDescriptorArray = supportedFeaturesVk12.RuntimeDescriptorArray,
SamplerMirrorClampToEdge = supportedFeaturesVk12.SamplerMirrorClampToEdge,
UniformBufferStandardLayout = physicalDevice.IsDeviceExtensionPresent("VK_KHR_uniform_buffer_standard_layout"), 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); CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex);
DescriptorSetManager = new DescriptorSetManager(_device, PipelineBase.DescriptorSetLayouts); DescriptorSetManager = new DescriptorSetManager(_device, PipelineBase.DescriptorSetLayoutsBindless);
PipelineLayoutCache = new PipelineLayoutCache(); PipelineLayoutCache = new PipelineLayoutCache();
@ -418,7 +418,7 @@ namespace Ryujinx.Graphics.Vulkan
if (info.State.HasValue || isCompute) 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); return new ShaderCollection(this, _device, sources, info.ResourceLayout);