mirror of
https://git.naxdy.org/Mirror/Ryujinx.git
synced 2025-01-12 21:59:12 +00:00
Initial bindless texture support
This commit is contained in:
parent
82a638230e
commit
29e9e90fe8
88 changed files with 3522 additions and 655 deletions
|
@ -42,6 +42,10 @@ namespace Ryujinx.Graphics.GAL
|
|||
|
||||
void EndTransformFeedback();
|
||||
|
||||
void RegisterBindlessSampler(int samplerId, ISampler sampler);
|
||||
void RegisterBindlessTexture(int textureId, ITexture texture, float textureScale);
|
||||
void RegisterBindlessTextureAndSampler(int textureId, ITexture texture, float textureScale, int samplerId, ISampler sampler);
|
||||
|
||||
void SetAlphaTest(bool enable, float reference, CompareOp op);
|
||||
|
||||
void SetBlendState(AdvancedBlendDescriptor blend);
|
||||
|
|
|
@ -100,6 +100,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
Register<DrawTextureCommand>(CommandType.DrawTexture);
|
||||
Register<EndHostConditionalRenderingCommand>(CommandType.EndHostConditionalRendering);
|
||||
Register<EndTransformFeedbackCommand>(CommandType.EndTransformFeedback);
|
||||
Register<RegisterBindlessSamplerCommand>(CommandType.RegisterBindlessSampler);
|
||||
Register<RegisterBindlessTextureCommand>(CommandType.RegisterBindlessTexture);
|
||||
Register<RegisterBindlessTextureAndSamplerCommand>(CommandType.RegisterBindlessTextureAndSampler);
|
||||
Register<SetAlphaTestCommand>(CommandType.SetAlphaTest);
|
||||
Register<SetBlendStateAdvancedCommand>(CommandType.SetBlendStateAdvanced);
|
||||
Register<SetBlendStateCommand>(CommandType.SetBlendState);
|
||||
|
|
|
@ -62,6 +62,9 @@
|
|||
DrawTexture,
|
||||
EndHostConditionalRendering,
|
||||
EndTransformFeedback,
|
||||
RegisterBindlessSampler,
|
||||
RegisterBindlessTexture,
|
||||
RegisterBindlessTextureAndSampler,
|
||||
SetAlphaTest,
|
||||
SetBlendStateAdvanced,
|
||||
SetBlendState,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -123,6 +123,24 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public void RegisterBindlessSampler(int samplerId, ISampler sampler)
|
||||
{
|
||||
_renderer.New<RegisterBindlessSamplerCommand>().Set(samplerId, Ref(sampler));
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public void RegisterBindlessTexture(int textureId, ITexture texture, float textureScale)
|
||||
{
|
||||
_renderer.New<RegisterBindlessTextureCommand>().Set(textureId, Ref(texture), textureScale);
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public void RegisterBindlessTextureAndSampler(int textureId, ITexture texture, float textureScale, int samplerId, ISampler sampler)
|
||||
{
|
||||
_renderer.New<RegisterBindlessTextureAndSamplerCommand>().Set(textureId, Ref(texture), textureScale, samplerId, Ref(sampler));
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public void SetAlphaTest(bool enable, float reference, CompareOp op)
|
||||
{
|
||||
_renderer.New<SetAlphaTestCommand>().Set(enable, reference, op);
|
||||
|
|
|
@ -3,21 +3,24 @@ namespace Ryujinx.Graphics.GAL
|
|||
public struct ShaderInfo
|
||||
{
|
||||
public int FragmentOutputMap { get; }
|
||||
public bool HasBindless { get; }
|
||||
public ResourceLayout ResourceLayout { get; }
|
||||
public ProgramPipelineState? State { get; }
|
||||
public bool FromCache { get; set; }
|
||||
|
||||
public ShaderInfo(int fragmentOutputMap, ResourceLayout resourceLayout, ProgramPipelineState state, bool fromCache = false)
|
||||
public ShaderInfo(int fragmentOutputMap, bool hasBindless, ResourceLayout resourceLayout, ProgramPipelineState state, bool fromCache = false)
|
||||
{
|
||||
FragmentOutputMap = fragmentOutputMap;
|
||||
HasBindless = hasBindless;
|
||||
ResourceLayout = resourceLayout;
|
||||
State = state;
|
||||
FromCache = fromCache;
|
||||
}
|
||||
|
||||
public ShaderInfo(int fragmentOutputMap, ResourceLayout resourceLayout, bool fromCache = false)
|
||||
public ShaderInfo(int fragmentOutputMap, bool hasBindless, ResourceLayout resourceLayout, bool fromCache = false)
|
||||
{
|
||||
FragmentOutputMap = fragmentOutputMap;
|
||||
HasBindless = hasBindless;
|
||||
ResourceLayout = resourceLayout;
|
||||
State = null;
|
||||
FromCache = fromCache;
|
||||
|
|
|
@ -89,5 +89,30 @@ namespace Ryujinx.Graphics.Gpu
|
|||
/// Maximum size that an storage buffer is assumed to have when the correct size is unknown.
|
||||
/// </summary>
|
||||
public const ulong MaxUnknownStorageSize = 0x100000;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum width and height for 1D, 2D and cube textures, including array and multisample variants.
|
||||
/// </summary>
|
||||
public const int MaxTextureSize = 0x4000;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum width, height and depth for 3D textures.
|
||||
/// </summary>
|
||||
public const int Max3DTextureSize = 0x800;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum layers for array textures.
|
||||
/// </summary>
|
||||
public const int MaxArrayTextureLayers = 0x800;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum width (effectively the size in pixels) for buffer textures.
|
||||
/// </summary>
|
||||
public const int MaxBufferTextureSize = 0x8000000;
|
||||
|
||||
/// <summary>
|
||||
/// Alignment in bytes for pitch linear textures.
|
||||
/// </summary>
|
||||
public const int LinearStrideAlignment = 0x20;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -195,7 +195,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
|||
|
||||
// Should never return false for mismatching spec state, since the shader was fetched above.
|
||||
_channel.TextureManager.CommitComputeBindings(cs.SpecializationState);
|
||||
|
||||
_channel.BufferManager.CommitComputeBindings();
|
||||
|
||||
_context.Renderer.Pipeline.DispatchCompute(qmd.CtaRasterWidth, qmd.CtaRasterHeight, qmd.CtaRasterDepth);
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
|||
/// <returns>Texture target value</returns>
|
||||
public static Target GetTarget(SamplerType type)
|
||||
{
|
||||
type &= ~(SamplerType.Indexed | SamplerType.Shadow);
|
||||
type &= ~SamplerType.Shadow;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
|
|
197
src/Ryujinx.Graphics.Gpu/Image/BitMap.cs
Normal file
197
src/Ryujinx.Graphics.Gpu/Image/BitMap.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,6 +22,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
protected T1[] Items;
|
||||
protected T2[] DescriptorCache;
|
||||
|
||||
protected readonly BitMap ModifiedEntries;
|
||||
|
||||
private int _minimumAccessedId;
|
||||
private int _maximumAccessedId;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum ID value of resources on the pool (inclusive).
|
||||
/// </summary>
|
||||
|
@ -61,6 +66,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
int count = maximumId + 1;
|
||||
|
||||
ModifiedEntries = new BitMap(count);
|
||||
|
||||
_minimumAccessedId = int.MaxValue;
|
||||
_maximumAccessedId = 0;
|
||||
|
||||
ulong size = (ulong)(uint)count * DescriptorSize;
|
||||
|
||||
Items = new T1[count];
|
||||
|
@ -197,6 +207,41 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the set of entries that have been modified.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the region of the pool that has been modfied</param>
|
||||
/// <param name="endAddress">End address of the region of the pool that has been modified, exclusive</param>
|
||||
protected void UpdateModifiedEntries(ulong address, ulong endAddress)
|
||||
{
|
||||
int startId = (int)((address - Address) / DescriptorSize);
|
||||
int endId = (int)((endAddress - Address + (DescriptorSize - 1)) / DescriptorSize) - 1;
|
||||
|
||||
if (endId < startId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ModifiedEntries.SetRange(startId, endId);
|
||||
|
||||
_minimumAccessedId = Math.Min(_minimumAccessedId, startId);
|
||||
_maximumAccessedId = Math.Max(_maximumAccessedId, endId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces all entries as modified, to be updated if any shader uses bindless textures.
|
||||
/// </summary>
|
||||
public void ForceModifiedEntries()
|
||||
{
|
||||
for (int id = _minimumAccessedId; id <= _maximumAccessedId; id++)
|
||||
{
|
||||
if (Items[id] != null)
|
||||
{
|
||||
ModifiedEntries.Set(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void InvalidateRangeImpl(ulong address, ulong size);
|
||||
|
||||
protected abstract void Delete(T1 item);
|
||||
|
|
|
@ -113,6 +113,24 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
return (CompareOp)(((Word0 >> 10) & 7) + 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the font filter width.
|
||||
/// </summary>
|
||||
/// <returns>Font filter width</returns>
|
||||
public int UnpackFontFilterWidth()
|
||||
{
|
||||
return (int)(Word0 >> 14) & 7;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the font filter height.
|
||||
/// </summary>
|
||||
/// <returns>Font filter height</returns>
|
||||
public int UnpackFontFilterHeight()
|
||||
{
|
||||
return (int)(Word0 >> 17) & 7;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks and converts the maximum anisotropy value used for texture anisotropic filtering.
|
||||
/// </summary>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
@ -117,6 +118,53 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
return ModifiedSequenceNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads all the samplers currently registered by the guest application on the pool.
|
||||
/// This is required for bindless access, as it's not possible to predict which sampler will be used.
|
||||
/// </summary>
|
||||
public void LoadAll()
|
||||
{
|
||||
if (SequenceNumber != Context.SequenceNumber)
|
||||
{
|
||||
SequenceNumber = Context.SequenceNumber;
|
||||
|
||||
SynchronizeMemory();
|
||||
}
|
||||
|
||||
ModifiedEntries.BeginIterating();
|
||||
|
||||
int id;
|
||||
|
||||
while ((id = ModifiedEntries.GetNextAndClear()) >= 0)
|
||||
{
|
||||
Sampler sampler = Items[id] ?? GetValidated(id);
|
||||
|
||||
if (sampler != null)
|
||||
{
|
||||
Context.Renderer.Pipeline.RegisterBindlessSampler(id, sampler.GetHostSampler(null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sampler at the given <paramref name="id"/> from the cache,
|
||||
/// or creates a new one if not found.
|
||||
/// This will return null if the sampler entry is considered invalid.
|
||||
/// </summary>
|
||||
/// <param name="id">Index of the sampler on the pool</param>
|
||||
/// <returns>Sampler for the given pool index</returns>
|
||||
private Sampler GetValidated(int id)
|
||||
{
|
||||
SamplerDescriptor descriptor = GetDescriptor(id);
|
||||
|
||||
if (descriptor.UnpackFontFilterWidth() != 1 || descriptor.UnpackFontFilterHeight() != 1 || (descriptor.Word0 >> 23) != 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Get(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of the sampler pool range invalidation.
|
||||
/// </summary>
|
||||
|
@ -126,6 +174,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
{
|
||||
ulong endAddress = address + size;
|
||||
|
||||
UpdateModifiedEntries(address, endAddress);
|
||||
|
||||
for (; address < endAddress; address += DescriptorSize)
|
||||
{
|
||||
int id = (int)((address - Address) / DescriptorSize);
|
||||
|
|
|
@ -1462,6 +1462,19 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
DisposeTextures();
|
||||
|
||||
HostTexture = hostTexture;
|
||||
|
||||
ForceTexturePoolUpdate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces the entries on all texture pool where this texture is present to be updated.
|
||||
/// </summary>
|
||||
private void ForceTexturePoolUpdate()
|
||||
{
|
||||
foreach (TexturePoolOwner poolOwner in _poolOwners)
|
||||
{
|
||||
poolOwner.Pool.ForceModifiedEntry(poolOwner.ID);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -5,6 +5,7 @@ using Ryujinx.Graphics.Gpu.Memory;
|
|||
using Ryujinx.Graphics.Gpu.Shader;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
@ -59,6 +60,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
private int _texturePoolSequence;
|
||||
private int _samplerPoolSequence;
|
||||
|
||||
private BindlessTextureFlags[] _bindlessTextureFlags;
|
||||
|
||||
private int _textureBufferIndex;
|
||||
|
||||
private int _lastFragmentTotal;
|
||||
|
@ -93,6 +96,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
_textureState = new TextureState[InitialTextureStateSize];
|
||||
_imageState = new TextureState[InitialImageStateSize];
|
||||
|
||||
_bindlessTextureFlags = new BindlessTextureFlags[stages];
|
||||
|
||||
for (int stage = 0; stage < stages; stage++)
|
||||
{
|
||||
_textureBindings[stage] = new TextureBindingInfo[InitialTextureStateSize];
|
||||
|
@ -109,6 +114,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
_textureBindings = bindings.TextureBindings;
|
||||
_imageBindings = bindings.ImageBindings;
|
||||
|
||||
_bindlessTextureFlags = bindings.BindlessTextureFlags;
|
||||
|
||||
SetMaxBindings(bindings.MaxTextureBinding, bindings.MaxImageBinding);
|
||||
}
|
||||
|
||||
|
@ -305,6 +312,23 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
// If it wasn't, then it's possible to avoid looking up textures again when the handle remains the same.
|
||||
if (_cachedTexturePool != texturePool || _cachedSamplerPool != samplerPool)
|
||||
{
|
||||
bool anyFullBindless = false;
|
||||
|
||||
for (int index = 0; index < (_isCompute ? 1 : _bindlessTextureFlags.Length); index++)
|
||||
{
|
||||
if (_bindlessTextureFlags[index].HasFlag(BindlessTextureFlags.BindlessFull))
|
||||
{
|
||||
anyFullBindless = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (anyFullBindless)
|
||||
{
|
||||
texturePool?.ForceModifiedEntries();
|
||||
samplerPool?.ForceModifiedEntries();
|
||||
}
|
||||
|
||||
Rebind();
|
||||
|
||||
_cachedTexturePool = texturePool;
|
||||
|
@ -341,6 +365,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
{
|
||||
specStateMatches &= CommitTextureBindings(texturePool, samplerPool, ShaderStage.Compute, 0, poolModified, specState);
|
||||
specStateMatches &= CommitImageBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState);
|
||||
|
||||
if (_bindlessTextureFlags[0].HasFlag(BindlessTextureFlags.BindlessNvn))
|
||||
{
|
||||
CommitBindlessResources(texturePool, ShaderStage.Compute, 0);
|
||||
}
|
||||
else if (_bindlessTextureFlags[0].HasFlag(BindlessTextureFlags.BindlessFull))
|
||||
{
|
||||
texturePool.LoadAll(_context.Renderer, _samplerPool);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -350,6 +383,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
specStateMatches &= CommitTextureBindings(texturePool, samplerPool, stage, stageIndex, poolModified, specState);
|
||||
specStateMatches &= CommitImageBindings(texturePool, stage, stageIndex, poolModified, specState);
|
||||
|
||||
if (_bindlessTextureFlags[stageIndex].HasFlag(BindlessTextureFlags.BindlessNvn))
|
||||
{
|
||||
CommitBindlessResources(texturePool, stage, stageIndex);
|
||||
}
|
||||
else if (_bindlessTextureFlags[stageIndex].HasFlag(BindlessTextureFlags.BindlessFull))
|
||||
{
|
||||
texturePool.LoadAll(_context.Renderer, _samplerPool);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -460,8 +502,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
ReadOnlySpan<int> cachedTextureBuffer = Span<int>.Empty;
|
||||
ReadOnlySpan<int> cachedSamplerBuffer = Span<int>.Empty;
|
||||
|
||||
int maxTexturesPerStage = TextureHandle.GetMaxTexturesPerStage(_context.Capabilities.Api);
|
||||
|
||||
for (int index = 0; index < textureCount; index++)
|
||||
{
|
||||
bool asBindless = index >= maxTexturesPerStage;
|
||||
|
||||
TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index];
|
||||
TextureUsageFlags usageFlags = bindingInfo.Flags;
|
||||
|
||||
|
@ -524,7 +570,17 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
// Ensure that the buffer texture is using the correct buffer as storage.
|
||||
// Buffers are frequently re-created to accommodate larger data, so we need to re-bind
|
||||
// to ensure we're not using a old buffer that was already deleted.
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, bindingInfo.Format, false);
|
||||
ulong address = texture.Range.GetSubRange(0).Address;
|
||||
ulong size = texture.Size;
|
||||
|
||||
if (asBindless)
|
||||
{
|
||||
_channel.BufferManager.SetBufferTextureStorage(hostTexture, address, size, bindingInfo, bindingInfo.Format, false, textureId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, address, size, bindingInfo, bindingInfo.Format, false);
|
||||
}
|
||||
|
||||
// Cache is not used for buffer texture, it must always rebind.
|
||||
state.CachedTexture = null;
|
||||
|
@ -545,8 +601,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
state.Texture = hostTexture;
|
||||
state.Sampler = hostSampler;
|
||||
|
||||
if (asBindless)
|
||||
{
|
||||
_context.Renderer.Pipeline.RegisterBindlessTextureAndSampler(textureId, hostTexture, texture?.ScaleFactor ?? 1f, samplerId, hostSampler);
|
||||
}
|
||||
else
|
||||
{
|
||||
_context.Renderer.Pipeline.SetTextureAndSampler(stage, bindingInfo.Binding, hostTexture, hostSampler);
|
||||
}
|
||||
}
|
||||
|
||||
state.CachedTexture = texture;
|
||||
state.CachedSampler = sampler;
|
||||
|
@ -703,6 +766,93 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
return specStateMatches;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the texture bindings are visible to the host GPU.
|
||||
/// Note: this actually performs the binding using the host graphics API.
|
||||
/// </summary>
|
||||
/// <param name="pool">The current texture pool</param>
|
||||
/// <param name="stage">The shader stage using the textures to be bound</param>
|
||||
/// <param name="stageIndex">The stage number of the specified shader stage</param>
|
||||
private void CommitBindlessResources(TexturePool pool, ShaderStage stage, int stageIndex)
|
||||
{
|
||||
var samplerPool = _samplerPool;
|
||||
|
||||
if (pool == null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Gpu, $"Shader stage \"{stage}\" uses bindless textures, but texture pool was not set.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int index = 0; index < 32; index++)
|
||||
{
|
||||
int wordOffset = 8 + index * 2;
|
||||
|
||||
int packedId = ReadConstantBuffer<int>(stageIndex, _textureBufferIndex, wordOffset);
|
||||
|
||||
int textureId = TextureHandle.UnpackTextureId(packedId);
|
||||
int samplerId;
|
||||
|
||||
if (_samplerIndex == SamplerIndex.ViaHeaderIndex)
|
||||
{
|
||||
samplerId = textureId;
|
||||
}
|
||||
else
|
||||
{
|
||||
samplerId = TextureHandle.UnpackSamplerId(packedId);
|
||||
}
|
||||
|
||||
Texture texture = pool.Get(textureId);
|
||||
|
||||
if (texture == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (texture.Target == Target.TextureBuffer)
|
||||
{
|
||||
// Ensure that the buffer texture is using the correct buffer as storage.
|
||||
// Buffers are frequently re-created to accomodate larger data, so we need to re-bind
|
||||
// to ensure we're not using a old buffer that was already deleted.
|
||||
TextureBindingInfo bindingInfo = new TextureBindingInfo(texture.Target, texture.Format, 0, 0, 0, TextureUsageFlags.None);
|
||||
ulong address = texture.Range.GetSubRange(0).Address;
|
||||
ulong size = texture.Size;
|
||||
_channel.BufferManager.SetBufferTextureStorage(texture.HostTexture, address, size, bindingInfo, texture.Format, false, textureId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Sampler sampler = samplerPool?.Get(samplerId);
|
||||
|
||||
if (sampler == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.RegisterBindlessTextureAndSampler(
|
||||
textureId,
|
||||
texture.HostTexture,
|
||||
texture.ScaleFactor,
|
||||
samplerId,
|
||||
sampler.GetHostSampler(texture));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a value from a constant buffer.
|
||||
/// </summary>
|
||||
/// <param name="stageIndex">Index of the shader stage where the constant buffer belongs</param>
|
||||
/// <param name="bufferIndex">Index of the constant buffer to read from</param>
|
||||
/// <param name="elementIndex">Index of the element on the constant buffer</param>
|
||||
/// <returns>The value at the specified buffer and offset</returns>
|
||||
private unsafe T ReadConstantBuffer<T>(int stageIndex, int bufferIndex, int elementIndex) where T : unmanaged
|
||||
{
|
||||
ulong baseAddress = _isCompute
|
||||
? _channel.BufferManager.GetComputeUniformBufferAddress(bufferIndex)
|
||||
: _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, bufferIndex);
|
||||
|
||||
return _channel.MemoryManager.Physical.Read<T>(baseAddress + (ulong)elementIndex * (ulong)sizeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the texture descriptor for a given texture handle.
|
||||
/// </summary>
|
||||
|
@ -749,14 +899,13 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a packed texture and sampler ID (basically, the real texture handle)
|
||||
/// from the texture constant buffer.
|
||||
/// Reads a combined texture and sampler handle from the texture constant buffer.
|
||||
/// </summary>
|
||||
/// <param name="stageIndex">The number of the shader stage where the texture is bound</param>
|
||||
/// <param name="wordOffset">A word offset of the handle on the buffer (the "fake" shader handle)</param>
|
||||
/// <param name="wordOffset">The word offset of the handle on the buffer</param>
|
||||
/// <param name="textureBufferIndex">Index of the constant buffer holding the texture handles</param>
|
||||
/// <param name="samplerBufferIndex">Index of the constant buffer holding the sampler handles</param>
|
||||
/// <returns>The packed texture and sampler ID (the real texture handle)</returns>
|
||||
/// <returns>The combined texture and sampler handle</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private int ReadPackedId(int stageIndex, int wordOffset, int textureBufferIndex, int samplerBufferIndex)
|
||||
{
|
||||
|
|
|
@ -2,6 +2,7 @@ using Ryujinx.Common.Logging;
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
using Ryujinx.Memory;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
|
@ -212,6 +213,94 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
return ModifiedSequenceNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads all the textures currently registered by the guest application on the pool.
|
||||
/// This is required for bindless access, as it's not possible to predict which textures will be used.
|
||||
/// </summary>
|
||||
/// <param name="renderer">Renderer of the current GPU context</param>
|
||||
/// <param name="activeSamplerPool">The currently active sampler pool</param>
|
||||
public void LoadAll(IRenderer renderer, SamplerPool activeSamplerPool)
|
||||
{
|
||||
activeSamplerPool?.LoadAll();
|
||||
|
||||
if (SequenceNumber != Context.SequenceNumber)
|
||||
{
|
||||
SequenceNumber = Context.SequenceNumber;
|
||||
|
||||
SynchronizeMemory();
|
||||
}
|
||||
|
||||
ModifiedEntries.BeginIterating();
|
||||
|
||||
int id;
|
||||
|
||||
while ((id = ModifiedEntries.GetNextAndClear()) >= 0)
|
||||
{
|
||||
Texture texture = Items[id] ?? GetValidated(id);
|
||||
|
||||
if (texture != null)
|
||||
{
|
||||
if (texture.Target == Target.TextureBuffer)
|
||||
{
|
||||
_channel.BufferManager.SetBufferTextureStorage(
|
||||
texture.HostTexture,
|
||||
texture.Range.GetSubRange(0).Address,
|
||||
texture.Size,
|
||||
default,
|
||||
0,
|
||||
false,
|
||||
id);
|
||||
}
|
||||
else
|
||||
{
|
||||
renderer.Pipeline.RegisterBindlessTexture(id, texture.HostTexture, texture.ScaleFactor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the texture at the given <paramref name="id"/> from the cache,
|
||||
/// or creates a new one if not found.
|
||||
/// This will return null if the texture entry is considered invalid.
|
||||
/// </summary>
|
||||
/// <param name="id">Index of the texture on the pool</param>
|
||||
/// <returns>Texture for the given pool index</returns>
|
||||
private Texture GetValidated(int id)
|
||||
{
|
||||
TextureDescriptor descriptor = GetDescriptor(id);
|
||||
|
||||
if (!FormatTable.TryGetTextureFormat(descriptor.UnpackFormat(), descriptor.UnpackSrgb(), out _))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
TextureInfo info = GetInfo(descriptor, out int layerSize);
|
||||
TextureValidationResult validationResult = TextureValidation.Validate(ref info);
|
||||
|
||||
if (validationResult != TextureValidationResult.Valid)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: Eventually get rid of that...
|
||||
// For now it avoids creating textures for garbage entries in some cases, but it is not
|
||||
// correct as a width or height of 8192 is valid (although extremely unlikely).
|
||||
if (info.Width > 8192 || info.Height > 8192 || info.DepthOrLayers > 8192)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Get(id);
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forcibly remove a texture from this pool's items.
|
||||
/// If deferred, the dereference will be queued to occur on the render thread.
|
||||
|
@ -342,6 +431,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
ulong endAddress = address + size;
|
||||
|
||||
UpdateModifiedEntries(address, endAddress);
|
||||
|
||||
for (; address < endAddress; address += DescriptorSize)
|
||||
{
|
||||
int id = (int)((address - Address) / DescriptorSize);
|
||||
|
@ -373,6 +464,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces a entry as modified, to be updated if any shader uses bindless textures.
|
||||
/// </summary>
|
||||
/// <param name="id">ID of the entry to be updated</param>
|
||||
public void ForceModifiedEntry(int id)
|
||||
{
|
||||
ModifiedEntries.Set(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets texture information from a texture descriptor.
|
||||
/// </summary>
|
||||
|
|
107
src/Ryujinx.Graphics.Gpu/Image/TextureValidation.cs
Normal file
107
src/Ryujinx.Graphics.Gpu/Image/TextureValidation.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -482,7 +482,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
|
||||
// The texture must be rebound to use the new storage if it was updated.
|
||||
|
||||
if (binding.IsImage)
|
||||
if (binding.AsBindless)
|
||||
{
|
||||
_context.Renderer.Pipeline.RegisterBindlessTexture(binding.TextureId, binding.Texture, 1f);
|
||||
}
|
||||
else if (binding.IsImage)
|
||||
{
|
||||
_context.Renderer.Pipeline.SetImage(binding.BindingInfo.Binding, binding.Texture, binding.Format);
|
||||
}
|
||||
|
@ -812,6 +816,30 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
_bufferTextures.Add(new BufferTextureBinding(stage, texture, address, size, bindingInfo, format, isImage));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the buffer storage of a bindless buffer texture. This will be bound when the buffer manager commits bindings.
|
||||
/// </summary>
|
||||
/// <param name="texture">Buffer texture</param>
|
||||
/// <param name="address">Address of the buffer in memory</param>
|
||||
/// <param name="size">Size of the buffer in bytes</param>
|
||||
/// <param name="bindingInfo">Binding info for the buffer texture</param>
|
||||
/// <param name="format">Format of the buffer texture</param>
|
||||
/// <param name="isImage">Whether the binding is for an image or a sampler</param>
|
||||
/// <param name="textureid">ID of the texture on the pool/param>
|
||||
public void SetBufferTextureStorage(
|
||||
ITexture texture,
|
||||
ulong address,
|
||||
ulong size,
|
||||
TextureBindingInfo bindingInfo,
|
||||
Format format,
|
||||
bool isImage,
|
||||
int textureId)
|
||||
{
|
||||
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(address, size);
|
||||
|
||||
_bufferTextures.Add(new BufferTextureBinding(texture, address, size, bindingInfo, format, isImage, textureId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force all bound textures and images to be rebound the next time CommitBindings is called.
|
||||
/// </summary>
|
||||
|
|
|
@ -44,6 +44,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
/// </summary>
|
||||
public bool IsImage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the texture should be bound as a bindless texture.
|
||||
/// </summary>
|
||||
public bool AsBindless { get; }
|
||||
|
||||
/// <summary>
|
||||
/// For bindless textures, indicates the texture ID.
|
||||
/// </summary>
|
||||
public int TextureId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new buffer texture binding.
|
||||
/// </summary>
|
||||
|
@ -70,6 +80,31 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
BindingInfo = bindingInfo;
|
||||
Format = format;
|
||||
IsImage = isImage;
|
||||
AsBindless = false;
|
||||
TextureId = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new bindless buffer texture binding.
|
||||
/// </summary>
|
||||
/// <param name="texture">Buffer texture</param>
|
||||
/// <param name="address">Base address</param>
|
||||
/// <param name="size">Size in bytes</param>
|
||||
/// <param name="bindingInfo">Binding info</param>
|
||||
/// <param name="format">Binding format</param>
|
||||
/// <param name="isImage">Whether the binding is for an image or a sampler</param>
|
||||
/// <param name="textureId">ID of the texture on the pool</param>
|
||||
public BufferTextureBinding(ITexture texture, ulong address, ulong size, TextureBindingInfo bindingInfo, Format format, bool isImage, int textureId)
|
||||
{
|
||||
Stage = default;
|
||||
Texture = texture;
|
||||
Address = address;
|
||||
Size = size;
|
||||
BindingInfo = bindingInfo;
|
||||
Format = format;
|
||||
IsImage = isImage;
|
||||
AsBindless = true;
|
||||
TextureId = textureId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
public BufferDescriptor[][] ConstantBufferBindings { get; }
|
||||
public BufferDescriptor[][] StorageBufferBindings { get; }
|
||||
|
||||
public BindlessTextureFlags[] BindlessTextureFlags { get; }
|
||||
|
||||
public int MaxTextureBinding { get; }
|
||||
public int MaxImageBinding { get; }
|
||||
|
||||
|
@ -34,6 +36,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
ConstantBufferBindings = new BufferDescriptor[stageCount][];
|
||||
StorageBufferBindings = new BufferDescriptor[stageCount][];
|
||||
|
||||
BindlessTextureFlags = new BindlessTextureFlags[stageCount];
|
||||
|
||||
int maxTextureBinding = -1;
|
||||
int maxImageBinding = -1;
|
||||
int offset = isCompute ? 0 : 1;
|
||||
|
@ -94,6 +98,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
|
||||
ConstantBufferBindings[i] = stage.Info.CBuffers.ToArray();
|
||||
StorageBufferBindings[i] = stage.Info.SBuffers.ToArray();
|
||||
|
||||
BindlessTextureFlags[i] = stage.Info.BindlessTextureFlags;
|
||||
}
|
||||
|
||||
MaxTextureBinding = maxTextureBinding;
|
||||
|
|
|
@ -123,6 +123,14 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
return _oldSpecState.GetTextureTarget(_stageIndex, handle, cbufSlot).ConvertSamplerType();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryTextureBufferIndex()
|
||||
{
|
||||
byte textureBufferIndex = _oldSpecState.GetTextureBufferIndex();
|
||||
_newSpecState.RecordTextureBufferIndex(textureBufferIndex);
|
||||
return textureBufferIndex;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool QueryTextureCoordNormalized(int handle, int cbufSlot)
|
||||
{
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
private const ushort FileFormatVersionMajor = 1;
|
||||
private const ushort FileFormatVersionMinor = 2;
|
||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||
private const uint CodeGenVersion = 5791;
|
||||
private const uint CodeGenVersion = 3001;
|
||||
|
||||
private const string SharedTocFileName = "shared.toc";
|
||||
private const string SharedDataFileName = "shared.data";
|
||||
|
@ -184,6 +184,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
/// Indicates if the vertex shader accesses draw parameters.
|
||||
/// </summary>
|
||||
public bool UsesDrawParameters;
|
||||
|
||||
/// <summary>
|
||||
/// Flags indicating if and how bindless texture accesses were translated for the shader stage.
|
||||
/// </summary>
|
||||
public BindlessTextureFlags BindlessTextureFlags;
|
||||
}
|
||||
|
||||
private readonly DiskCacheGuestStorage _guestStorage;
|
||||
|
@ -799,6 +804,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
textures,
|
||||
images,
|
||||
dataInfo.Stage,
|
||||
dataInfo.BindlessTextureFlags,
|
||||
dataInfo.GeometryVerticesPerPrimitive,
|
||||
dataInfo.GeometryMaxOutputVertices,
|
||||
dataInfo.ThreadsPerInputPrimitive,
|
||||
|
@ -829,6 +835,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
TexturesCount = (ushort)info.Textures.Count,
|
||||
ImagesCount = (ushort)info.Images.Count,
|
||||
Stage = info.Stage,
|
||||
BindlessTextureFlags = info.BindlessTextureFlags,
|
||||
GeometryVerticesPerPrimitive = (byte)info.GeometryVerticesPerPrimitive,
|
||||
GeometryMaxOutputVertices = (ushort)info.GeometryMaxOutputVertices,
|
||||
ThreadsPerInputPrimitive = (ushort)info.ThreadsPerInputPrimitive,
|
||||
|
|
|
@ -134,6 +134,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
return GetTextureDescriptor(handle, cbufSlot).UnpackTextureTarget().ConvertSamplerType();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryTextureBufferIndex()
|
||||
{
|
||||
byte textureBufferIndex = (byte)_state.PoolState.TextureBufferIndex;
|
||||
_state.SpecializationState?.RecordTextureBufferIndex(textureBufferIndex);
|
||||
return textureBufferIndex;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool QueryTextureCoordNormalized(int handle, int cbufSlot)
|
||||
{
|
||||
|
|
|
@ -40,7 +40,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
/// <param name="vertexAsCompute">Indicates that the vertex shader will be emulated on a compute shader</param>
|
||||
public void InitializeReservedCounts(bool tfEnabled, bool vertexAsCompute)
|
||||
{
|
||||
ResourceReservationCounts rrc = new(!_context.Capabilities.SupportsTransformFeedback && tfEnabled, vertexAsCompute);
|
||||
ResourceReservationCounts rrc = new(
|
||||
_context.Capabilities.Api,
|
||||
!_context.Capabilities.SupportsTransformFeedback && tfEnabled,
|
||||
vertexAsCompute);
|
||||
|
||||
_reservedConstantBuffers = rrc.ReservedConstantBuffers;
|
||||
_reservedStorageBuffers = rrc.ReservedStorageBuffers;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
|
@ -10,11 +11,17 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
class ShaderInfoBuilder
|
||||
{
|
||||
private const int TotalSets = 4;
|
||||
private const int TotalBindlessSets = 9;
|
||||
|
||||
private const int UniformSetIndex = 0;
|
||||
private const int StorageSetIndex = 1;
|
||||
private const int TextureSetIndex = 2;
|
||||
private const int ImageSetIndex = 3;
|
||||
private const int BindlessTextureSetIndex = 4;
|
||||
private const int BindlessBufferTextureSetIndex = 5;
|
||||
private const int BindlessSamplerSetIndex = 6;
|
||||
private const int BindlessImageSetIndex = 7;
|
||||
private const int BindlessBufferImageSetIndex = 8;
|
||||
|
||||
private const ResourceStages SupportBufferStages =
|
||||
ResourceStages.Compute |
|
||||
|
@ -27,17 +34,20 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
ResourceStages.TessellationEvaluation |
|
||||
ResourceStages.Geometry;
|
||||
|
||||
private const ResourceStages GraphicsStages = VtgStages | ResourceStages.Fragment;
|
||||
|
||||
private readonly GpuContext _context;
|
||||
|
||||
private int _fragmentOutputMap;
|
||||
private bool _anyBindless;
|
||||
|
||||
private readonly int _reservedConstantBuffers;
|
||||
private readonly int _reservedStorageBuffers;
|
||||
private readonly int _reservedTextures;
|
||||
private readonly int _reservedImages;
|
||||
|
||||
private readonly List<ResourceDescriptor>[] _resourceDescriptors;
|
||||
private readonly List<ResourceUsage>[] _resourceUsages;
|
||||
private List<ResourceDescriptor>[] _resourceDescriptors;
|
||||
private List<ResourceUsage>[] _resourceUsages;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new shader info builder.
|
||||
|
@ -63,7 +73,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
AddDescriptor(SupportBufferStages, ResourceType.UniformBuffer, UniformSetIndex, 0, 1);
|
||||
AddUsage(SupportBufferStages, ResourceType.UniformBuffer, UniformSetIndex, 0, 1);
|
||||
|
||||
ResourceReservationCounts rrc = new(!context.Capabilities.SupportsTransformFeedback && tfEnabled, vertexAsCompute);
|
||||
ResourceReservationCounts rrc = new(
|
||||
context.Capabilities.Api,
|
||||
!context.Capabilities.SupportsTransformFeedback && tfEnabled,
|
||||
vertexAsCompute);
|
||||
|
||||
_reservedConstantBuffers = rrc.ReservedConstantBuffers;
|
||||
_reservedStorageBuffers = rrc.ReservedStorageBuffers;
|
||||
|
@ -97,6 +110,30 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
_fragmentOutputMap = info.FragmentOutputMap;
|
||||
}
|
||||
|
||||
if (info.BindlessTextureFlags != BindlessTextureFlags.None && !_anyBindless)
|
||||
{
|
||||
Array.Resize(ref _resourceDescriptors, TotalBindlessSets);
|
||||
Array.Resize(ref _resourceUsages, TotalBindlessSets);
|
||||
|
||||
for (int index = TotalSets; index < TotalBindlessSets; index++)
|
||||
{
|
||||
_resourceDescriptors[index] = new();
|
||||
_resourceUsages[index] = new();
|
||||
}
|
||||
|
||||
ResourceStages bindlessStages = info.Stage == ShaderStage.Compute ? ResourceStages.Compute : GraphicsStages;
|
||||
|
||||
AddDescriptor(bindlessStages, ResourceType.UniformBuffer, BindlessTextureSetIndex, 0, 1);
|
||||
AddDescriptor(bindlessStages, ResourceType.StorageBuffer, BindlessTextureSetIndex, 1, 1);
|
||||
AddArrayDescriptor(bindlessStages, ResourceType.Texture, BindlessTextureSetIndex, 2, 0);
|
||||
AddArrayDescriptor(bindlessStages, ResourceType.BufferTexture, BindlessBufferTextureSetIndex, 0, 0);
|
||||
AddArrayDescriptor(bindlessStages, ResourceType.Sampler, BindlessSamplerSetIndex, 0, 0);
|
||||
AddArrayDescriptor(bindlessStages, ResourceType.Image, BindlessImageSetIndex, 0, 0);
|
||||
AddArrayDescriptor(bindlessStages, ResourceType.BufferImage, BindlessBufferImageSetIndex, 0, 0);
|
||||
|
||||
_anyBindless = true;
|
||||
}
|
||||
|
||||
int stageIndex = GpuAccessorBase.GetStageIndex(info.Stage switch
|
||||
{
|
||||
ShaderStage.TessellationControl => 1,
|
||||
|
@ -154,6 +191,19 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an array of resource descriptors to the list of descriptors.
|
||||
/// </summary>
|
||||
/// <param name="stages">Shader stages where the resource is used</param>
|
||||
/// <param name="type">Type of the resource</param>
|
||||
/// <param name="setIndex">Descriptor set number where the resource will be bound</param>
|
||||
/// <param name="binding">Binding number where the resource will be bound</param>
|
||||
/// <param name="count">Number of array elements</param>
|
||||
private void AddArrayDescriptor(ResourceStages stages, ResourceType type, int setIndex, int binding, int count)
|
||||
{
|
||||
_resourceDescriptors[setIndex].Add(new ResourceDescriptor(binding, count, type, stages));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds two interleaved groups of resources to the list of descriptors.
|
||||
/// </summary>
|
||||
|
@ -235,10 +285,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
/// <returns>Shader information</returns>
|
||||
public ShaderInfo Build(ProgramPipelineState? pipeline, bool fromCache = false)
|
||||
{
|
||||
var descriptors = new ResourceDescriptorCollection[TotalSets];
|
||||
var usages = new ResourceUsageCollection[TotalSets];
|
||||
var descriptors = new ResourceDescriptorCollection[_resourceDescriptors.Length];
|
||||
var usages = new ResourceUsageCollection[_resourceUsages.Length];
|
||||
|
||||
for (int index = 0; index < TotalSets; index++)
|
||||
for (int index = 0; index < _resourceDescriptors.Length; index++)
|
||||
{
|
||||
descriptors[index] = new ResourceDescriptorCollection(_resourceDescriptors[index].ToArray().AsReadOnly());
|
||||
usages[index] = new ResourceUsageCollection(_resourceUsages[index].ToArray().AsReadOnly());
|
||||
|
@ -248,11 +298,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
|
||||
if (pipeline.HasValue)
|
||||
{
|
||||
return new ShaderInfo(_fragmentOutputMap, resourceLayout, pipeline.Value, fromCache);
|
||||
return new ShaderInfo(_fragmentOutputMap, _anyBindless, resourceLayout, pipeline.Value, fromCache);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new ShaderInfo(_fragmentOutputMap, resourceLayout, fromCache);
|
||||
return new ShaderInfo(_fragmentOutputMap, _anyBindless, resourceLayout, fromCache);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,11 +30,13 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
{
|
||||
PrimitiveTopology = 1 << 1,
|
||||
TransformFeedback = 1 << 3,
|
||||
TextureBufferIndex = 1 << 4,
|
||||
}
|
||||
|
||||
private QueriedStateFlags _queriedState;
|
||||
private bool _compute;
|
||||
private byte _constantBufferUsePerStage;
|
||||
private byte _textureBufferIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Compute engine state.
|
||||
|
@ -323,6 +325,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
state.Value.CoordNormalized = coordNormalized;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records the index of the constant buffer with texture handles.
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the constant buffer with texture handles</param>
|
||||
public void RecordTextureBufferIndex(byte index)
|
||||
{
|
||||
_textureBufferIndex = index;
|
||||
_queriedState |= QueriedStateFlags.TextureBufferIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the format of a given texture was used during the shader translation process.
|
||||
/// </summary>
|
||||
|
@ -385,18 +397,30 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
/// <param name="stageIndex">Shader stage where the texture is used</param>
|
||||
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
|
||||
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
|
||||
/// <returns>Format of the given texture, and whether that format is a sRGB format</returns>
|
||||
public (uint, bool) GetFormat(int stageIndex, int handle, int cbufSlot)
|
||||
{
|
||||
TextureSpecializationState state = GetTextureSpecState(stageIndex, handle, cbufSlot).Value;
|
||||
return (state.Format, state.FormatSrgb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the constant buffer with texture handles.
|
||||
/// </summary>
|
||||
/// <returns>Index of the constant buffer with texture handles</returns>
|
||||
public byte GetTextureBufferIndex()
|
||||
{
|
||||
// Note: We assume the NVN default if the cache is old and did not store the buffer index.
|
||||
return _queriedState.HasFlag(QueriedStateFlags.TextureBufferIndex) ? _textureBufferIndex : (byte)TextureHandle.NvnTextureBufferIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the recorded target of a given texture.
|
||||
/// </summary>
|
||||
/// <param name="stageIndex">Shader stage where the texture is used</param>
|
||||
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
|
||||
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
|
||||
/// <returns>Target of the given texture</returns>
|
||||
public TextureTarget GetTextureTarget(int stageIndex, int handle, int cbufSlot)
|
||||
{
|
||||
return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.TextureTarget;
|
||||
|
@ -408,6 +432,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
/// <param name="stageIndex">Shader stage where the texture is used</param>
|
||||
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
|
||||
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
|
||||
/// <returns>Normalization state of the given texture</returns>
|
||||
public bool GetCoordNormalized(int stageIndex, int handle, int cbufSlot)
|
||||
{
|
||||
return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.CoordNormalized;
|
||||
|
@ -799,6 +824,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
constantBufferUsePerStageMask &= ~(1 << index);
|
||||
}
|
||||
|
||||
if (specState._queriedState.HasFlag(QueriedStateFlags.TextureBufferIndex))
|
||||
{
|
||||
dataReader.Read(ref specState._textureBufferIndex);
|
||||
}
|
||||
|
||||
bool hasPipelineState = false;
|
||||
|
||||
dataReader.Read(ref hasPipelineState);
|
||||
|
@ -870,6 +900,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
constantBufferUsePerStageMask &= ~(1 << index);
|
||||
}
|
||||
|
||||
if (_queriedState.HasFlag(QueriedStateFlags.TextureBufferIndex))
|
||||
{
|
||||
dataWriter.Write(ref _textureBufferIndex);
|
||||
}
|
||||
|
||||
bool hasPipelineState = PipelineState.HasValue;
|
||||
|
||||
dataWriter.Write(ref hasPipelineState);
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
{
|
||||
static class HwCapabilities
|
||||
{
|
||||
private static readonly Lazy<bool> _supportsArbBindlessTexture = new Lazy<bool>(() => HasExtension("GL_ARB_bindless_texture"));
|
||||
private static readonly Lazy<bool> _supportsAlphaToCoverageDitherControl = new(() => HasExtension("GL_NV_alpha_to_coverage_dither_control"));
|
||||
private static readonly Lazy<bool> _supportsAstcCompression = new(() => HasExtension("GL_KHR_texture_compression_astc_ldr"));
|
||||
private static readonly Lazy<bool> _supportsBlendEquationAdvanced = new(() => HasExtension("GL_NV_blend_equation_advanced"));
|
||||
|
@ -14,6 +15,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
private static readonly Lazy<bool> _supportsGeometryShaderPassthrough = new(() => HasExtension("GL_NV_geometry_shader_passthrough"));
|
||||
private static readonly Lazy<bool> _supportsImageLoadFormatted = new(() => HasExtension("GL_EXT_shader_image_load_formatted"));
|
||||
private static readonly Lazy<bool> _supportsIndirectParameters = new(() => HasExtension("GL_ARB_indirect_parameters"));
|
||||
private static readonly Lazy<bool> _supportsNvBindlessTexture = new Lazy<bool>(() => HasExtension("GL_NV_bindless_texture"));
|
||||
private static readonly Lazy<bool> _supportsParallelShaderCompile = new(() => HasExtension("GL_ARB_parallel_shader_compile"));
|
||||
private static readonly Lazy<bool> _supportsPolygonOffsetClamp = new(() => HasExtension("GL_EXT_polygon_offset_clamp"));
|
||||
private static readonly Lazy<bool> _supportsQuads = new(SupportsQuadsCheck);
|
||||
|
@ -51,6 +53,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
|
||||
public static bool UsePersistentBufferForFlush => _gpuVendor.Value == GpuVendor.AmdWindows || _gpuVendor.Value == GpuVendor.Nvidia;
|
||||
|
||||
public static bool SupportsArbBindlessTexture => _supportsArbBindlessTexture.Value;
|
||||
public static bool SupportsAlphaToCoverageDitherControl => _supportsAlphaToCoverageDitherControl.Value;
|
||||
public static bool SupportsAstcCompression => _supportsAstcCompression.Value;
|
||||
public static bool SupportsBlendEquationAdvanced => _supportsBlendEquationAdvanced.Value;
|
||||
|
@ -60,6 +63,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
public static bool SupportsGeometryShaderPassthrough => _supportsGeometryShaderPassthrough.Value;
|
||||
public static bool SupportsImageLoadFormatted => _supportsImageLoadFormatted.Value;
|
||||
public static bool SupportsIndirectParameters => _supportsIndirectParameters.Value;
|
||||
public static bool SupportsNvBindlessTexture => _supportsNvBindlessTexture.Value;
|
||||
public static bool SupportsParallelShaderCompile => _supportsParallelShaderCompile.Value;
|
||||
public static bool SupportsPolygonOffsetClamp => _supportsPolygonOffsetClamp.Value;
|
||||
public static bool SupportsQuads => _supportsQuads.Value;
|
||||
|
|
245
src/Ryujinx.Graphics.OpenGL/Image/BindlessHandleManager.cs
Normal file
245
src/Ryujinx.Graphics.OpenGL/Image/BindlessHandleManager.cs
Normal 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);
|
||||
}
|
||||
}
|
166
src/Ryujinx.Graphics.OpenGL/Image/BindlessManager.cs
Normal file
166
src/Ryujinx.Graphics.OpenGL/Image/BindlessManager.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
189
src/Ryujinx.Graphics.OpenGL/Image/BitMap.cs
Normal file
189
src/Ryujinx.Graphics.OpenGL/Image/BitMap.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
using OpenTK.Graphics.OpenGL;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.OpenGL.Image
|
||||
{
|
||||
|
@ -15,6 +16,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
public Target Target => Info.Target;
|
||||
public Format Format => Info.Format;
|
||||
|
||||
private Dictionary<int, (BindlessManager, long, int)> _bindlessHandles;
|
||||
|
||||
public TextureBase(TextureCreateInfo info)
|
||||
{
|
||||
Info = info;
|
||||
|
@ -35,8 +38,32 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
|
||||
public static void ClearBinding(int unit)
|
||||
{
|
||||
GL.ActiveTexture(TextureUnit.Texture0 + unit);
|
||||
GL.BindTextureUnit(unit, 0);
|
||||
}
|
||||
|
||||
public bool AddBindlessHandle(int textureId, int samplerId, BindlessManager owner, long bindlessHandle)
|
||||
{
|
||||
var bindlessHandles = _bindlessHandles ??= new();
|
||||
return bindlessHandles.TryAdd(samplerId, (owner, bindlessHandle, textureId));
|
||||
}
|
||||
|
||||
public void RevokeBindlessAccess()
|
||||
{
|
||||
if (_bindlessHandles == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var kv in _bindlessHandles)
|
||||
{
|
||||
int samplerId = kv.Key;
|
||||
(BindlessManager owner, long bindlessHandle, int textureId) = kv.Value;
|
||||
|
||||
owner.Unregister(textureId, samplerId, bindlessHandle);
|
||||
}
|
||||
|
||||
_bindlessHandles.Clear();
|
||||
_bindlessHandles = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,6 +97,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
RevokeBindlessAccess();
|
||||
|
||||
if (Handle != 0)
|
||||
{
|
||||
GL.DeleteTexture(Handle);
|
||||
|
|
|
@ -890,6 +890,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||
/// </summary>
|
||||
public void Release()
|
||||
{
|
||||
RevokeBindlessAccess();
|
||||
|
||||
bool hadHandle = Handle != 0;
|
||||
|
||||
if (_parent.DefaultView != this)
|
||||
|
|
|
@ -45,7 +45,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
|
||||
public OpenGLRenderer()
|
||||
{
|
||||
_pipeline = new Pipeline();
|
||||
_pipeline = new Pipeline(this);
|
||||
_counters = new Counters();
|
||||
_window = new Window(this);
|
||||
_textureCopy = new TextureCopy(this);
|
||||
|
|
|
@ -26,7 +26,6 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
private IntPtr _indexBaseOffset;
|
||||
|
||||
private DrawElementsType _elementsType;
|
||||
|
||||
private PrimitiveType _primitiveType;
|
||||
|
||||
private int _stencilFrontMask;
|
||||
|
@ -66,9 +65,11 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
private readonly BufferHandle[] _tfbs;
|
||||
private readonly BufferRange[] _tfbTargets;
|
||||
|
||||
private readonly BindlessManager _bindlessManager;
|
||||
|
||||
private ColorF _blendConstant;
|
||||
|
||||
internal Pipeline()
|
||||
internal Pipeline(OpenGLRenderer renderer)
|
||||
{
|
||||
_drawTexture = new DrawTextureEmulation();
|
||||
_rasterizerDiscard = false;
|
||||
|
@ -82,6 +83,8 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
|
||||
_tfbs = new BufferHandle[Constants.MaxTransformFeedbackBuffers];
|
||||
_tfbTargets = new BufferRange[Constants.MaxTransformFeedbackBuffers];
|
||||
|
||||
_bindlessManager = new BindlessManager(renderer);
|
||||
}
|
||||
|
||||
public void Barrier()
|
||||
|
@ -759,6 +762,21 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
_tfEnabled = false;
|
||||
}
|
||||
|
||||
public void RegisterBindlessSampler(int samplerId, ISampler sampler)
|
||||
{
|
||||
_bindlessManager.AddSeparateSampler(samplerId, sampler);
|
||||
}
|
||||
|
||||
public void RegisterBindlessTexture(int textureId, ITexture texture, float textureScale)
|
||||
{
|
||||
_bindlessManager.AddSeparateTexture(textureId, texture, textureScale);
|
||||
}
|
||||
|
||||
public void RegisterBindlessTextureAndSampler(int textureId, ITexture texture, float textureScale, int samplerId, ISampler sampler)
|
||||
{
|
||||
_bindlessManager.Add(textureId, texture, textureScale, samplerId, sampler);
|
||||
}
|
||||
|
||||
public void SetAlphaTest(bool enable, float reference, CompareOp op)
|
||||
{
|
||||
if (!enable)
|
||||
|
@ -1302,7 +1320,6 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers)
|
||||
{
|
||||
if (_tfEnabled)
|
||||
|
|
79
src/Ryujinx.Graphics.OpenGL/TypedBuffer.cs
Normal file
79
src/Ryujinx.Graphics.OpenGL/TypedBuffer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
11
src/Ryujinx.Graphics.Shader/BindlessTextureFlags.cs
Normal file
11
src/Ryujinx.Graphics.Shader/BindlessTextureFlags.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
namespace Ryujinx.Graphics.Shader
|
||||
{
|
||||
public enum BindlessTextureFlags : ushort
|
||||
{
|
||||
None = 0,
|
||||
|
||||
BindlessConverted = 1 << 0,
|
||||
BindlessNvn = 1 << 1,
|
||||
BindlessFull = 1 << 2,
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen
|
|||
public readonly HostCapabilities HostCapabilities;
|
||||
public readonly ILogger Logger;
|
||||
public readonly TargetApi TargetApi;
|
||||
public readonly BindlessTextureFlags BindlessTextureFlags;
|
||||
|
||||
public CodeGenParameters(
|
||||
AttributeUsage attributeUsage,
|
||||
|
@ -18,7 +19,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen
|
|||
ShaderProperties properties,
|
||||
HostCapabilities hostCapabilities,
|
||||
ILogger logger,
|
||||
TargetApi targetApi)
|
||||
TargetApi targetApi,
|
||||
BindlessTextureFlags bindlessTextureFlags)
|
||||
{
|
||||
AttributeUsage = attributeUsage;
|
||||
Definitions = definitions;
|
||||
|
@ -26,6 +28,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen
|
|||
HostCapabilities = hostCapabilities;
|
||||
Logger = logger;
|
||||
TargetApi = targetApi;
|
||||
BindlessTextureFlags = bindlessTextureFlags;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||
public HostCapabilities HostCapabilities { get; }
|
||||
public ILogger Logger { get; }
|
||||
public TargetApi TargetApi { get; }
|
||||
public BindlessTextureFlags BindlessTextureFlags { get; }
|
||||
|
||||
public OperandManager OperandManager { get; }
|
||||
|
||||
|
@ -36,6 +37,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||
HostCapabilities = parameters.HostCapabilities;
|
||||
Logger = parameters.Logger;
|
||||
TargetApi = parameters.TargetApi;
|
||||
BindlessTextureFlags = parameters.BindlessTextureFlags;
|
||||
|
||||
OperandManager = new OperandManager();
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
{
|
||||
|
@ -15,6 +14,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||
public static void Declare(CodeGenContext context, StructuredProgramInfo info)
|
||||
{
|
||||
context.AppendLine(context.TargetApi == TargetApi.Vulkan ? "#version 460 core" : "#version 450 core");
|
||||
context.AppendLine("#extension GL_ARB_bindless_texture : enable");
|
||||
context.AppendLine("#extension GL_ARB_gpu_shader_int64 : enable");
|
||||
|
||||
if (context.HostCapabilities.SupportsShaderBallot)
|
||||
|
@ -32,6 +32,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||
context.AppendLine("#extension GL_EXT_shader_image_load_formatted : enable");
|
||||
context.AppendLine("#extension GL_EXT_texture_shadow_lod : enable");
|
||||
|
||||
if (context.TargetApi == TargetApi.Vulkan)
|
||||
{
|
||||
context.AppendLine("#extension GL_EXT_nonuniform_qualifier : enable");
|
||||
}
|
||||
|
||||
if (context.Definitions.Stage == ShaderStage.Compute)
|
||||
{
|
||||
context.AppendLine("#extension GL_ARB_compute_shader : enable");
|
||||
|
@ -189,6 +194,18 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||
}
|
||||
}
|
||||
|
||||
if (context.BindlessTextureFlags != BindlessTextureFlags.None)
|
||||
{
|
||||
if (context.TargetApi == TargetApi.Vulkan)
|
||||
{
|
||||
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/GetBindlessHandleVk.glsl");
|
||||
}
|
||||
else
|
||||
{
|
||||
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/GetBindlessHandle.glsl");
|
||||
}
|
||||
}
|
||||
|
||||
if ((info.HelperFunctionsMask & HelperFunctionsMask.MultiplyHighS32) != 0)
|
||||
{
|
||||
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighS32.glsl");
|
||||
|
@ -337,83 +354,84 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||
}
|
||||
}
|
||||
|
||||
private static void DeclareSamplers(CodeGenContext context, IEnumerable<TextureDefinition> definitions)
|
||||
private static void DeclareSamplers(CodeGenContext context, IEnumerable<TextureDefinition> samplers)
|
||||
{
|
||||
int arraySize = 0;
|
||||
|
||||
foreach (var definition in definitions)
|
||||
foreach (var sampler in samplers)
|
||||
{
|
||||
string indexExpr = string.Empty;
|
||||
|
||||
if (definition.Type.HasFlag(SamplerType.Indexed))
|
||||
{
|
||||
if (arraySize == 0)
|
||||
{
|
||||
arraySize = ResourceManager.SamplerArraySize;
|
||||
}
|
||||
else if (--arraySize != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
indexExpr = $"[{NumberFormatter.FormatInt(arraySize)}]";
|
||||
}
|
||||
|
||||
string samplerTypeName = definition.Type.ToGlslSamplerType();
|
||||
string samplerTypeName = sampler.Type.HasFlag(SamplerType.Separate)
|
||||
? (sampler.Type & ~SamplerType.Separate).ToGlslTextureType()
|
||||
: sampler.Type.ToGlslSamplerType();
|
||||
|
||||
string layout = string.Empty;
|
||||
|
||||
if (context.TargetApi == TargetApi.Vulkan)
|
||||
{
|
||||
layout = $", set = {definition.Set}";
|
||||
layout = $"set = {sampler.Set}, ";
|
||||
}
|
||||
|
||||
context.AppendLine($"layout (binding = {definition.Binding}{layout}) uniform {samplerTypeName} {definition.Name}{indexExpr};");
|
||||
}
|
||||
}
|
||||
string suffix = string.Empty;
|
||||
|
||||
private static void DeclareImages(CodeGenContext context, IEnumerable<TextureDefinition> definitions)
|
||||
if (sampler.ArraySize == 0)
|
||||
{
|
||||
int arraySize = 0;
|
||||
|
||||
foreach (var definition in definitions)
|
||||
{
|
||||
string indexExpr = string.Empty;
|
||||
|
||||
if (definition.Type.HasFlag(SamplerType.Indexed))
|
||||
{
|
||||
if (arraySize == 0)
|
||||
{
|
||||
arraySize = ResourceManager.SamplerArraySize;
|
||||
suffix = "[]";
|
||||
}
|
||||
else if (--arraySize != 0)
|
||||
else if (sampler.ArraySize != 1)
|
||||
{
|
||||
continue;
|
||||
suffix = $"[{(uint)sampler.ArraySize}]";
|
||||
}
|
||||
|
||||
indexExpr = $"[{NumberFormatter.FormatInt(arraySize)}]";
|
||||
if (sampler.ArraySize != 1)
|
||||
{
|
||||
Console.WriteLine($"found bindless {sampler.Name} {(sampler.Type & ~SamplerType.Shadow)}");
|
||||
context.OperandManager.BindlessTextures[sampler.Type & ~(SamplerType.Shadow | SamplerType.Separate)] = sampler.Name;
|
||||
}
|
||||
|
||||
string imageTypeName = definition.Type.ToGlslImageType(definition.Format.GetComponentType());
|
||||
context.AppendLine($"layout ({layout}binding = {sampler.Binding}) uniform {samplerTypeName} {sampler.Name}{suffix};");
|
||||
}
|
||||
}
|
||||
|
||||
if (definition.Flags.HasFlag(TextureUsageFlags.ImageCoherent))
|
||||
private static void DeclareImages(CodeGenContext context, IEnumerable<TextureDefinition> images)
|
||||
{
|
||||
foreach (var image in images)
|
||||
{
|
||||
string imageTypeName = image.Type.ToGlslImageType(image.Format.GetComponentType());
|
||||
|
||||
if (image.Flags.HasFlag(TextureUsageFlags.ImageCoherent))
|
||||
{
|
||||
imageTypeName = "coherent " + imageTypeName;
|
||||
}
|
||||
|
||||
string layout = definition.Format.ToGlslFormat();
|
||||
|
||||
if (!string.IsNullOrEmpty(layout))
|
||||
{
|
||||
layout = ", " + layout;
|
||||
}
|
||||
string layout = string.Empty;
|
||||
|
||||
if (context.TargetApi == TargetApi.Vulkan)
|
||||
{
|
||||
layout = $", set = {definition.Set}{layout}";
|
||||
layout = $"set = {image.Set}, ";
|
||||
}
|
||||
|
||||
context.AppendLine($"layout (binding = {definition.Binding}{layout}) uniform {imageTypeName} {definition.Name}{indexExpr};");
|
||||
string format = image.Format.ToGlslFormat();
|
||||
|
||||
if (!string.IsNullOrEmpty(format))
|
||||
{
|
||||
format = ", " + format;
|
||||
}
|
||||
|
||||
string suffix = string.Empty;
|
||||
|
||||
if (image.ArraySize == 0)
|
||||
{
|
||||
suffix = "[]";
|
||||
}
|
||||
else if (image.ArraySize != 1)
|
||||
{
|
||||
suffix = $"[{(uint)image.ArraySize}]";
|
||||
}
|
||||
|
||||
if (image.ArraySize != 1)
|
||||
{
|
||||
context.OperandManager.BindlessTextures[image.Type] = image.Name;
|
||||
}
|
||||
|
||||
context.AppendLine($"layout ({layout}binding = {image.Binding}{format}) uniform {imageTypeName} {image.Name}{suffix};");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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)];
|
||||
}
|
|
@ -2,6 +2,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||
{
|
||||
static class HelperFunctionNames
|
||||
{
|
||||
public static string GetBindlessHandle = "Helper_GetBindlessHandle";
|
||||
public static string GetBindlessTextureIndexVk = "Helper_GetBindlessTextureIndex";
|
||||
public static string GetBindlessSamplerIndexVk = "Helper_GetBindlessSamplerIndex";
|
||||
|
||||
public static string MultiplyHighS32 = "Helper_MultiplyHighS32";
|
||||
public static string MultiplyHighU32 = "Helper_MultiplyHighU32";
|
||||
|
||||
|
|
|
@ -16,33 +16,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||
|
||||
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
|
||||
|
||||
// TODO: Bindless texture support. For now we just return 0/do nothing.
|
||||
if (isBindless)
|
||||
{
|
||||
switch (texOp.Inst)
|
||||
{
|
||||
case Instruction.ImageStore:
|
||||
return "// imageStore(bindless)";
|
||||
case Instruction.ImageLoad:
|
||||
AggregateType componentType = texOp.Format.GetComponentType();
|
||||
|
||||
NumberFormatter.TryFormat(0, componentType, out string imageConst);
|
||||
|
||||
AggregateType outputType = texOp.GetVectorType(componentType);
|
||||
|
||||
if ((outputType & AggregateType.ElementCountMask) != 0)
|
||||
{
|
||||
return $"{Declarations.GetVarTypeName(context, outputType, precise: false)}({imageConst})";
|
||||
}
|
||||
|
||||
return imageConst;
|
||||
default:
|
||||
return NumberFormatter.FormatInt(0);
|
||||
}
|
||||
}
|
||||
|
||||
bool isArray = (texOp.Type & SamplerType.Array) != 0;
|
||||
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
|
||||
|
||||
var texCallBuilder = new StringBuilder();
|
||||
|
||||
|
@ -70,21 +44,27 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||
texCallBuilder.Append(texOp.Inst == Instruction.ImageLoad ? "imageLoad" : "imageStore");
|
||||
}
|
||||
|
||||
int srcIndex = isBindless ? 1 : 0;
|
||||
int srcIndex = 0;
|
||||
|
||||
string Src(AggregateType type)
|
||||
{
|
||||
return GetSoureExpr(context, texOp.GetSource(srcIndex++), type);
|
||||
}
|
||||
|
||||
string indexExpr = null;
|
||||
AggregateType type = texOp.Format.GetComponentType();
|
||||
|
||||
if (isIndexed)
|
||||
string bindlessHandle = null;
|
||||
string imageName;
|
||||
|
||||
if (isBindless)
|
||||
{
|
||||
indexExpr = Src(AggregateType.S32);
|
||||
bindlessHandle = Src(AggregateType.S32);
|
||||
imageName = GetBindlessImage(context, texOp.Type, type, bindlessHandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
imageName = GetImageName(context.Properties, texOp);
|
||||
}
|
||||
|
||||
string imageName = GetImageName(context.Properties, texOp, indexExpr);
|
||||
|
||||
texCallBuilder.Append('(');
|
||||
texCallBuilder.Append(imageName);
|
||||
|
@ -117,8 +97,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||
|
||||
if (texOp.Inst == Instruction.ImageStore)
|
||||
{
|
||||
AggregateType type = texOp.Format.GetComponentType();
|
||||
|
||||
string[] cElems = new string[4];
|
||||
|
||||
for (int index = 0; index < 4; index++)
|
||||
|
@ -150,8 +128,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||
|
||||
if (texOp.Inst == Instruction.ImageAtomic)
|
||||
{
|
||||
AggregateType type = texOp.Format.GetComponentType();
|
||||
|
||||
if ((texOp.Flags & TextureFlags.AtomicMask) == TextureFlags.CAS)
|
||||
{
|
||||
Append(Src(type)); // Compare value.
|
||||
|
@ -207,18 +183,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||
return NumberFormatter.FormatFloat(0);
|
||||
}
|
||||
|
||||
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
|
||||
string samplerName = GetSamplerName(context.Properties, texOp);
|
||||
|
||||
string indexExpr = null;
|
||||
|
||||
if (isIndexed)
|
||||
{
|
||||
indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32);
|
||||
}
|
||||
|
||||
string samplerName = GetSamplerName(context.Properties, texOp, indexExpr);
|
||||
|
||||
int coordsIndex = isBindless || isIndexed ? 1 : 0;
|
||||
int coordsIndex = isBindless ? 1 : 0;
|
||||
|
||||
string coordsExpr;
|
||||
|
||||
|
@ -260,7 +227,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||
bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0;
|
||||
|
||||
bool isArray = (texOp.Type & SamplerType.Array) != 0;
|
||||
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
|
||||
bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0;
|
||||
bool isShadow = (texOp.Type & SamplerType.Shadow) != 0;
|
||||
|
||||
|
@ -286,24 +252,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||
hasLodLevel = false;
|
||||
}
|
||||
|
||||
// TODO: Bindless texture support. For now we just return 0.
|
||||
if (isBindless)
|
||||
{
|
||||
string scalarValue = NumberFormatter.FormatFloat(0);
|
||||
|
||||
if (colorIsVector)
|
||||
{
|
||||
AggregateType outputType = texOp.GetVectorType(AggregateType.FP32);
|
||||
|
||||
if ((outputType & AggregateType.ElementCountMask) != 0)
|
||||
{
|
||||
return $"{Declarations.GetVarTypeName(context, outputType, precise: false)}({scalarValue})";
|
||||
}
|
||||
}
|
||||
|
||||
return scalarValue;
|
||||
}
|
||||
|
||||
string texCall = intCoords ? "texelFetch" : "texture";
|
||||
|
||||
if (isGather)
|
||||
|
@ -328,23 +276,24 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||
texCall += "Offsets";
|
||||
}
|
||||
|
||||
int srcIndex = isBindless ? 1 : 0;
|
||||
int srcIndex = 0;
|
||||
|
||||
string Src(AggregateType type)
|
||||
{
|
||||
return GetSoureExpr(context, texOp.GetSource(srcIndex++), type);
|
||||
}
|
||||
|
||||
string indexExpr = null;
|
||||
string bindlessHandle = null;
|
||||
|
||||
if (isIndexed)
|
||||
if (isBindless)
|
||||
{
|
||||
indexExpr = Src(AggregateType.S32);
|
||||
bindlessHandle = Src(AggregateType.S32);
|
||||
texCall += "(" + GetBindlessSampler(context, texOp.Type, bindlessHandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
texCall += "(" + GetSamplerName(context.Properties, texOp);
|
||||
}
|
||||
|
||||
string samplerName = GetSamplerName(context.Properties, texOp, indexExpr);
|
||||
|
||||
texCall += "(" + samplerName;
|
||||
|
||||
int coordsCount = texOp.Type.GetDimensions();
|
||||
|
||||
|
@ -523,22 +472,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||
|
||||
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
|
||||
|
||||
// TODO: Bindless texture support. For now we just return 0.
|
||||
if (isBindless)
|
||||
{
|
||||
return NumberFormatter.FormatInt(0);
|
||||
}
|
||||
string bindlessHandle = isBindless ? GetSoureExpr(context, operation.GetSource(0), AggregateType.S32) : null;
|
||||
|
||||
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
|
||||
|
||||
string indexExpr = null;
|
||||
|
||||
if (isIndexed)
|
||||
{
|
||||
indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32);
|
||||
}
|
||||
|
||||
string samplerName = GetSamplerName(context.Properties, texOp, indexExpr);
|
||||
string samplerName = isBindless
|
||||
? GetBindlessSampler(context, texOp.Type, bindlessHandle)
|
||||
: GetSamplerName(context.Properties, texOp);
|
||||
|
||||
return $"textureSamples({samplerName})";
|
||||
}
|
||||
|
@ -549,22 +487,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||
|
||||
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
|
||||
|
||||
// TODO: Bindless texture support. For now we just return 0.
|
||||
if (isBindless)
|
||||
{
|
||||
return NumberFormatter.FormatInt(0);
|
||||
}
|
||||
string bindlessHandle = isBindless ? GetSoureExpr(context, operation.GetSource(0), AggregateType.S32) : null;
|
||||
|
||||
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
|
||||
|
||||
string indexExpr = null;
|
||||
|
||||
if (isIndexed)
|
||||
{
|
||||
indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32);
|
||||
}
|
||||
|
||||
string samplerName = GetSamplerName(context.Properties, texOp, indexExpr);
|
||||
string samplerName = isBindless
|
||||
? GetBindlessSampler(context, texOp.Type, bindlessHandle)
|
||||
: GetSamplerName(context.Properties, texOp);
|
||||
|
||||
if (texOp.Index == 3)
|
||||
{
|
||||
|
@ -572,13 +499,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||
}
|
||||
else
|
||||
{
|
||||
context.Properties.Textures.TryGetValue(texOp.Binding, out TextureDefinition definition);
|
||||
context.Properties.Textures.TryGetValue(SetBindingPair.Unpack(texOp.Binding), out TextureDefinition definition);
|
||||
bool hasLod = !definition.Type.HasFlag(SamplerType.Multisample) && (definition.Type & SamplerType.Mask) != SamplerType.TextureBuffer;
|
||||
string texCall;
|
||||
|
||||
if (hasLod)
|
||||
{
|
||||
int lodSrcIndex = isBindless || isIndexed ? 1 : 0;
|
||||
int lodSrcIndex = isBindless ? 1 : 0;
|
||||
IAstNode lod = operation.GetSource(lodSrcIndex);
|
||||
string lodExpr = GetSoureExpr(context, lod, GetSrcVarType(operation.Inst, lodSrcIndex));
|
||||
|
||||
|
@ -619,8 +546,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||
|
||||
int binding = bindingIndex.Value;
|
||||
BufferDefinition buffer = storageKind == StorageKind.ConstantBuffer
|
||||
? context.Properties.ConstantBuffers[binding]
|
||||
: context.Properties.StorageBuffers[binding];
|
||||
? context.Properties.ConstantBuffers[SetBindingPair.Unpack(binding)]
|
||||
: context.Properties.StorageBuffers[SetBindingPair.Unpack(binding)];
|
||||
|
||||
if (operation.GetSource(srcIndex++) is not AstOperand fieldIndex || fieldIndex.Type != OperandType.Constant)
|
||||
{
|
||||
|
@ -748,28 +675,52 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||
return varName;
|
||||
}
|
||||
|
||||
private static string GetSamplerName(ShaderProperties resourceDefinitions, AstTextureOperation texOp, string indexExpr)
|
||||
private static string GetBindlessSampler(CodeGenContext context, SamplerType type, string bindlessHandle)
|
||||
{
|
||||
string name = resourceDefinitions.Textures[texOp.Binding].Name;
|
||||
string samplerType = type.ToGlslSamplerType();
|
||||
|
||||
if (texOp.Type.HasFlag(SamplerType.Indexed))
|
||||
if (context.TargetApi == TargetApi.Vulkan)
|
||||
{
|
||||
name = $"{name}[{indexExpr}]";
|
||||
string textureIndex = $"{HelperFunctionNames.GetBindlessTextureIndexVk}({bindlessHandle})";
|
||||
string samplerIndex = $"{HelperFunctionNames.GetBindlessSamplerIndexVk}({bindlessHandle})";
|
||||
|
||||
string bindlessTextureArrayName = context.OperandManager.BindlessTextures[type & ~SamplerType.Shadow];
|
||||
string bindlessSamplerArrayName = context.OperandManager.BindlessTextures[SamplerType.None];
|
||||
|
||||
return $"{samplerType}({bindlessTextureArrayName}[{textureIndex}], {bindlessSamplerArrayName}[{samplerIndex}])";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"{samplerType}({HelperFunctionNames.GetBindlessHandle}({bindlessHandle}))";
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
private static string GetBindlessImage(CodeGenContext context, SamplerType type, AggregateType componentType, string bindlessHandle)
|
||||
{
|
||||
string imageType = type.ToGlslImageType(componentType);
|
||||
|
||||
if (context.TargetApi == TargetApi.Vulkan)
|
||||
{
|
||||
string textureIndex = $"{HelperFunctionNames.GetBindlessTextureIndexVk}({bindlessHandle})";
|
||||
|
||||
string bindlessImageArrayName = context.OperandManager.BindlessImages[type];
|
||||
|
||||
return $"{bindlessImageArrayName}[{textureIndex}]";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"{imageType}({HelperFunctionNames.GetBindlessHandle}({bindlessHandle}))";
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetImageName(ShaderProperties resourceDefinitions, AstTextureOperation texOp, string indexExpr)
|
||||
private static string GetSamplerName(ShaderProperties resourceDefinitions, AstTextureOperation texOp)
|
||||
{
|
||||
string name = resourceDefinitions.Images[texOp.Binding].Name;
|
||||
|
||||
if (texOp.Type.HasFlag(SamplerType.Indexed))
|
||||
{
|
||||
name = $"{name}[{indexExpr}]";
|
||||
return resourceDefinitions.Textures[SetBindingPair.Unpack(texOp.Binding)].Name;
|
||||
}
|
||||
|
||||
return name;
|
||||
private static string GetImageName(ShaderProperties resourceDefinitions, AstTextureOperation texOp)
|
||||
{
|
||||
return resourceDefinitions.Images[SetBindingPair.Unpack(texOp.Binding)].Name;
|
||||
}
|
||||
|
||||
private static string GetMask(int index)
|
||||
|
|
|
@ -13,9 +13,15 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||
{
|
||||
private readonly Dictionary<AstOperand, string> _locals;
|
||||
|
||||
public Dictionary<SamplerType, string> BindlessTextures { get; }
|
||||
public Dictionary<SamplerType, string> BindlessImages { get; }
|
||||
|
||||
public OperandManager()
|
||||
{
|
||||
_locals = new Dictionary<AstOperand, string>();
|
||||
_locals = new();
|
||||
|
||||
BindlessTextures = new();
|
||||
BindlessImages = new();
|
||||
}
|
||||
|
||||
public string DeclareLocal(AstOperand operand)
|
||||
|
@ -68,8 +74,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||
}
|
||||
|
||||
BufferDefinition buffer = operation.StorageKind == StorageKind.ConstantBuffer
|
||||
? context.Properties.ConstantBuffers[bindingIndex.Value]
|
||||
: context.Properties.StorageBuffers[bindingIndex.Value];
|
||||
? context.Properties.ConstantBuffers[SetBindingPair.Unpack(bindingIndex.Value)]
|
||||
: context.Properties.StorageBuffers[SetBindingPair.Unpack(bindingIndex.Value)];
|
||||
StructureField field = buffer.Type.Fields[fieldIndex.Value];
|
||||
|
||||
return field.Type & AggregateType.ElementTypeMask;
|
||||
|
|
|
@ -29,15 +29,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||
|
||||
public Dictionary<int, Instruction> ConstantBuffers { get; } = new();
|
||||
public Dictionary<int, Instruction> StorageBuffers { get; } = new();
|
||||
|
||||
public Dictionary<int, Instruction> LocalMemories { get; } = new();
|
||||
public Dictionary<int, Instruction> SharedMemories { get; } = new();
|
||||
|
||||
public Dictionary<int, SamplerType> SamplersTypes { get; } = new();
|
||||
public Dictionary<int, (Instruction, Instruction, Instruction)> Samplers { get; } = new();
|
||||
public Dictionary<int, (Instruction, Instruction)> Images { get; } = new();
|
||||
|
||||
public Dictionary<IoDefinition, Instruction> Inputs { get; } = new();
|
||||
public Dictionary<SamplerType, (Instruction, Instruction, Instruction, Instruction)> BindlessTextures { get; } = new();
|
||||
public Dictionary<SamplerType, (Instruction, Instruction, Instruction)> BindlessImages { get; } = new();
|
||||
public Dictionary<IoDefinition, Instruction> Outputs { get; } = new();
|
||||
public Dictionary<IoDefinition, Instruction> InputsPerPatch { get; } = new();
|
||||
public Dictionary<IoDefinition, Instruction> OutputsPerPatch { get; } = new();
|
||||
|
|
|
@ -43,12 +43,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||
|
||||
public static void DeclareAll(CodeGenContext context, StructuredProgramInfo info)
|
||||
{
|
||||
DeclareConstantBuffers(context, context.Properties.ConstantBuffers.Values);
|
||||
DeclareStorageBuffers(context, context.Properties.StorageBuffers.Values);
|
||||
DeclareConstantBuffers(context, context.Properties.ConstantBuffers);
|
||||
DeclareStorageBuffers(context, context.Properties.StorageBuffers);
|
||||
DeclareMemories(context, context.Properties.LocalMemories, context.LocalMemories, StorageClass.Private);
|
||||
DeclareMemories(context, context.Properties.SharedMemories, context.SharedMemories, StorageClass.Workgroup);
|
||||
DeclareSamplers(context, context.Properties.Textures.Values);
|
||||
DeclareImages(context, context.Properties.Images.Values);
|
||||
DeclareSamplers(context, context.Properties.Textures);
|
||||
DeclareImages(context, context.Properties.Images);
|
||||
DeclareInputsAndOutputs(context, info);
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||
Dictionary<int, SpvInstruction> dict,
|
||||
StorageClass storage)
|
||||
{
|
||||
foreach ((int id, MemoryDefinition memory) in memories)
|
||||
foreach ((int id, var memory) in memories)
|
||||
{
|
||||
var pointerType = context.TypePointer(storage, context.GetType(memory.Type, memory.ArrayLength));
|
||||
var variable = context.Variable(pointerType, storage);
|
||||
|
@ -69,21 +69,21 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||
}
|
||||
}
|
||||
|
||||
private static void DeclareConstantBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers)
|
||||
private static void DeclareConstantBuffers(CodeGenContext context, IReadOnlyDictionary<SetBindingPair, BufferDefinition> buffers)
|
||||
{
|
||||
DeclareBuffers(context, buffers, isBuffer: false);
|
||||
}
|
||||
|
||||
private static void DeclareStorageBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers)
|
||||
private static void DeclareStorageBuffers(CodeGenContext context, IReadOnlyDictionary<SetBindingPair, BufferDefinition> buffers)
|
||||
{
|
||||
DeclareBuffers(context, buffers, isBuffer: true);
|
||||
}
|
||||
|
||||
private static void DeclareBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers, bool isBuffer)
|
||||
private static void DeclareBuffers(CodeGenContext context, IReadOnlyDictionary<SetBindingPair, BufferDefinition> buffers, bool isBuffer)
|
||||
{
|
||||
HashSet<SpvInstruction> decoratedTypes = new();
|
||||
|
||||
foreach (BufferDefinition buffer in buffers)
|
||||
foreach ((SetBindingPair sbPair, var buffer) in buffers)
|
||||
{
|
||||
int setIndex = context.TargetApi == TargetApi.Vulkan ? buffer.Set : 0;
|
||||
int alignment = buffer.Layout == BufferLayout.Std140 ? 16 : 4;
|
||||
|
@ -145,32 +145,29 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||
|
||||
if (isBuffer)
|
||||
{
|
||||
context.StorageBuffers.Add(buffer.Binding, variable);
|
||||
context.StorageBuffers.Add(sbPair.Pack(), variable);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.ConstantBuffers.Add(buffer.Binding, variable);
|
||||
context.ConstantBuffers.Add(sbPair.Pack(), variable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DeclareSamplers(CodeGenContext context, IEnumerable<TextureDefinition> samplers)
|
||||
private static void DeclareSamplers(CodeGenContext context, IReadOnlyDictionary<SetBindingPair, TextureDefinition> samplers)
|
||||
{
|
||||
foreach (var sampler in samplers)
|
||||
foreach ((SetBindingPair sbPair, var sampler) in samplers)
|
||||
{
|
||||
int setIndex = context.TargetApi == TargetApi.Vulkan ? sampler.Set : 0;
|
||||
|
||||
var dim = (sampler.Type & SamplerType.Mask) switch
|
||||
{
|
||||
SamplerType.Texture1D => Dim.Dim1D,
|
||||
SamplerType.Texture2D => Dim.Dim2D,
|
||||
SamplerType.Texture3D => Dim.Dim3D,
|
||||
SamplerType.TextureCube => Dim.Cube,
|
||||
SamplerType.TextureBuffer => Dim.Buffer,
|
||||
_ => throw new InvalidOperationException($"Invalid sampler type \"{sampler.Type & SamplerType.Mask}\"."),
|
||||
};
|
||||
SpvInstruction imageType;
|
||||
SpvInstruction sampledImageType;
|
||||
|
||||
var imageType = context.TypeImage(
|
||||
if (sampler.Type != SamplerType.None)
|
||||
{
|
||||
var dim = GetDim(sampler.Type);
|
||||
|
||||
imageType = context.TypeImage(
|
||||
context.TypeFP32(),
|
||||
dim,
|
||||
sampler.Type.HasFlag(SamplerType.Shadow),
|
||||
|
@ -179,12 +176,40 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||
1,
|
||||
ImageFormat.Unknown);
|
||||
|
||||
var sampledImageType = context.TypeSampledImage(imageType);
|
||||
var sampledImagePointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageType);
|
||||
var sampledImageVariable = context.Variable(sampledImagePointerType, StorageClass.UniformConstant);
|
||||
sampledImageType = context.TypeSampledImage(imageType);
|
||||
}
|
||||
else
|
||||
{
|
||||
imageType = sampledImageType = context.TypeSampler();
|
||||
}
|
||||
|
||||
context.Samplers.Add(sampler.Binding, (imageType, sampledImageType, sampledImageVariable));
|
||||
context.SamplersTypes.Add(sampler.Binding, sampler.Type);
|
||||
var imageTypeForSampler = sampler.Type.HasFlag(SamplerType.Separate) ? imageType : sampledImageType;
|
||||
var sampledImagePointerType = context.TypePointer(StorageClass.UniformConstant, imageTypeForSampler);
|
||||
|
||||
SpvInstruction sampledImageArrayPointerType = sampledImagePointerType;
|
||||
|
||||
if (sampler.ArraySize == 0)
|
||||
{
|
||||
var sampledImageArrayType = context.TypeRuntimeArray(imageTypeForSampler);
|
||||
sampledImageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageArrayType);
|
||||
}
|
||||
else if (sampler.ArraySize != 1)
|
||||
{
|
||||
var sampledImageArrayType = context.TypeArray(imageTypeForSampler, context.Constant(context.TypeU32(), sampler.ArraySize));
|
||||
sampledImageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageArrayType);
|
||||
}
|
||||
|
||||
var sampledImageVariable = context.Variable(sampledImageArrayPointerType, StorageClass.UniformConstant);
|
||||
|
||||
if (sampler.ArraySize != 1)
|
||||
{
|
||||
context.BindlessTextures[sampler.Type & ~(SamplerType.Shadow | SamplerType.Separate)] = (imageType, sampledImageType, sampledImagePointerType, sampledImageVariable);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Samplers.Add(sbPair.Pack(), (imageType, sampledImageType, sampledImageVariable));
|
||||
context.SamplersTypes.Add(sbPair.Pack(), sampler.Type);
|
||||
}
|
||||
|
||||
context.Name(sampledImageVariable, sampler.Name);
|
||||
context.Decorate(sampledImageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex);
|
||||
|
@ -193,9 +218,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||
}
|
||||
}
|
||||
|
||||
private static void DeclareImages(CodeGenContext context, IEnumerable<TextureDefinition> images)
|
||||
private static void DeclareImages(CodeGenContext context, IReadOnlyDictionary<SetBindingPair, TextureDefinition> images)
|
||||
{
|
||||
foreach (var image in images)
|
||||
foreach ((SetBindingPair sbPair, var image) in images)
|
||||
{
|
||||
int setIndex = context.TargetApi == TargetApi.Vulkan ? image.Set : 0;
|
||||
|
||||
|
@ -211,9 +236,30 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||
GetImageFormat(image.Format));
|
||||
|
||||
var imagePointerType = context.TypePointer(StorageClass.UniformConstant, imageType);
|
||||
var imageVariable = context.Variable(imagePointerType, StorageClass.UniformConstant);
|
||||
|
||||
context.Images.Add(image.Binding, (imageType, imageVariable));
|
||||
SpvInstruction imageArrayPointerType = imagePointerType;
|
||||
|
||||
if (image.ArraySize == 0)
|
||||
{
|
||||
var imageArrayType = context.TypeRuntimeArray(imageType);
|
||||
imageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, imageArrayType);
|
||||
}
|
||||
else if (image.ArraySize != 1)
|
||||
{
|
||||
var imageArrayType = context.TypeArray(imageType, context.Constant(context.TypeU32(), image.ArraySize));
|
||||
imageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, imageArrayType);
|
||||
}
|
||||
|
||||
var imageVariable = context.Variable(imageArrayPointerType, StorageClass.UniformConstant);
|
||||
|
||||
if (image.ArraySize != 1)
|
||||
{
|
||||
context.BindlessImages[image.Type] = (imageType, imagePointerType, imageVariable);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Images.Add(sbPair.Pack(), (imageType, imageVariable));
|
||||
}
|
||||
|
||||
context.Name(imageVariable, image.Name);
|
||||
context.Decorate(imageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex);
|
||||
|
|
|
@ -595,31 +595,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||
|
||||
var componentType = texOp.Format.GetComponentType();
|
||||
|
||||
// TODO: Bindless texture support. For now we just return 0/do nothing.
|
||||
if (isBindless)
|
||||
{
|
||||
return new OperationResult(componentType, componentType switch
|
||||
{
|
||||
AggregateType.S32 => context.Constant(context.TypeS32(), 0),
|
||||
AggregateType.U32 => context.Constant(context.TypeU32(), 0u),
|
||||
_ => context.Constant(context.TypeFP32(), 0f),
|
||||
});
|
||||
}
|
||||
|
||||
bool isArray = (texOp.Type & SamplerType.Array) != 0;
|
||||
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
|
||||
|
||||
int srcIndex = isBindless ? 1 : 0;
|
||||
int srcIndex = 0;
|
||||
|
||||
SpvInstruction Src(AggregateType type)
|
||||
{
|
||||
return context.Get(type, texOp.GetSource(srcIndex++));
|
||||
}
|
||||
|
||||
if (isIndexed)
|
||||
{
|
||||
Src(AggregateType.S32);
|
||||
}
|
||||
SpvInstruction bindlessHandle = isBindless ? Src(AggregateType.S32) : null;
|
||||
|
||||
int coordsCount = texOp.Type.GetDimensions();
|
||||
|
||||
|
@ -646,9 +631,19 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||
|
||||
SpvInstruction value = Src(componentType);
|
||||
|
||||
(var imageType, var imageVariable) = context.Images[texOp.Binding];
|
||||
SpvInstruction imageVariable;
|
||||
|
||||
context.Load(imageType, imageVariable);
|
||||
if (isBindless)
|
||||
{
|
||||
(_, var bindlessImagePointerType, var bindlessImageVariable) = context.BindlessImages[texOp.Type];
|
||||
|
||||
var imageIndex = GenerateBindlessTextureHandleToIndex(context, bindlessHandle);
|
||||
imageVariable = context.AccessChain(bindlessImagePointerType, bindlessImageVariable, imageIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
(_, imageVariable) = context.Images[texOp.Binding];
|
||||
}
|
||||
|
||||
SpvInstruction resultType = context.GetType(componentType);
|
||||
SpvInstruction imagePointerType = context.TypePointer(StorageClass.Image, resultType);
|
||||
|
@ -687,26 +682,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||
|
||||
var componentType = texOp.Format.GetComponentType();
|
||||
|
||||
// TODO: Bindless texture support. For now we just return 0/do nothing.
|
||||
if (isBindless)
|
||||
{
|
||||
return GetZeroOperationResult(context, texOp, componentType, isVector: true);
|
||||
}
|
||||
|
||||
bool isArray = (texOp.Type & SamplerType.Array) != 0;
|
||||
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
|
||||
|
||||
int srcIndex = isBindless ? 1 : 0;
|
||||
int srcIndex = 0;
|
||||
|
||||
SpvInstruction Src(AggregateType type)
|
||||
{
|
||||
return context.Get(type, texOp.GetSource(srcIndex++));
|
||||
}
|
||||
|
||||
if (isIndexed)
|
||||
{
|
||||
Src(AggregateType.S32);
|
||||
}
|
||||
SpvInstruction bindlessHandle = isBindless ? Src(AggregateType.S32) : null;
|
||||
|
||||
int coordsCount = texOp.Type.GetDimensions();
|
||||
|
||||
|
@ -731,9 +716,27 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||
pCoords = Src(AggregateType.S32);
|
||||
}
|
||||
|
||||
SpvInstruction bindlessIndex;
|
||||
SpvInstruction image;
|
||||
|
||||
if (isBindless)
|
||||
{
|
||||
(var imageType, var imagePointerType, var imageVariable) = context.BindlessImages[texOp.Type];
|
||||
|
||||
var imageIndex = GenerateBindlessTextureHandleToIndex(context, bindlessHandle);
|
||||
var imagePointer = context.AccessChain(imagePointerType, imageVariable, imageIndex);
|
||||
|
||||
bindlessIndex = imageIndex;
|
||||
image = context.Load(imageType, imagePointer);
|
||||
}
|
||||
else
|
||||
{
|
||||
(var imageType, var imageVariable) = context.Images[texOp.Binding];
|
||||
|
||||
var image = context.Load(imageType, imageVariable);
|
||||
bindlessIndex = null;
|
||||
image = context.Load(imageType, imageVariable);
|
||||
}
|
||||
|
||||
var imageComponentType = context.GetType(componentType);
|
||||
var swizzledResultType = texOp.GetVectorType(componentType);
|
||||
|
||||
|
@ -749,26 +752,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||
|
||||
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
|
||||
|
||||
// TODO: Bindless texture support. For now we just return 0/do nothing.
|
||||
if (isBindless)
|
||||
{
|
||||
return OperationResult.Invalid;
|
||||
}
|
||||
|
||||
bool isArray = (texOp.Type & SamplerType.Array) != 0;
|
||||
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
|
||||
|
||||
int srcIndex = isBindless ? 1 : 0;
|
||||
int srcIndex = 0;
|
||||
|
||||
SpvInstruction Src(AggregateType type)
|
||||
{
|
||||
return context.Get(type, texOp.GetSource(srcIndex++));
|
||||
}
|
||||
|
||||
if (isIndexed)
|
||||
{
|
||||
Src(AggregateType.S32);
|
||||
}
|
||||
SpvInstruction bindlessHandle = isBindless ? Src(AggregateType.S32) : null;
|
||||
|
||||
int coordsCount = texOp.Type.GetDimensions();
|
||||
|
||||
|
@ -818,9 +811,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||
|
||||
var texel = context.CompositeConstruct(context.TypeVector(context.GetType(componentType), ComponentsCount), cElems);
|
||||
|
||||
SpvInstruction image;
|
||||
|
||||
if (isBindless)
|
||||
{
|
||||
(var imageType, var imagePointerType, var imageVariable) = context.BindlessImages[texOp.Type];
|
||||
|
||||
var imageIndex = GenerateBindlessTextureHandleToIndex(context, bindlessHandle);
|
||||
var imagePointer = context.AccessChain(imagePointerType, imageVariable, imageIndex);
|
||||
|
||||
image = context.Load(imageType, imagePointer);
|
||||
}
|
||||
else
|
||||
{
|
||||
(var imageType, var imageVariable) = context.Images[texOp.Binding];
|
||||
|
||||
var image = context.Load(imageType, imageVariable);
|
||||
image = context.Load(imageType, imageVariable);
|
||||
}
|
||||
|
||||
context.ImageWrite(image, pCoords, texel, ImageOperandsMask.MaskNone);
|
||||
|
||||
|
@ -856,14 +863,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||
|
||||
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
|
||||
|
||||
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
|
||||
|
||||
// TODO: Bindless texture support. For now we just return 0.
|
||||
if (isBindless)
|
||||
{
|
||||
return new OperationResult(AggregateType.S32, context.Constant(context.TypeS32(), 0));
|
||||
}
|
||||
|
||||
int srcIndex = 0;
|
||||
|
||||
SpvInstruction Src(AggregateType type)
|
||||
|
@ -871,10 +870,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||
return context.Get(type, texOp.GetSource(srcIndex++));
|
||||
}
|
||||
|
||||
if (isIndexed)
|
||||
{
|
||||
Src(AggregateType.S32);
|
||||
}
|
||||
SpvInstruction bindlessHandle = isBindless ? Src(AggregateType.S32) : null;
|
||||
|
||||
int pCount = texOp.Type.GetDimensions();
|
||||
|
||||
|
@ -897,9 +893,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||
pCoords = Src(AggregateType.FP32);
|
||||
}
|
||||
|
||||
SpvInstruction image;
|
||||
|
||||
if (isBindless)
|
||||
{
|
||||
(var imageType, _, var imagePointerType, var imageVariable) = context.BindlessTextures[texOp.Type & ~SamplerType.Shadow];
|
||||
|
||||
var imageIndex = GenerateBindlessTextureHandleToIndex(context, bindlessHandle);
|
||||
var imagePointer = context.AccessChain(imagePointerType, imageVariable, imageIndex);
|
||||
|
||||
image = context.Load(imageType, imagePointer);
|
||||
}
|
||||
else
|
||||
{
|
||||
(_, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding];
|
||||
|
||||
var image = context.Load(sampledImageType, sampledImageVariable);
|
||||
image = context.Load(sampledImageType, sampledImageVariable);
|
||||
}
|
||||
|
||||
var resultType = context.TypeVector(context.TypeFP32(), 2);
|
||||
var packed = context.ImageQueryLod(resultType, image, pCoords);
|
||||
|
@ -1192,29 +1202,19 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||
bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0;
|
||||
|
||||
bool isArray = (texOp.Type & SamplerType.Array) != 0;
|
||||
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
|
||||
bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0;
|
||||
bool isShadow = (texOp.Type & SamplerType.Shadow) != 0;
|
||||
|
||||
bool colorIsVector = isGather || !isShadow;
|
||||
|
||||
// TODO: Bindless texture support. For now we just return 0.
|
||||
if (isBindless)
|
||||
{
|
||||
return GetZeroOperationResult(context, texOp, AggregateType.FP32, colorIsVector);
|
||||
}
|
||||
|
||||
int srcIndex = isBindless ? 1 : 0;
|
||||
int srcIndex = 0;
|
||||
|
||||
SpvInstruction Src(AggregateType type)
|
||||
{
|
||||
return context.Get(type, texOp.GetSource(srcIndex++));
|
||||
}
|
||||
|
||||
if (isIndexed)
|
||||
{
|
||||
Src(AggregateType.S32);
|
||||
}
|
||||
SpvInstruction bindlessHandle = isBindless ? Src(AggregateType.S32) : null;
|
||||
|
||||
int coordsCount = texOp.Type.GetDimensions();
|
||||
|
||||
|
@ -1262,6 +1262,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||
}
|
||||
|
||||
SpvInstruction pCoords = AssemblePVector(pCount);
|
||||
SpvInstruction bindlessIndex = isBindless ? GenerateBindlessTextureHandleToIndex(context, bindlessHandle) : null;
|
||||
|
||||
SpvInstruction AssembleDerivativesVector(int count)
|
||||
{
|
||||
|
@ -1421,9 +1422,33 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||
|
||||
var resultType = colorIsVector ? context.TypeVector(context.TypeFP32(), 4) : context.TypeFP32();
|
||||
|
||||
(var imageType, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding];
|
||||
SpvInstruction imageType;
|
||||
SpvInstruction image;
|
||||
|
||||
var image = context.Load(sampledImageType, sampledImageVariable);
|
||||
if (isBindless)
|
||||
{
|
||||
(imageType, var sampledImageType, var imagePointerType, var imageVariable) = context.BindlessTextures[texOp.Type & ~SamplerType.Shadow];
|
||||
|
||||
var imageIndex = GenerateBindlessTextureHandleToIndex(context, bindlessHandle);
|
||||
var imagePointer = context.AccessChain(imagePointerType, imageVariable, imageIndex);
|
||||
|
||||
image = context.Load(imageType, imagePointer);
|
||||
|
||||
(_, var samplerType, var samplerPointerType, var bindlessSamplerArray) = context.BindlessTextures[SamplerType.None];
|
||||
|
||||
var samplerIndex = GenerateBindlessSamplerHandleToIndex(context, bindlessHandle);
|
||||
var samplerPointer = context.AccessChain(samplerPointerType, bindlessSamplerArray, samplerIndex);
|
||||
|
||||
var sampler = context.Load(samplerType, samplerPointer);
|
||||
|
||||
image = context.SampledImage(sampledImageType, image, sampler);
|
||||
}
|
||||
else
|
||||
{
|
||||
(imageType, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding];
|
||||
|
||||
image = context.Load(sampledImageType, sampledImageVariable);
|
||||
}
|
||||
|
||||
if (intCoords)
|
||||
{
|
||||
|
@ -1493,13 +1518,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||
return new OperationResult(AggregateType.S32, context.Constant(context.TypeS32(), 0));
|
||||
}
|
||||
|
||||
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
|
||||
|
||||
if (isIndexed)
|
||||
{
|
||||
context.GetS32(texOp.GetSource(0));
|
||||
}
|
||||
|
||||
(var imageType, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding];
|
||||
|
||||
var image = context.Load(sampledImageType, sampledImageVariable);
|
||||
|
@ -1516,22 +1534,30 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||
|
||||
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
|
||||
|
||||
// TODO: Bindless texture support. For now we just return 0.
|
||||
SpvInstruction bindlessIndex;
|
||||
SpvInstruction imageType;
|
||||
SpvInstruction image;
|
||||
|
||||
if (isBindless)
|
||||
{
|
||||
return new OperationResult(AggregateType.S32, context.Constant(context.TypeS32(), 0));
|
||||
SpvInstruction bindlessHandle = context.GetS32(operation.GetSource(0));
|
||||
|
||||
(imageType, _, var imagePointerType, var imageVariable) = context.BindlessTextures[texOp.Type & ~SamplerType.Shadow];
|
||||
|
||||
var imageIndex = GenerateBindlessTextureHandleToIndex(context, bindlessHandle);
|
||||
var imagePointer = context.AccessChain(imagePointerType, imageVariable, imageIndex);
|
||||
|
||||
bindlessIndex = imageIndex;
|
||||
image = context.Load(imageType, imagePointer);
|
||||
}
|
||||
|
||||
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
|
||||
|
||||
if (isIndexed)
|
||||
else
|
||||
{
|
||||
context.GetS32(texOp.GetSource(0));
|
||||
(imageType, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding];
|
||||
|
||||
bindlessIndex = null;
|
||||
image = context.Load(sampledImageType, sampledImageVariable);
|
||||
}
|
||||
|
||||
(var imageType, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding];
|
||||
|
||||
var image = context.Load(sampledImageType, sampledImageVariable);
|
||||
image = context.Image(imageType, image);
|
||||
|
||||
if (texOp.Index == 3)
|
||||
|
@ -1540,7 +1566,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||
}
|
||||
else
|
||||
{
|
||||
var type = context.SamplersTypes[texOp.Binding];
|
||||
var type = isBindless ? texOp.Type : context.SamplersTypes[texOp.Binding];
|
||||
bool hasLod = !type.HasFlag(SamplerType.Multisample) && type != SamplerType.TextureBuffer;
|
||||
|
||||
int dimensions = (type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : type.GetDimensions();
|
||||
|
@ -1556,7 +1582,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||
|
||||
if (hasLod)
|
||||
{
|
||||
int lodSrcIndex = isBindless || isIndexed ? 1 : 0;
|
||||
int lodSrcIndex = isBindless ? 1 : 0;
|
||||
var lod = context.GetS32(operation.GetSource(lodSrcIndex));
|
||||
result = context.ImageQuerySizeLod(resultType, image, lod);
|
||||
}
|
||||
|
@ -1638,6 +1664,51 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||
return new OperationResult(AggregateType.Bool, result);
|
||||
}
|
||||
|
||||
private static SpvInstruction GenerateBindlessTextureHandleToIndex(CodeGenContext context, SpvInstruction bindlessHandle)
|
||||
{
|
||||
var bindlessTable = context.ConstantBuffers[SetBindingPair.Pack(Constants.BindlessTextureSetIndex, Constants.BindlessTableBinding)];
|
||||
var id = context.BitwiseAnd(context.TypeS32(), bindlessHandle, context.Constant(context.TypeS32(), 0xfffff));
|
||||
var tableIndex = context.ShiftRightArithmetic(context.TypeS32(), id, context.Constant(context.TypeS32(), 8));
|
||||
|
||||
var pointerUint = context.TypePointer(StorageClass.Uniform, context.TypeU32());
|
||||
var baseIndex = context.AccessChain(
|
||||
pointerUint,
|
||||
bindlessTable,
|
||||
context.Constant(context.TypeS32(), 0),
|
||||
tableIndex,
|
||||
context.Constant(context.TypeU32(), 0));
|
||||
|
||||
baseIndex = context.Load(context.TypeU32(), baseIndex);
|
||||
|
||||
var idLow = context.BitwiseAnd(context.TypeS32(), id, context.Constant(context.TypeS32(), 0xff));
|
||||
var index = context.BitwiseOr(context.TypeU32(), baseIndex, idLow);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
private static SpvInstruction GenerateBindlessSamplerHandleToIndex(CodeGenContext context, SpvInstruction bindlessHandle)
|
||||
{
|
||||
var bindlessTable = context.ConstantBuffers[SetBindingPair.Pack(Constants.BindlessTextureSetIndex, Constants.BindlessTableBinding)];
|
||||
var idHigh = context.ShiftRightArithmetic(context.TypeS32(), bindlessHandle, context.Constant(context.TypeS32(), 20));
|
||||
var id = context.BitwiseAnd(context.TypeS32(), idHigh, context.Constant(context.TypeS32(), 0xfff));
|
||||
var tableIndex = context.ShiftRightArithmetic(context.TypeS32(), id, context.Constant(context.TypeS32(), 8));
|
||||
|
||||
var pointerUint = context.TypePointer(StorageClass.Uniform, context.TypeU32());
|
||||
var baseIndex = context.AccessChain(
|
||||
pointerUint,
|
||||
bindlessTable,
|
||||
context.Constant(context.TypeS32(), 0),
|
||||
tableIndex,
|
||||
context.Constant(context.TypeU32(), 1));
|
||||
|
||||
baseIndex = context.Load(context.TypeU32(), baseIndex);
|
||||
|
||||
var idLow = context.BitwiseAnd(context.TypeS32(), id, context.Constant(context.TypeS32(), 0xff));
|
||||
var index = context.BitwiseOr(context.TypeU32(), baseIndex, idLow);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
private static OperationResult GenerateCompare(
|
||||
CodeGenContext context,
|
||||
AstOperation operation,
|
||||
|
@ -1746,8 +1817,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||
}
|
||||
|
||||
BufferDefinition buffer = storageKind == StorageKind.ConstantBuffer
|
||||
? context.Properties.ConstantBuffers[bindingIndex.Value]
|
||||
: context.Properties.StorageBuffers[bindingIndex.Value];
|
||||
? context.Properties.ConstantBuffers[SetBindingPair.Unpack(bindingIndex.Value)]
|
||||
: context.Properties.StorageBuffers[SetBindingPair.Unpack(bindingIndex.Value)];
|
||||
StructureField field = buffer.Type.Fields[fieldIndex.Value];
|
||||
|
||||
storageClass = StorageClass.Uniform;
|
||||
|
|
|
@ -105,6 +105,17 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||
context.AddCapability(Capability.ShaderViewportMaskNV);
|
||||
}
|
||||
|
||||
if (parameters.BindlessTextureFlags != BindlessTextureFlags.None)
|
||||
{
|
||||
context.AddExtension("SPV_EXT_descriptor_indexing");
|
||||
context.AddCapability(Capability.Sampled1D);
|
||||
context.AddCapability(Capability.Image1D);
|
||||
context.AddCapability(Capability.SampledCubeArray);
|
||||
context.AddCapability(Capability.ImageCubeArray);
|
||||
context.AddCapability(Capability.StorageImageMultisample);
|
||||
context.AddCapability(Capability.RuntimeDescriptorArray);
|
||||
}
|
||||
|
||||
if ((info.HelperFunctionsMask & NeedsInvocationIdMask) != 0)
|
||||
{
|
||||
info.IoDefinitions.Add(new IoDefinition(StorageKind.Input, IoVariable.SubgroupLaneId));
|
||||
|
|
|
@ -10,5 +10,16 @@ namespace Ryujinx.Graphics.Shader
|
|||
public const int NvnBaseVertexByteOffset = 0x640;
|
||||
public const int NvnBaseInstanceByteOffset = 0x644;
|
||||
public const int NvnDrawIndexByteOffset = 0x648;
|
||||
|
||||
public const int VkConstantBufferSetIndex = 0;
|
||||
public const int VkStorageBufferSetIndex = 1;
|
||||
public const int VkTextureSetIndex = 2;
|
||||
public const int VkImageSetIndex = 3;
|
||||
|
||||
// Bindless emulation.
|
||||
|
||||
public const int BindlessTextureSetIndex = 4;
|
||||
public const int BindlessTableBinding = 0;
|
||||
public const int BindlessScalesBinding = 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -404,6 +404,15 @@ namespace Ryujinx.Graphics.Shader
|
|||
return SamplerType.Texture2D;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries the number of the constant buffer where the texture handles are located.
|
||||
/// </summary>
|
||||
/// <returns>Constant buffer where the texture handles are located</returns>
|
||||
int QueryTextureBufferIndex()
|
||||
{
|
||||
return 2; // NVN default.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries texture coordinate normalization information.
|
||||
/// </summary>
|
||||
|
|
|
@ -161,5 +161,13 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
|||
inst &= Instruction.Mask;
|
||||
return inst == Instruction.Lod || inst == Instruction.TextureQuerySamples || inst == Instruction.TextureQuerySize;
|
||||
}
|
||||
|
||||
public static bool IsImage(this Instruction inst)
|
||||
{
|
||||
inst &= Instruction.Mask;
|
||||
return inst == Instruction.ImageLoad ||
|
||||
inst == Instruction.ImageStore ||
|
||||
inst == Instruction.ImageAtomic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -144,6 +144,26 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
|||
}
|
||||
}
|
||||
|
||||
public void PrependSources(Operand[] operands)
|
||||
{
|
||||
int endIndex = operands.Length;
|
||||
|
||||
Array.Resize(ref _sources, endIndex + _sources.Length);
|
||||
Array.Copy(_sources, 0, _sources, endIndex, _sources.Length - endIndex);
|
||||
|
||||
for (int index = 0; index < operands.Length; index++)
|
||||
{
|
||||
Operand source = operands[index];
|
||||
|
||||
if (source.Type == OperandType.LocalVariable)
|
||||
{
|
||||
source.UseOps.Add(this);
|
||||
}
|
||||
|
||||
_sources[index] = source;
|
||||
}
|
||||
}
|
||||
|
||||
public void AppendSources(Operand[] operands)
|
||||
{
|
||||
int startIndex = _sources.Length;
|
||||
|
|
|
@ -26,19 +26,11 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
|||
Binding = binding;
|
||||
}
|
||||
|
||||
public void TurnIntoIndexed(int binding)
|
||||
{
|
||||
Type |= SamplerType.Indexed;
|
||||
Flags &= ~TextureFlags.Bindless;
|
||||
Binding = binding;
|
||||
}
|
||||
|
||||
public void SetBinding(int binding)
|
||||
{
|
||||
if ((Flags & TextureFlags.Bindless) != 0)
|
||||
{
|
||||
Flags &= ~TextureFlags.Bindless;
|
||||
|
||||
RemoveSource(0);
|
||||
}
|
||||
|
||||
|
@ -49,5 +41,14 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
|||
{
|
||||
Flags |= TextureFlags.LodLevel;
|
||||
}
|
||||
|
||||
public void TurnIntoBindless(Operand handle)
|
||||
{
|
||||
if ((Flags & TextureFlags.Bindless) == 0)
|
||||
{
|
||||
Flags |= TextureFlags.Bindless;
|
||||
PrependSources(new Operand[] { handle });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,9 +9,9 @@ namespace Ryujinx.Graphics.Shader
|
|||
public readonly int ReservedTextures { get; }
|
||||
public readonly int ReservedImages { get; }
|
||||
|
||||
public ResourceReservationCounts(bool isTransformFeedbackEmulated, bool vertexAsCompute)
|
||||
public ResourceReservationCounts(TargetApi targetApi, bool isTransformFeedbackEmulated, bool vertexAsCompute)
|
||||
{
|
||||
ResourceReservations reservations = new(isTransformFeedbackEmulated, vertexAsCompute);
|
||||
ResourceReservations reservations = new(targetApi, isTransformFeedbackEmulated, vertexAsCompute);
|
||||
|
||||
ReservedConstantBuffers = reservations.ReservedConstantBuffers;
|
||||
ReservedStorageBuffers = reservations.ReservedStorageBuffers;
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\GetBindlessHandle.glsl" />
|
||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\GetBindlessHandleVk.glsl" />
|
||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\MultiplyHighS32.glsl" />
|
||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\MultiplyHighU32.glsl" />
|
||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\SwizzleAdd.glsl" />
|
||||
|
|
|
@ -16,9 +16,9 @@ namespace Ryujinx.Graphics.Shader
|
|||
Mask = 0xff,
|
||||
|
||||
Array = 1 << 8,
|
||||
Indexed = 1 << 9,
|
||||
Multisample = 1 << 10,
|
||||
Shadow = 1 << 11,
|
||||
Multisample = 1 << 9,
|
||||
Shadow = 1 << 10,
|
||||
Separate = 1 << 11,
|
||||
}
|
||||
|
||||
static class SamplerTypeExtensions
|
||||
|
@ -40,6 +40,7 @@ namespace Ryujinx.Graphics.Shader
|
|||
{
|
||||
string typeName = (type & SamplerType.Mask) switch
|
||||
{
|
||||
SamplerType.None => "sampler",
|
||||
SamplerType.Texture1D => "sampler1D",
|
||||
SamplerType.TextureBuffer => "samplerBuffer",
|
||||
SamplerType.Texture2D => "sampler2D",
|
||||
|
@ -66,6 +67,31 @@ namespace Ryujinx.Graphics.Shader
|
|||
return typeName;
|
||||
}
|
||||
|
||||
public static string ToGlslTextureType(this SamplerType type)
|
||||
{
|
||||
string typeName = (type & SamplerType.Mask) switch
|
||||
{
|
||||
SamplerType.Texture1D => "texture1D",
|
||||
SamplerType.TextureBuffer => "textureBuffer",
|
||||
SamplerType.Texture2D => "texture2D",
|
||||
SamplerType.Texture3D => "texture3D",
|
||||
SamplerType.TextureCube => "textureCube",
|
||||
_ => throw new ArgumentException($"Invalid texture type \"{type}\"."),
|
||||
};
|
||||
|
||||
if ((type & SamplerType.Multisample) != 0)
|
||||
{
|
||||
typeName += "MS";
|
||||
}
|
||||
|
||||
if ((type & SamplerType.Array) != 0)
|
||||
{
|
||||
typeName += "Array";
|
||||
}
|
||||
|
||||
return typeName;
|
||||
}
|
||||
|
||||
public static string ToGlslImageType(this SamplerType type, AggregateType componentType)
|
||||
{
|
||||
string typeName = (type & SamplerType.Mask) switch
|
||||
|
|
|
@ -11,6 +11,7 @@ namespace Ryujinx.Graphics.Shader
|
|||
public ReadOnlyCollection<TextureDescriptor> Images { get; }
|
||||
|
||||
public ShaderStage Stage { get; }
|
||||
public BindlessTextureFlags BindlessTextureFlags { get; }
|
||||
public int GeometryVerticesPerPrimitive { get; }
|
||||
public int GeometryMaxOutputVertices { get; }
|
||||
public int ThreadsPerInputPrimitive { get; }
|
||||
|
@ -27,6 +28,7 @@ namespace Ryujinx.Graphics.Shader
|
|||
TextureDescriptor[] textures,
|
||||
TextureDescriptor[] images,
|
||||
ShaderStage stage,
|
||||
BindlessTextureFlags bindlessTextureFlags,
|
||||
int geometryVerticesPerPrimitive,
|
||||
int geometryMaxOutputVertices,
|
||||
int threadsPerInputPrimitive,
|
||||
|
@ -43,6 +45,7 @@ namespace Ryujinx.Graphics.Shader
|
|||
Images = Array.AsReadOnly(images);
|
||||
|
||||
Stage = stage;
|
||||
BindlessTextureFlags = bindlessTextureFlags;
|
||||
GeometryVerticesPerPrimitive = geometryVerticesPerPrimitive;
|
||||
GeometryMaxOutputVertices = geometryMaxOutputVertices;
|
||||
ThreadsPerInputPrimitive = threadsPerInputPrimitive;
|
||||
|
|
46
src/Ryujinx.Graphics.Shader/StructuredIr/SetBindingPair.cs
Normal file
46
src/Ryujinx.Graphics.Shader/StructuredIr/SetBindingPair.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,48 +4,48 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
|||
{
|
||||
class ShaderProperties
|
||||
{
|
||||
private readonly Dictionary<int, BufferDefinition> _constantBuffers;
|
||||
private readonly Dictionary<int, BufferDefinition> _storageBuffers;
|
||||
private readonly Dictionary<int, TextureDefinition> _textures;
|
||||
private readonly Dictionary<int, TextureDefinition> _images;
|
||||
private readonly Dictionary<SetBindingPair, BufferDefinition> _constantBuffers;
|
||||
private readonly Dictionary<SetBindingPair, BufferDefinition> _storageBuffers;
|
||||
private readonly Dictionary<SetBindingPair, TextureDefinition> _textures;
|
||||
private readonly Dictionary<SetBindingPair, TextureDefinition> _images;
|
||||
private readonly Dictionary<int, MemoryDefinition> _localMemories;
|
||||
private readonly Dictionary<int, MemoryDefinition> _sharedMemories;
|
||||
|
||||
public IReadOnlyDictionary<int, BufferDefinition> ConstantBuffers => _constantBuffers;
|
||||
public IReadOnlyDictionary<int, BufferDefinition> StorageBuffers => _storageBuffers;
|
||||
public IReadOnlyDictionary<int, TextureDefinition> Textures => _textures;
|
||||
public IReadOnlyDictionary<int, TextureDefinition> Images => _images;
|
||||
public IReadOnlyDictionary<SetBindingPair, BufferDefinition> ConstantBuffers => _constantBuffers;
|
||||
public IReadOnlyDictionary<SetBindingPair, BufferDefinition> StorageBuffers => _storageBuffers;
|
||||
public IReadOnlyDictionary<SetBindingPair, TextureDefinition> Textures => _textures;
|
||||
public IReadOnlyDictionary<SetBindingPair, TextureDefinition> Images => _images;
|
||||
public IReadOnlyDictionary<int, MemoryDefinition> LocalMemories => _localMemories;
|
||||
public IReadOnlyDictionary<int, MemoryDefinition> SharedMemories => _sharedMemories;
|
||||
|
||||
public ShaderProperties()
|
||||
{
|
||||
_constantBuffers = new Dictionary<int, BufferDefinition>();
|
||||
_storageBuffers = new Dictionary<int, BufferDefinition>();
|
||||
_textures = new Dictionary<int, TextureDefinition>();
|
||||
_images = new Dictionary<int, TextureDefinition>();
|
||||
_constantBuffers = new Dictionary<SetBindingPair, BufferDefinition>();
|
||||
_storageBuffers = new Dictionary<SetBindingPair, BufferDefinition>();
|
||||
_textures = new Dictionary<SetBindingPair, TextureDefinition>();
|
||||
_images = new Dictionary<SetBindingPair, TextureDefinition>();
|
||||
_localMemories = new Dictionary<int, MemoryDefinition>();
|
||||
_sharedMemories = new Dictionary<int, MemoryDefinition>();
|
||||
}
|
||||
|
||||
public void AddOrUpdateConstantBuffer(BufferDefinition definition)
|
||||
{
|
||||
_constantBuffers[definition.Binding] = definition;
|
||||
_constantBuffers[new(definition.Set, definition.Binding)] = definition;
|
||||
}
|
||||
|
||||
public void AddOrUpdateStorageBuffer(BufferDefinition definition)
|
||||
{
|
||||
_storageBuffers[definition.Binding] = definition;
|
||||
_storageBuffers[new(definition.Set, definition.Binding)] = definition;
|
||||
}
|
||||
|
||||
public void AddOrUpdateTexture(TextureDefinition definition)
|
||||
{
|
||||
_textures[definition.Binding] = definition;
|
||||
_textures[new(definition.Set, definition.Binding)] = definition;
|
||||
}
|
||||
|
||||
public void AddOrUpdateImage(TextureDefinition definition)
|
||||
{
|
||||
_images[definition.Binding] = definition;
|
||||
_images[new(definition.Set, definition.Binding)] = definition;
|
||||
}
|
||||
|
||||
public int AddLocalMemory(MemoryDefinition definition)
|
||||
|
|
|
@ -8,8 +8,9 @@ namespace Ryujinx.Graphics.Shader
|
|||
public SamplerType Type { get; }
|
||||
public TextureFormat Format { get; }
|
||||
public TextureUsageFlags Flags { get; }
|
||||
public int ArraySize { get; }
|
||||
|
||||
public TextureDefinition(int set, int binding, string name, SamplerType type, TextureFormat format, TextureUsageFlags flags)
|
||||
public TextureDefinition(int set, int binding, string name, SamplerType type, TextureFormat format, TextureUsageFlags flags, int arraySize = 1)
|
||||
{
|
||||
Set = set;
|
||||
Binding = binding;
|
||||
|
@ -17,11 +18,12 @@ namespace Ryujinx.Graphics.Shader
|
|||
Type = type;
|
||||
Format = format;
|
||||
Flags = flags;
|
||||
ArraySize = arraySize;
|
||||
}
|
||||
|
||||
public TextureDefinition SetFlag(TextureUsageFlags flag)
|
||||
{
|
||||
return new TextureDefinition(Set, Binding, Name, Type, Format, Flags | flag);
|
||||
return new TextureDefinition(Set, Binding, Name, Type, Format, Flags | flag, ArraySize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
|
@ -13,6 +14,11 @@ namespace Ryujinx.Graphics.Shader
|
|||
|
||||
public static class TextureHandle
|
||||
{
|
||||
// Maximum is actually 32 for OpenGL, but we reserve 2 textures for bindless emulation.
|
||||
private const int MaxTexturesPerStageGl = 30;
|
||||
private const int MaxTexturesPerStageVk = 64;
|
||||
public const int NvnTextureBufferIndex = 2;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int PackSlots(int cbufSlot0, int cbufSlot1)
|
||||
{
|
||||
|
@ -120,5 +126,11 @@ namespace Ryujinx.Graphics.Shader
|
|||
|
||||
return handle;
|
||||
}
|
||||
|
||||
public static int GetMaxTexturesPerStage(TargetApi api)
|
||||
{
|
||||
// TODO: Query that value from the backend since those limits are not really fixed per API.
|
||||
return api == TargetApi.Vulkan ? MaxTexturesPerStageVk : MaxTexturesPerStageGl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection.Metadata;
|
||||
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
|
||||
|
||||
namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
@ -77,7 +78,9 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
HelperFunctionName.ConvertDoubleToFloat => GenerateConvertDoubleToFloatFunction(),
|
||||
HelperFunctionName.ConvertFloatToDouble => GenerateConvertFloatToDoubleFunction(),
|
||||
HelperFunctionName.TexelFetchScale => GenerateTexelFetchScaleFunction(),
|
||||
HelperFunctionName.TexelFetchScaleBindless => GenerateTexelFetchScaleBindlessFunction(),
|
||||
HelperFunctionName.TextureSizeUnscale => GenerateTextureSizeUnscaleFunction(),
|
||||
HelperFunctionName.TextureSizeUnscaleBindless => GenerateTextureSizeUnscaleBindlessFunction(),
|
||||
_ => throw new ArgumentException($"Invalid function name {functionName}"),
|
||||
};
|
||||
}
|
||||
|
@ -412,6 +415,29 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "TexelFetchScale", true, inArgumentsCount, 0);
|
||||
}
|
||||
|
||||
private Function GenerateTexelFetchScaleBindlessFunction()
|
||||
{
|
||||
EmitterContext context = new();
|
||||
|
||||
Operand input = Argument(0);
|
||||
Operand nvHandle = Argument(1);
|
||||
|
||||
Operand scale = GetBindlessScale(context, nvHandle);
|
||||
|
||||
Operand scaleIsOne = context.FPCompareEqual(scale, ConstF(1f));
|
||||
Operand lblScaleNotOne = Label();
|
||||
|
||||
context.BranchIfFalse(lblScaleNotOne, scaleIsOne);
|
||||
context.Return(input);
|
||||
context.MarkLabel(lblScaleNotOne);
|
||||
|
||||
Operand inputScaled2 = context.FPMultiply(context.IConvertS32ToFP32(input), scale);
|
||||
|
||||
context.Return(context.FP32ConvertToS32(inputScaled2));
|
||||
|
||||
return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "TexelFetchScaleBindless", true, 2, 0);
|
||||
}
|
||||
|
||||
private Function GenerateTextureSizeUnscaleFunction()
|
||||
{
|
||||
EmitterContext context = new();
|
||||
|
@ -436,6 +462,29 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "TextureSizeUnscale", true, 2, 0);
|
||||
}
|
||||
|
||||
private Function GenerateTextureSizeUnscaleBindlessFunction()
|
||||
{
|
||||
EmitterContext context = new();
|
||||
|
||||
Operand input = Argument(0);
|
||||
Operand nvHandle = Argument(1);
|
||||
|
||||
Operand scale = GetBindlessScale(context, nvHandle);
|
||||
|
||||
Operand scaleIsOne = context.FPCompareEqual(scale, ConstF(1f));
|
||||
Operand lblScaleNotOne = Label();
|
||||
|
||||
context.BranchIfFalse(lblScaleNotOne, scaleIsOne);
|
||||
context.Return(input);
|
||||
context.MarkLabel(lblScaleNotOne);
|
||||
|
||||
Operand inputUnscaled = context.FPDivide(context.IConvertS32ToFP32(input), scale);
|
||||
|
||||
context.Return(context.FP32ConvertToS32(inputUnscaled));
|
||||
|
||||
return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "TextureSizeUnscaleBindless", true, 2, 0);
|
||||
}
|
||||
|
||||
private Operand GetScaleIndex(EmitterContext context, Operand index)
|
||||
{
|
||||
switch (_stage)
|
||||
|
@ -448,6 +497,19 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
}
|
||||
}
|
||||
|
||||
private Operand GetBindlessScale(EmitterContext context, Operand nvHandle)
|
||||
{
|
||||
int bindlessTableBinding = SetBindingPair.Pack(Constants.BindlessTextureSetIndex, Constants.BindlessTableBinding);
|
||||
int bindlessScalesBinding = SetBindingPair.Pack(Constants.BindlessTextureSetIndex, Constants.BindlessScalesBinding);
|
||||
|
||||
Operand id = context.BitwiseAnd(nvHandle, Const(0xfffff));
|
||||
Operand tableIndex = context.ShiftRightU32(id, Const(8));
|
||||
Operand scaleIndex = context.Load(StorageKind.ConstantBuffer, bindlessTableBinding, Const(0), tableIndex, Const(0));
|
||||
Operand scale = context.Load(StorageKind.ConstantBuffer, bindlessScalesBinding, Const(0), scaleIndex);
|
||||
|
||||
return scale;
|
||||
}
|
||||
|
||||
public static Operand GetBitOffset(EmitterContext context, Operand offset)
|
||||
{
|
||||
return context.ShiftLeft(context.BitwiseAnd(offset, Const(3)), Const(3));
|
||||
|
|
|
@ -15,6 +15,8 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
ShuffleUp,
|
||||
ShuffleXor,
|
||||
TexelFetchScale,
|
||||
TexelFetchScaleBindless,
|
||||
TextureSizeUnscale,
|
||||
TextureSizeUnscaleBindless,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,11 +16,34 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
|||
GlobalToStorage.RunPass(context.Hfm, context.Blocks, context.ResourceManager, context.GpuAccessor, context.TargetLanguage);
|
||||
|
||||
bool hostSupportsShaderFloat64 = context.GpuAccessor.QueryHostSupportsShaderFloat64();
|
||||
int textureBufferIndex = context.GpuAccessor.QueryTextureBufferIndex();
|
||||
|
||||
// Those passes are looking for specific patterns and only needs to run once.
|
||||
for (int blkIndex = 0; blkIndex < context.Blocks.Length; blkIndex++)
|
||||
{
|
||||
BindlessToIndexed.RunPass(context.Blocks[blkIndex], context.ResourceManager);
|
||||
if (textureBufferIndex == TextureHandle.NvnTextureBufferIndex)
|
||||
{
|
||||
BasicBlock block = context.Blocks[blkIndex];
|
||||
|
||||
for (LinkedListNode<INode> node = block.Operations.First; node != null; node = node.Next)
|
||||
{
|
||||
for (int index = 0; index < node.Value.SourcesCount; index++)
|
||||
{
|
||||
Operand src = node.Value.GetSource(index);
|
||||
|
||||
// The shader accessing constant buffer 2 is an indication that
|
||||
// the bindless access is for separate texture/sampler combinations.
|
||||
// Bindless elimination should be able to take care of that, but if it doesn't,
|
||||
// we still don't want to use full bindless for those cases
|
||||
if (src.Type == OperandType.ConstantBuffer && src.GetCbufSlot() == textureBufferIndex)
|
||||
{
|
||||
context.BindlessTexturesAllowed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BindlessElimination.RunPass(context.Blocks[blkIndex], context.ResourceManager, context.GpuAccessor);
|
||||
|
||||
// FragmentCoord only exists on fragment shaders, so we don't need to check other stages.
|
||||
|
|
|
@ -14,9 +14,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
private const int DefaultLocalMemorySize = 128;
|
||||
private const int DefaultSharedMemorySize = 4096;
|
||||
|
||||
// TODO: Non-hardcoded array size.
|
||||
public const int SamplerArraySize = 4;
|
||||
|
||||
private static readonly string[] _stagePrefixes = new string[] { "cp", "vp", "tcp", "tep", "gp", "fp" };
|
||||
|
||||
private readonly IGpuAccessor _gpuAccessor;
|
||||
|
@ -28,11 +25,10 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
private uint _sbSlotWritten;
|
||||
|
||||
private readonly Dictionary<int, int> _sbSlots;
|
||||
private readonly Dictionary<int, int> _sbSlotsReverse;
|
||||
|
||||
private readonly HashSet<int> _usedConstantBufferBindings;
|
||||
|
||||
private readonly record struct TextureInfo(int CbufSlot, int Handle, bool Indexed, TextureFormat Format);
|
||||
private readonly record struct TextureInfo(int CbufSlot, int Handle, TextureFormat Format);
|
||||
|
||||
private struct TextureMeta
|
||||
{
|
||||
|
@ -73,7 +69,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
_sbSlotToBindingMap.AsSpan().Fill(-1);
|
||||
|
||||
_sbSlots = new();
|
||||
_sbSlotsReverse = new();
|
||||
|
||||
_usedConstantBufferBindings = new();
|
||||
|
||||
|
@ -147,6 +142,68 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
return Properties.AddLocalMemory(new MemoryDefinition(name, type, arrayLength));
|
||||
}
|
||||
|
||||
public void EnsureBindlessBinding(TargetApi targetApi, SamplerType samplerType, bool isImage)
|
||||
{
|
||||
if (targetApi == TargetApi.Vulkan)
|
||||
{
|
||||
Properties.AddOrUpdateConstantBuffer(new BufferDefinition(
|
||||
BufferLayout.Std140,
|
||||
Constants.BindlessTextureSetIndex,
|
||||
Constants.BindlessTableBinding,
|
||||
"bindless_table",
|
||||
new StructureType(new[] { new StructureField(AggregateType.Array | AggregateType.Vector2 | AggregateType.U32, "table", 0x1000) })));
|
||||
|
||||
Properties.AddOrUpdateStorageBuffer(new BufferDefinition(
|
||||
BufferLayout.Std430,
|
||||
Constants.BindlessTextureSetIndex,
|
||||
Constants.BindlessScalesBinding,
|
||||
"bindless_scales",
|
||||
new StructureType(new[] { new StructureField(AggregateType.Array | AggregateType.FP32, "scales", 0) })));
|
||||
|
||||
if (isImage)
|
||||
{
|
||||
string name = $"bindless_{samplerType.ToGlslImageType(AggregateType.FP32)}";
|
||||
|
||||
if (samplerType == SamplerType.TextureBuffer)
|
||||
{
|
||||
AddBindlessDefinition(8, 0, name, SamplerType.TextureBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddBindlessDefinition(7, 0, name, samplerType);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string name = $"bindless_{(samplerType & ~SamplerType.Shadow).ToGlslSamplerType()}";
|
||||
|
||||
if (samplerType == SamplerType.TextureBuffer)
|
||||
{
|
||||
AddBindlessSeparateDefinition(5, 0, name, SamplerType.TextureBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddBindlessSeparateDefinition(4, 2, name, samplerType);
|
||||
}
|
||||
|
||||
// Sampler
|
||||
AddBindlessDefinition(6, 0, "bindless_samplers", SamplerType.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddBindlessDefinition(int set, int binding, string name, SamplerType samplerType)
|
||||
{
|
||||
TextureDefinition definition = new(set, binding, name, samplerType, TextureFormat.Unknown, TextureUsageFlags.None, 0);
|
||||
Properties.AddOrUpdateTexture(definition);
|
||||
}
|
||||
|
||||
private void AddBindlessSeparateDefinition(int set, int binding, string name, SamplerType samplerType)
|
||||
{
|
||||
samplerType = (samplerType & ~SamplerType.Shadow) | SamplerType.Separate;
|
||||
AddBindlessDefinition(set, binding, name, samplerType);
|
||||
}
|
||||
|
||||
public int GetConstantBufferBinding(int slot)
|
||||
{
|
||||
int binding = _cbSlotToBindingMap[slot];
|
||||
|
@ -158,7 +215,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
AddNewConstantBuffer(binding, $"{_stagePrefix}_c{slotNumber}");
|
||||
}
|
||||
|
||||
return binding;
|
||||
return SetBindingPair.Pack(Constants.VkConstantBufferSetIndex, binding);
|
||||
}
|
||||
|
||||
public bool TryGetStorageBufferBinding(int sbCbSlot, int sbCbOffset, bool write, out int binding)
|
||||
|
@ -166,6 +223,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
if (!TryGetSbSlot((byte)sbCbSlot, (ushort)sbCbOffset, out int slot))
|
||||
{
|
||||
binding = 0;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -179,6 +237,8 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
AddNewStorageBuffer(binding, $"{_stagePrefix}_s{slotNumber}");
|
||||
}
|
||||
|
||||
binding = SetBindingPair.Pack(Constants.VkStorageBufferSetIndex, binding);
|
||||
|
||||
if (write)
|
||||
{
|
||||
_sbSlotWritten |= 1u << slot;
|
||||
|
@ -201,7 +261,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
}
|
||||
|
||||
_sbSlots.Add(key, slot);
|
||||
_sbSlotsReverse.Add(slot, key);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -211,7 +270,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
{
|
||||
for (slot = 0; slot < _cbSlotToBindingMap.Length; slot++)
|
||||
{
|
||||
if (_cbSlotToBindingMap[slot] == binding)
|
||||
if (SetBindingPair.Pack(Constants.VkConstantBufferSetIndex, _cbSlotToBindingMap[slot]) == binding)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -245,7 +304,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
|
||||
_gpuAccessor.RegisterTexture(handle, cbufSlot);
|
||||
|
||||
return binding;
|
||||
return SetBindingPair.Pack(isImage ? Constants.VkImageSetIndex : Constants.VkTextureSetIndex, binding);
|
||||
}
|
||||
|
||||
private int GetTextureOrImageBinding(
|
||||
|
@ -260,7 +319,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
bool coherent)
|
||||
{
|
||||
var dimensions = type.GetDimensions();
|
||||
var isIndexed = type.HasFlag(SamplerType.Indexed);
|
||||
var dict = isImage ? _usedImages : _usedTextures;
|
||||
|
||||
var usageFlags = TextureUsageFlags.None;
|
||||
|
@ -269,7 +327,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
{
|
||||
usageFlags |= TextureUsageFlags.NeedsScaleValue;
|
||||
|
||||
var canScale = _stage.SupportsRenderScale() && !isIndexed && !write && dimensions == 2;
|
||||
var canScale = _stage.SupportsRenderScale() && !write && dimensions == 2;
|
||||
|
||||
if (!canScale)
|
||||
{
|
||||
|
@ -289,17 +347,12 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
usageFlags |= TextureUsageFlags.ImageCoherent;
|
||||
}
|
||||
|
||||
int arraySize = isIndexed ? SamplerArraySize : 1;
|
||||
int firstBinding = -1;
|
||||
|
||||
for (int layer = 0; layer < arraySize; layer++)
|
||||
{
|
||||
var info = new TextureInfo(cbufSlot, handle + layer * 2, isIndexed, format);
|
||||
var info = new TextureInfo(cbufSlot, handle, format);
|
||||
var meta = new TextureMeta()
|
||||
{
|
||||
AccurateType = accurateType,
|
||||
Type = type,
|
||||
UsageFlags = usageFlags,
|
||||
UsageFlags = usageFlags
|
||||
};
|
||||
|
||||
int binding;
|
||||
|
@ -352,13 +405,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
Properties.AddOrUpdateTexture(definition);
|
||||
}
|
||||
|
||||
if (layer == 0)
|
||||
{
|
||||
firstBinding = binding;
|
||||
}
|
||||
}
|
||||
|
||||
return firstBinding;
|
||||
return binding;
|
||||
}
|
||||
|
||||
private static TextureMeta MergeTextureMeta(TextureMeta meta, TextureMeta existingMeta)
|
||||
|
@ -385,7 +432,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
|
||||
foreach ((TextureInfo info, TextureMeta meta) in _usedTextures)
|
||||
{
|
||||
if (meta.Binding == binding)
|
||||
if (SetBindingPair.Pack(Constants.VkTextureSetIndex, meta.Binding) == binding)
|
||||
{
|
||||
selectedInfo = info;
|
||||
selectedMeta = meta;
|
||||
|
@ -399,8 +446,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
selectedMeta.UsageFlags |= TextureUsageFlags.NeedsScaleValue;
|
||||
|
||||
var dimensions = type.GetDimensions();
|
||||
var isIndexed = type.HasFlag(SamplerType.Indexed);
|
||||
var canScale = _stage.SupportsRenderScale() && !isIndexed && dimensions == 2;
|
||||
var canScale = _stage.SupportsRenderScale() && dimensions == 2;
|
||||
|
||||
if (!canScale)
|
||||
{
|
||||
|
@ -428,7 +474,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
{
|
||||
int binding = _cbSlotToBindingMap[slot];
|
||||
|
||||
if (binding >= 0 && _usedConstantBufferBindings.Contains(binding))
|
||||
if (binding >= 0 && _usedConstantBufferBindings.Contains(SetBindingPair.Pack(Constants.VkConstantBufferSetIndex, binding)))
|
||||
{
|
||||
descriptors[descriptorIndex++] = new BufferDescriptor(binding, slot);
|
||||
}
|
||||
|
@ -502,7 +548,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
{
|
||||
foreach ((TextureInfo info, TextureMeta meta) in _usedTextures)
|
||||
{
|
||||
if (meta.Binding == binding)
|
||||
if (SetBindingPair.Pack(Constants.VkTextureSetIndex, meta.Binding) == binding)
|
||||
{
|
||||
cbufSlot = info.CbufSlot;
|
||||
handle = info.Handle;
|
||||
|
@ -516,19 +562,19 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
return false;
|
||||
}
|
||||
|
||||
private static int FindDescriptorIndex(TextureDescriptor[] array, int binding)
|
||||
private static int FindDescriptorIndex(TextureDescriptor[] array, int setIndex, int binding)
|
||||
{
|
||||
return Array.FindIndex(array, x => x.Binding == binding);
|
||||
return Array.FindIndex(array, x => SetBindingPair.Pack(setIndex, x.Binding) == binding);
|
||||
}
|
||||
|
||||
public int FindTextureDescriptorIndex(int binding)
|
||||
{
|
||||
return FindDescriptorIndex(GetTextureDescriptors(), binding);
|
||||
return FindDescriptorIndex(GetTextureDescriptors(), Constants.VkTextureSetIndex, binding);
|
||||
}
|
||||
|
||||
public int FindImageDescriptorIndex(int binding)
|
||||
{
|
||||
return FindDescriptorIndex(GetImageDescriptors(), binding);
|
||||
return FindDescriptorIndex(GetImageDescriptors(), Constants.VkImageSetIndex, binding);
|
||||
}
|
||||
|
||||
private void AddNewConstantBuffer(int binding, string name)
|
||||
|
|
|
@ -32,12 +32,12 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
private readonly Dictionary<IoDefinition, int> _offsets;
|
||||
internal IReadOnlyDictionary<IoDefinition, int> Offsets => _offsets;
|
||||
|
||||
internal ResourceReservations(bool isTransformFeedbackEmulated, bool vertexAsCompute)
|
||||
internal ResourceReservations(TargetApi targetApi, bool isTransformFeedbackEmulated, bool vertexAsCompute)
|
||||
{
|
||||
// All stages reserves the first constant buffer binding for the support buffer.
|
||||
ReservedConstantBuffers = 1;
|
||||
ReservedStorageBuffers = 0;
|
||||
ReservedTextures = 0;
|
||||
ReservedTextures = targetApi == TargetApi.OpenGL ? 2 : 0; // Reserve 2 texture bindings on OpenGL for bindless emulation.
|
||||
ReservedImages = 0;
|
||||
|
||||
if (isTransformFeedbackEmulated)
|
||||
|
@ -71,10 +71,11 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
|
||||
internal ResourceReservations(
|
||||
IGpuAccessor gpuAccessor,
|
||||
TargetApi targetApi,
|
||||
bool isTransformFeedbackEmulated,
|
||||
bool vertexAsCompute,
|
||||
IoUsage? vacInput,
|
||||
IoUsage vacOutput) : this(isTransformFeedbackEmulated, vertexAsCompute)
|
||||
IoUsage vacOutput) : this(targetApi, isTransformFeedbackEmulated, vertexAsCompute)
|
||||
{
|
||||
if (vertexAsCompute)
|
||||
{
|
||||
|
|
|
@ -9,9 +9,12 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
public readonly ShaderDefinitions Definitions;
|
||||
public readonly ResourceManager ResourceManager;
|
||||
public readonly IGpuAccessor GpuAccessor;
|
||||
public readonly TargetApi TargetApi;
|
||||
public readonly TargetLanguage TargetLanguage;
|
||||
public readonly ShaderStage Stage;
|
||||
public readonly ref FeatureFlags UsedFeatures;
|
||||
public readonly ref BindlessTextureFlags BindlessTextureFlags;
|
||||
public readonly ref bool BindlessTexturesAllowed;
|
||||
|
||||
public TransformContext(
|
||||
HelperFunctionManager hfm,
|
||||
|
@ -19,18 +22,24 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
ShaderDefinitions definitions,
|
||||
ResourceManager resourceManager,
|
||||
IGpuAccessor gpuAccessor,
|
||||
TargetApi targetApi,
|
||||
TargetLanguage targetLanguage,
|
||||
ShaderStage stage,
|
||||
ref FeatureFlags usedFeatures)
|
||||
ref FeatureFlags usedFeatures,
|
||||
ref BindlessTextureFlags bindlessTextureFlags,
|
||||
ref bool bindlessTexturesAllowed)
|
||||
{
|
||||
Hfm = hfm;
|
||||
Blocks = blocks;
|
||||
Definitions = definitions;
|
||||
ResourceManager = resourceManager;
|
||||
GpuAccessor = gpuAccessor;
|
||||
TargetApi = targetApi;
|
||||
TargetLanguage = targetLanguage;
|
||||
Stage = stage;
|
||||
UsedFeatures = ref usedFeatures;
|
||||
BindlessTextureFlags = ref bindlessTextureFlags;
|
||||
BindlessTexturesAllowed = ref bindlessTexturesAllowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,12 +16,26 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||
{
|
||||
if (node.Value is TextureOperation texOp)
|
||||
{
|
||||
node = InsertTexelFetchScale(context.Hfm, node, context.ResourceManager, context.Stage);
|
||||
node = InsertTextureSizeUnscale(context.Hfm, node, context.ResourceManager, context.Stage);
|
||||
LinkedListNode<INode> prevNode = node;
|
||||
node = TurnIntoBindlessIfExceeding(
|
||||
node,
|
||||
context.ResourceManager,
|
||||
context.TargetApi,
|
||||
ref context.BindlessTextureFlags,
|
||||
context.BindlessTexturesAllowed,
|
||||
context.GpuAccessor.QueryTextureBufferIndex());
|
||||
|
||||
if (prevNode != node)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
node = InsertTexelFetchScale(context.Hfm, node, context.ResourceManager, context.Stage, context.TargetApi);
|
||||
node = InsertTextureSizeUnscale(context.Hfm, node, context.ResourceManager, context.Stage, context.TargetApi);
|
||||
|
||||
if (texOp.Inst == Instruction.TextureSample)
|
||||
{
|
||||
node = InsertCoordNormalization(context.Hfm, node, context.ResourceManager, context.GpuAccessor, context.Stage);
|
||||
node = InsertCoordNormalization(context.Hfm, node, context.ResourceManager, context.GpuAccessor, context.Stage, context.TargetApi);
|
||||
node = InsertCoordGatherBias(node, context.ResourceManager, context.GpuAccessor);
|
||||
node = InsertConstOffsets(node, context.GpuAccessor, context.Stage);
|
||||
|
||||
|
@ -39,31 +53,41 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||
HelperFunctionManager hfm,
|
||||
LinkedListNode<INode> node,
|
||||
ResourceManager resourceManager,
|
||||
ShaderStage stage)
|
||||
ShaderStage stage,
|
||||
TargetApi targetApi)
|
||||
{
|
||||
TextureOperation texOp = (TextureOperation)node.Value;
|
||||
|
||||
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
|
||||
bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
|
||||
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
|
||||
|
||||
int coordsCount = texOp.Type.GetDimensions();
|
||||
|
||||
int coordsIndex = isBindless || isIndexed ? 1 : 0;
|
||||
int coordsIndex = isBindless ? 1 : 0;
|
||||
|
||||
bool isImage = IsImageInstructionWithScale(texOp.Inst);
|
||||
|
||||
if ((texOp.Inst == Instruction.TextureSample || isImage) &&
|
||||
(intCoords || isImage) &&
|
||||
!isBindless &&
|
||||
!isIndexed &&
|
||||
(!isBindless || targetApi == TargetApi.Vulkan) && // TODO: OpenGL support.
|
||||
stage.SupportsRenderScale() &&
|
||||
TypeSupportsScale(texOp.Type))
|
||||
{
|
||||
int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TexelFetchScale);
|
||||
int samplerIndex = isImage
|
||||
? resourceManager.GetTextureDescriptors().Length + resourceManager.FindImageDescriptorIndex(texOp.Binding)
|
||||
: resourceManager.FindTextureDescriptorIndex(texOp.Binding);
|
||||
int functionId;
|
||||
Operand samplerIndex;
|
||||
|
||||
if (isBindless)
|
||||
{
|
||||
functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TexelFetchScaleBindless);
|
||||
samplerIndex = texOp.GetSource(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TexelFetchScale);
|
||||
samplerIndex = isImage
|
||||
? Const(resourceManager.GetTextureDescriptors().Length + resourceManager.FindImageDescriptorIndex(texOp.Binding))
|
||||
: Const(resourceManager.FindTextureDescriptorIndex(texOp.Binding));
|
||||
}
|
||||
|
||||
for (int index = 0; index < coordsCount; index++)
|
||||
{
|
||||
|
@ -72,11 +96,11 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||
|
||||
if (stage == ShaderStage.Fragment)
|
||||
{
|
||||
callArgs = new Operand[] { Const(functionId), texOp.GetSource(coordsIndex + index), Const(samplerIndex), Const(index) };
|
||||
callArgs = new Operand[] { Const(functionId), texOp.GetSource(coordsIndex + index), samplerIndex, Const(index) };
|
||||
}
|
||||
else
|
||||
{
|
||||
callArgs = new Operand[] { Const(functionId), texOp.GetSource(coordsIndex + index), Const(samplerIndex) };
|
||||
callArgs = new Operand[] { Const(functionId), texOp.GetSource(coordsIndex + index), samplerIndex };
|
||||
}
|
||||
|
||||
node.List.AddBefore(node, new Operation(Instruction.Call, 0, scaledCoord, callArgs));
|
||||
|
@ -92,22 +116,32 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||
HelperFunctionManager hfm,
|
||||
LinkedListNode<INode> node,
|
||||
ResourceManager resourceManager,
|
||||
ShaderStage stage)
|
||||
ShaderStage stage,
|
||||
TargetApi targetApi)
|
||||
{
|
||||
TextureOperation texOp = (TextureOperation)node.Value;
|
||||
|
||||
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
|
||||
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
|
||||
|
||||
if (texOp.Inst == Instruction.TextureQuerySize &&
|
||||
texOp.Index < 2 &&
|
||||
!isBindless &&
|
||||
!isIndexed &&
|
||||
(!isBindless || targetApi == TargetApi.Vulkan) && // TODO: OpenGL support.
|
||||
stage.SupportsRenderScale() &&
|
||||
TypeSupportsScale(texOp.Type))
|
||||
{
|
||||
int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TextureSizeUnscale);
|
||||
int samplerIndex = resourceManager.FindTextureDescriptorIndex(texOp.Binding);
|
||||
int functionId;
|
||||
Operand samplerIndex;
|
||||
|
||||
if (isBindless)
|
||||
{
|
||||
functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TextureSizeUnscaleBindless);
|
||||
samplerIndex = texOp.GetSource(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TextureSizeUnscale);
|
||||
samplerIndex = Const(resourceManager.FindTextureDescriptorIndex(texOp.Binding));
|
||||
}
|
||||
|
||||
for (int index = texOp.DestsCount - 1; index >= 0; index--)
|
||||
{
|
||||
|
@ -128,7 +162,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||
}
|
||||
}
|
||||
|
||||
Operand[] callArgs = new Operand[] { Const(functionId), dest, Const(samplerIndex) };
|
||||
Operand[] callArgs = new Operand[] { Const(functionId), dest, samplerIndex };
|
||||
|
||||
node.List.AddAfter(node, new Operation(Instruction.Call, 0, unscaledSize, callArgs));
|
||||
}
|
||||
|
@ -142,7 +176,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||
LinkedListNode<INode> node,
|
||||
ResourceManager resourceManager,
|
||||
IGpuAccessor gpuAccessor,
|
||||
ShaderStage stage)
|
||||
ShaderStage stage,
|
||||
TargetApi targetApi)
|
||||
{
|
||||
// Emulate non-normalized coordinates by normalizing the coordinates on the shader.
|
||||
// Without normalization, the coordinates are expected to the in the [0, W or H] range,
|
||||
|
@ -167,10 +202,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||
return node;
|
||||
}
|
||||
|
||||
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
|
||||
|
||||
int coordsCount = texOp.Type.GetDimensions();
|
||||
int coordsIndex = isBindless || isIndexed ? 1 : 0;
|
||||
int coordsIndex = isBindless ? 1 : 0;
|
||||
|
||||
int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount;
|
||||
|
||||
|
@ -180,7 +213,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||
|
||||
Operand[] texSizeSources;
|
||||
|
||||
if (isBindless || isIndexed)
|
||||
if (isBindless)
|
||||
{
|
||||
texSizeSources = new Operand[] { texOp.GetSource(0), Const(0) };
|
||||
}
|
||||
|
@ -209,7 +242,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||
|
||||
texOp.SetSource(coordsIndex + index, coordNormalized);
|
||||
|
||||
InsertTextureSizeUnscale(hfm, textureSizeNode, resourceManager, stage);
|
||||
InsertTextureSizeUnscale(hfm, textureSizeNode, resourceManager, stage, targetApi);
|
||||
}
|
||||
|
||||
return node;
|
||||
|
@ -234,10 +267,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||
return node;
|
||||
}
|
||||
|
||||
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
|
||||
|
||||
int coordsCount = texOp.Type.GetDimensions();
|
||||
int coordsIndex = isBindless || isIndexed ? 1 : 0;
|
||||
int coordsIndex = isBindless ? 1 : 0;
|
||||
|
||||
int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount;
|
||||
|
||||
|
@ -249,7 +280,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||
|
||||
Operand[] texSizeSources;
|
||||
|
||||
if (isBindless || isIndexed)
|
||||
if (isBindless)
|
||||
{
|
||||
texSizeSources = new Operand[] { texOp.GetSource(0), Const(0) };
|
||||
}
|
||||
|
@ -321,7 +352,6 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||
bool hasLodLevel = (texOp.Flags & TextureFlags.LodLevel) != 0;
|
||||
|
||||
bool isArray = (texOp.Type & SamplerType.Array) != 0;
|
||||
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
|
||||
bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0;
|
||||
bool isShadow = (texOp.Type & SamplerType.Shadow) != 0;
|
||||
|
||||
|
@ -347,7 +377,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||
|
||||
int copyCount = 0;
|
||||
|
||||
if (isBindless || isIndexed)
|
||||
if (isBindless)
|
||||
{
|
||||
copyCount++;
|
||||
}
|
||||
|
@ -424,7 +454,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||
sources[dstIndex++] = texOp.GetSource(srcIndex++);
|
||||
}
|
||||
|
||||
int coordsIndex = isBindless || isIndexed ? 1 : 0;
|
||||
int coordsIndex = isBindless ? 1 : 0;
|
||||
|
||||
int componentIndex = texOp.Index;
|
||||
|
||||
|
@ -435,7 +465,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||
dests[i] = texOp.GetDest(i);
|
||||
}
|
||||
|
||||
Operand bindlessHandle = isBindless || isIndexed ? sources[0] : null;
|
||||
Operand bindlessHandle = isBindless ? sources[0] : null;
|
||||
|
||||
LinkedListNode<INode> oldNode = node;
|
||||
|
||||
|
@ -748,5 +778,113 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||
{
|
||||
return (type & SamplerType.Mask) == SamplerType.Texture2D;
|
||||
}
|
||||
|
||||
private static LinkedListNode<INode> TurnIntoBindlessIfExceeding(
|
||||
LinkedListNode<INode> node,
|
||||
ResourceManager resourceManager,
|
||||
TargetApi targetApi,
|
||||
ref BindlessTextureFlags bindlessTextureFlags,
|
||||
bool bindlessTexturesAllowed,
|
||||
int textureBufferIndex)
|
||||
{
|
||||
if (node.Value is not TextureOperation texOp)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
// If it's already bindless, then we have nothing to do.
|
||||
if (texOp.Flags.HasFlag(TextureFlags.Bindless))
|
||||
{
|
||||
resourceManager.EnsureBindlessBinding(targetApi, texOp.Type, texOp.Inst.IsImage());
|
||||
|
||||
if (IsIndexedAccess(resourceManager, texOp, textureBufferIndex))
|
||||
{
|
||||
bindlessTextureFlags |= BindlessTextureFlags.BindlessNvn;
|
||||
return node;
|
||||
}
|
||||
|
||||
if (bindlessTexturesAllowed)
|
||||
{
|
||||
bindlessTextureFlags |= BindlessTextureFlags.BindlessFull;
|
||||
return node;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set any destination operand to zero and remove the texture access.
|
||||
// This is a case where bindless elimination failed, and we assume
|
||||
// it's too risky to try using full bindless emulation.
|
||||
|
||||
for (int destIndex = 0; destIndex < texOp.DestsCount; destIndex++)
|
||||
{
|
||||
Operand dest = texOp.GetDest(destIndex);
|
||||
node.List.AddBefore(node, new Operation(Instruction.Copy, dest, Const(0)));
|
||||
}
|
||||
|
||||
LinkedListNode<INode> prevNode = node.Previous;
|
||||
node.List.Remove(node);
|
||||
|
||||
return prevNode;
|
||||
}
|
||||
}
|
||||
|
||||
// If the index is within the host API limits, then we don't need to make it bindless.
|
||||
int index = resourceManager.FindTextureDescriptorIndex(texOp.Binding);
|
||||
if (index < TextureHandle.GetMaxTexturesPerStage(targetApi))
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
TextureDescriptor descriptor = resourceManager.GetTextureDescriptors()[index];
|
||||
|
||||
(int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = TextureHandle.UnpackOffsets(descriptor.HandleIndex);
|
||||
(int textureCbufSlot, int samplerCbufSlot) = TextureHandle.UnpackSlots(descriptor.CbufSlot, textureBufferIndex);
|
||||
|
||||
Operand handle = Cbuf(textureCbufSlot, textureWordOffset);
|
||||
|
||||
if (handleType != TextureHandleType.CombinedSampler)
|
||||
{
|
||||
Operand handle2 = Cbuf(samplerCbufSlot, samplerWordOffset);
|
||||
|
||||
if (handleType == TextureHandleType.SeparateSamplerId)
|
||||
{
|
||||
Operand temp = Local();
|
||||
node.List.AddBefore(node, new Operation(Instruction.ShiftLeft, temp, handle2, Const(20)));
|
||||
handle2 = temp;
|
||||
}
|
||||
|
||||
Operand handleCombined = Local();
|
||||
node.List.AddBefore(node, new Operation(Instruction.BitwiseOr, handleCombined, handle, handle2));
|
||||
handle = handleCombined;
|
||||
}
|
||||
|
||||
texOp.TurnIntoBindless(handle);
|
||||
bindlessTextureFlags |= BindlessTextureFlags.BindlessConverted;
|
||||
|
||||
resourceManager.EnsureBindlessBinding(targetApi, texOp.Type, texOp.Inst.IsImage());
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private static bool IsIndexedAccess(ResourceManager resourceManager, TextureOperation texOp, int textureBufferIndex)
|
||||
{
|
||||
// Try to detect a indexed access.
|
||||
// The access is considered indexed if the handle is loaded with a LDC instruction
|
||||
// from the driver reserved constant buffer used for texture handles.
|
||||
if (!(texOp.GetSource(0).AsgOp is Operation handleAsgOp))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (handleAsgOp.Inst != Instruction.Load || handleAsgOp.StorageKind != StorageKind.ConstantBuffer)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Operand ldcSrc0 = handleAsgOp.GetSource(0);
|
||||
|
||||
return ldcSrc0.Type == OperandType.Constant &&
|
||||
resourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int cbSlot) &&
|
||||
cbSlot == textureBufferIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||
return node;
|
||||
}
|
||||
|
||||
BufferDefinition buffer = context.ResourceManager.Properties.ConstantBuffers[bindingIndex.Value];
|
||||
BufferDefinition buffer = context.ResourceManager.Properties.ConstantBuffers[SetBindingPair.Unpack(bindingIndex.Value)];
|
||||
StructureField field = buffer.Type.Fields[fieldIndex.Value];
|
||||
|
||||
int elemCount = (field.Type & AggregateType.ElementCountMask) switch
|
||||
|
|
|
@ -264,6 +264,9 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
|
||||
HelperFunctionManager hfm = new(funcs, Definitions.Stage);
|
||||
|
||||
BindlessTextureFlags bindlessTextureFlags = BindlessTextureFlags.None;
|
||||
bool bindlessTexturesAllowed = true;
|
||||
|
||||
for (int i = 0; i < functions.Length; i++)
|
||||
{
|
||||
var cfg = cfgs[i];
|
||||
|
@ -294,9 +297,12 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
Definitions,
|
||||
resourceManager,
|
||||
GpuAccessor,
|
||||
Options.TargetApi,
|
||||
Options.TargetLanguage,
|
||||
Definitions.Stage,
|
||||
ref usedFeatures);
|
||||
ref usedFeatures,
|
||||
ref bindlessTextureFlags,
|
||||
ref bindlessTexturesAllowed);
|
||||
|
||||
Optimizer.RunPass(context);
|
||||
TransformPasses.RunPass(context);
|
||||
|
@ -312,6 +318,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
Definitions,
|
||||
resourceManager,
|
||||
usedFeatures,
|
||||
bindlessTextureFlags,
|
||||
clipDistancesWritten);
|
||||
}
|
||||
|
||||
|
@ -322,6 +329,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
ShaderDefinitions originalDefinitions,
|
||||
ResourceManager resourceManager,
|
||||
FeatureFlags usedFeatures,
|
||||
BindlessTextureFlags bindlessTextureFlags,
|
||||
byte clipDistancesWritten)
|
||||
{
|
||||
var sInfo = StructuredProgram.MakeStructuredProgram(
|
||||
|
@ -345,6 +353,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
resourceManager.GetTextureDescriptors(),
|
||||
resourceManager.GetImageDescriptors(),
|
||||
originalDefinitions.Stage,
|
||||
bindlessTextureFlags,
|
||||
geometryVerticesPerPrimitive,
|
||||
originalDefinitions.MaxOutputVertices,
|
||||
originalDefinitions.ThreadsPerInputPrimitive,
|
||||
|
@ -365,7 +374,14 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
GpuAccessor.QueryHostSupportsTextureShadowLod(),
|
||||
GpuAccessor.QueryHostSupportsViewportMask());
|
||||
|
||||
var parameters = new CodeGenParameters(attributeUsage, definitions, resourceManager.Properties, hostCapabilities, GpuAccessor, Options.TargetApi);
|
||||
var parameters = new CodeGenParameters(
|
||||
attributeUsage,
|
||||
definitions,
|
||||
resourceManager.Properties,
|
||||
hostCapabilities,
|
||||
GpuAccessor,
|
||||
Options.TargetApi,
|
||||
bindlessTextureFlags);
|
||||
|
||||
return Options.TargetLanguage switch
|
||||
{
|
||||
|
@ -474,7 +490,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
ioUsage = ioUsage.Combine(_vertexOutput);
|
||||
}
|
||||
|
||||
return new ResourceReservations(GpuAccessor, IsTransformFeedbackEmulated, vertexAsCompute: true, _vertexOutput, ioUsage);
|
||||
return new ResourceReservations(GpuAccessor, Options.TargetApi, IsTransformFeedbackEmulated, vertexAsCompute: true, _vertexOutput, ioUsage);
|
||||
}
|
||||
|
||||
public void SetVertexOutputMapForGeometryAsCompute(TranslatorContext vertexContext)
|
||||
|
@ -569,6 +585,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
definitions,
|
||||
resourceManager,
|
||||
FeatureFlags.None,
|
||||
BindlessTextureFlags.None,
|
||||
0);
|
||||
}
|
||||
|
||||
|
@ -665,6 +682,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
definitions,
|
||||
resourceManager,
|
||||
FeatureFlags.RtLayer,
|
||||
BindlessTextureFlags.None,
|
||||
0);
|
||||
}
|
||||
}
|
||||
|
|
360
src/Ryujinx.Graphics.Vulkan/BindlessManager.cs
Normal file
360
src/Ryujinx.Graphics.Vulkan/BindlessManager.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -88,6 +88,22 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
}
|
||||
}
|
||||
|
||||
public unsafe void UpdateImage(int setIndex, int bindingIndex, int elementIndex, DescriptorImageInfo imageInfo, DescriptorType type)
|
||||
{
|
||||
var writeDescriptorSet = new WriteDescriptorSet
|
||||
{
|
||||
SType = StructureType.WriteDescriptorSet,
|
||||
DstSet = _descriptorSets[setIndex],
|
||||
DstBinding = (uint)bindingIndex,
|
||||
DstArrayElement = (uint)elementIndex,
|
||||
DescriptorType = type,
|
||||
DescriptorCount = 1,
|
||||
PImageInfo = &imageInfo
|
||||
};
|
||||
|
||||
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
|
||||
}
|
||||
|
||||
public unsafe void UpdateImages(int setIndex, int baseBinding, ReadOnlySpan<DescriptorImageInfo> imageInfo, DescriptorType type)
|
||||
{
|
||||
if (imageInfo.Length == 0)
|
||||
|
@ -152,7 +168,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
}
|
||||
}
|
||||
|
||||
public unsafe void UpdateBufferImage(int setIndex, int bindingIndex, BufferView texelBufferView, DescriptorType type)
|
||||
public unsafe void UpdateBufferImage(int setIndex, int bindingIndex, int elementIndex, BufferView texelBufferView, DescriptorType type)
|
||||
{
|
||||
if (texelBufferView.Handle != 0UL)
|
||||
{
|
||||
|
@ -161,6 +177,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
SType = StructureType.WriteDescriptorSet,
|
||||
DstSet = _descriptorSets[setIndex],
|
||||
DstBinding = (uint)bindingIndex,
|
||||
DstArrayElement = (uint)elementIndex,
|
||||
DescriptorType = type,
|
||||
DescriptorCount = 1,
|
||||
PTexelBufferView = &texelBufferView,
|
||||
|
|
|
@ -61,6 +61,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
|
||||
private bool _updateDescriptorCacheCbIndex;
|
||||
|
||||
private readonly BindlessManager _bindlessManager;
|
||||
|
||||
public uint BindlessTexturesCount => _bindlessManager.TexturesCount;
|
||||
public uint BindlessSamplersCount => _bindlessManager.SamplersCount;
|
||||
|
||||
[Flags]
|
||||
private enum DirtyFlags
|
||||
{
|
||||
|
@ -69,7 +74,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
Storage = 1 << 1,
|
||||
Texture = 1 << 2,
|
||||
Image = 1 << 3,
|
||||
All = Uniform | Storage | Texture | Image,
|
||||
Bindless = 1 << 4,
|
||||
All = Uniform | Storage | Texture | Image | Bindless,
|
||||
}
|
||||
|
||||
private DirtyFlags _dirty;
|
||||
|
@ -110,6 +116,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
_textures.AsSpan().Fill(initialImageInfo);
|
||||
_images.AsSpan().Fill(initialImageInfo);
|
||||
|
||||
_bindlessManager = new BindlessManager();
|
||||
|
||||
if (gd.Capabilities.SupportsNullDescriptors)
|
||||
{
|
||||
// If null descriptors are supported, we can pass null as the handle.
|
||||
|
@ -405,6 +413,20 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
SignalDirty(DirtyFlags.Uniform);
|
||||
}
|
||||
|
||||
public void SetBindlessTexture(int textureId, ITexture texture, float textureScale)
|
||||
{
|
||||
_bindlessManager.SetBindlessTexture(textureId, texture, textureScale);
|
||||
|
||||
SignalDirty(DirtyFlags.Bindless);
|
||||
}
|
||||
|
||||
public void SetBindlessSampler(int samplerId, ISampler sampler)
|
||||
{
|
||||
_bindlessManager.SetBindlessSampler(samplerId, ((SamplerHolder)sampler)?.GetSampler());
|
||||
|
||||
SignalDirty(DirtyFlags.Bindless);
|
||||
}
|
||||
|
||||
private void SignalDirty(DirtyFlags flag)
|
||||
{
|
||||
_dirty |= flag;
|
||||
|
@ -444,7 +466,21 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
UpdateAndBind(cbs, PipelineBase.ImageSetIndex, pbp);
|
||||
}
|
||||
|
||||
_dirty = DirtyFlags.None;
|
||||
DirtyFlags newFlags = DirtyFlags.None;
|
||||
|
||||
if (_dirty.HasFlag(DirtyFlags.Bindless))
|
||||
{
|
||||
if (_program.HasBindless)
|
||||
{
|
||||
_bindlessManager.UpdateAndBind(_gd, _program, cbs, pbp, _dummySampler);
|
||||
}
|
||||
else
|
||||
{
|
||||
newFlags = DirtyFlags.Bindless;
|
||||
}
|
||||
}
|
||||
|
||||
_dirty = newFlags;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
@ -623,9 +659,12 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
}
|
||||
}
|
||||
|
||||
(uint bindlessTexturesCount, uint bindlessSamplersCount) = GetBindlessCountsForProgram();
|
||||
|
||||
var pipelineLayout = _program.GetPipelineLayout(_gd, bindlessTexturesCount, bindlessSamplersCount);
|
||||
var sets = dsc.GetSets();
|
||||
|
||||
_gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan<uint>.Empty);
|
||||
_gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, pipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan<uint>.Empty);
|
||||
}
|
||||
|
||||
private unsafe void UpdateBuffers(
|
||||
|
@ -640,6 +679,10 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
return;
|
||||
}
|
||||
|
||||
(uint bindlessTexturesCount, uint bindlessSamplersCount) = GetBindlessCountsForProgram();
|
||||
|
||||
var pipelineLayout = _program.GetPipelineLayout(_gd, bindlessTexturesCount, bindlessSamplersCount);
|
||||
|
||||
fixed (DescriptorBufferInfo* pBufferInfo = bufferInfo)
|
||||
{
|
||||
var writeDescriptorSet = new WriteDescriptorSet
|
||||
|
@ -651,7 +694,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
PBufferInfo = pBufferInfo,
|
||||
};
|
||||
|
||||
_gd.PushDescriptorApi.CmdPushDescriptorSet(cbs.CommandBuffer, pbp, _program.PipelineLayout, 0, 1, &writeDescriptorSet);
|
||||
_gd.PushDescriptorApi.CmdPushDescriptorSet(cbs.CommandBuffer, pbp, pipelineLayout, 0, 1, &writeDescriptorSet);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -688,6 +731,16 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
}
|
||||
}
|
||||
|
||||
private (uint, uint) GetBindlessCountsForProgram()
|
||||
{
|
||||
if (_program == null || !_program.HasBindless)
|
||||
{
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
return (BindlessTexturesCount, BindlessSamplersCount);
|
||||
}
|
||||
|
||||
private void Initialize(CommandBufferScoped cbs, int setIndex, DescriptorSetCollection dsc)
|
||||
{
|
||||
// We don't support clearing texture descriptors currently.
|
||||
|
@ -736,6 +789,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
{
|
||||
_dummyTexture.Dispose();
|
||||
_dummySampler.Dispose();
|
||||
|
||||
_bindlessManager.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,11 +19,17 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
class PipelineBase : IDisposable
|
||||
{
|
||||
public const int DescriptorSetLayouts = 4;
|
||||
public const int DescriptorSetLayoutsBindless = 9;
|
||||
|
||||
public const int UniformSetIndex = 0;
|
||||
public const int StorageSetIndex = 1;
|
||||
public const int TextureSetIndex = 2;
|
||||
public const int ImageSetIndex = 3;
|
||||
public const int BindlessTexturesSetIndex = 4;
|
||||
public const int BindlessBufferTextureSetIndex = 5;
|
||||
public const int BindlessSamplersSetIndex = 6;
|
||||
public const int BindlessImagesSetIndex = 7;
|
||||
public const int BindlessBufferImageSetIndex = 8;
|
||||
|
||||
protected readonly VulkanRenderer Gd;
|
||||
protected readonly Device Device;
|
||||
|
@ -714,6 +720,42 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
_tfEnabled = false;
|
||||
}
|
||||
|
||||
public void RegisterBindlessSampler(int samplerId, ISampler sampler)
|
||||
{
|
||||
_descriptorSetUpdater.SetBindlessSampler(samplerId, sampler);
|
||||
|
||||
bool hasBindless = _program?.HasBindless ?? false;
|
||||
uint bindlessSamplersCount = hasBindless ? _descriptorSetUpdater.BindlessSamplersCount : 0;
|
||||
|
||||
if (_newState.BindlessSamplersCount != bindlessSamplersCount)
|
||||
{
|
||||
_newState.BindlessSamplersCount = bindlessSamplersCount;
|
||||
|
||||
SignalStateChange();
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterBindlessTexture(int textureId, ITexture texture, float textureScale)
|
||||
{
|
||||
_descriptorSetUpdater.SetBindlessTexture(textureId, texture, textureScale);
|
||||
|
||||
bool hasBindless = _program?.HasBindless ?? false;
|
||||
uint bindlessTexturesCount = hasBindless ? _descriptorSetUpdater.BindlessTexturesCount : 0;
|
||||
|
||||
if (_newState.BindlessTexturesCount != bindlessTexturesCount)
|
||||
{
|
||||
_newState.BindlessTexturesCount = bindlessTexturesCount;
|
||||
|
||||
SignalStateChange();
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterBindlessTextureAndSampler(int textureId, ITexture texture, float textureScale, int samplerId, ISampler sampler)
|
||||
{
|
||||
RegisterBindlessSampler(samplerId, sampler);
|
||||
RegisterBindlessTexture(textureId, texture, textureScale);
|
||||
}
|
||||
|
||||
public bool IsCommandBufferActive(CommandBuffer cb)
|
||||
{
|
||||
return CommandBuffer.Handle == cb.Handle;
|
||||
|
@ -967,11 +1009,24 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
|
||||
_descriptorSetUpdater.SetProgram(internalProgram);
|
||||
|
||||
_newState.PipelineLayout = internalProgram.PipelineLayout;
|
||||
_newState.StagesCount = (uint)stages.Length;
|
||||
|
||||
stages.CopyTo(_newState.Stages.AsSpan()[..stages.Length]);
|
||||
|
||||
if (internalProgram.HasBindless)
|
||||
{
|
||||
uint bindlessTexturesCount = _descriptorSetUpdater.BindlessTexturesCount;
|
||||
uint bindlessSamplersCount = _descriptorSetUpdater.BindlessSamplersCount;
|
||||
|
||||
_newState.BindlessTexturesCount = bindlessTexturesCount;
|
||||
_newState.BindlessSamplersCount = bindlessSamplersCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
_newState.BindlessTexturesCount = 0;
|
||||
_newState.BindlessSamplersCount = 0;
|
||||
}
|
||||
|
||||
SignalStateChange();
|
||||
|
||||
if (internalProgram.IsCompute)
|
||||
|
|
|
@ -11,12 +11,12 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
private readonly struct PlceKey : IEquatable<PlceKey>
|
||||
{
|
||||
public readonly ReadOnlyCollection<ResourceDescriptorCollection> SetDescriptors;
|
||||
public readonly bool UsePushDescriptors;
|
||||
public readonly PipelineLayoutUsageInfo UsageInfo;
|
||||
|
||||
public PlceKey(ReadOnlyCollection<ResourceDescriptorCollection> setDescriptors, bool usePushDescriptors)
|
||||
public PlceKey(ReadOnlyCollection<ResourceDescriptorCollection> setDescriptors, PipelineLayoutUsageInfo usageInfo)
|
||||
{
|
||||
SetDescriptors = setDescriptors;
|
||||
UsePushDescriptors = usePushDescriptors;
|
||||
UsageInfo = usageInfo;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
|
@ -31,7 +31,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
}
|
||||
}
|
||||
|
||||
hasher.Add(UsePushDescriptors);
|
||||
hasher.Add(UsageInfo);
|
||||
|
||||
return hasher.ToHashCode();
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
}
|
||||
}
|
||||
|
||||
return UsePushDescriptors == other.UsePushDescriptors;
|
||||
return UsageInfo.Equals(other.UsageInfo);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,18 +72,18 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
|
||||
public PipelineLayoutCache()
|
||||
{
|
||||
_plces = new ConcurrentDictionary<PlceKey, PipelineLayoutCacheEntry>();
|
||||
_plces = new();
|
||||
}
|
||||
|
||||
public PipelineLayoutCacheEntry GetOrCreate(
|
||||
VulkanRenderer gd,
|
||||
Device device,
|
||||
ReadOnlyCollection<ResourceDescriptorCollection> setDescriptors,
|
||||
bool usePushDescriptors)
|
||||
PipelineLayoutUsageInfo usageInfo)
|
||||
{
|
||||
var key = new PlceKey(setDescriptors, usePushDescriptors);
|
||||
var key = new PlceKey(setDescriptors, usageInfo);
|
||||
|
||||
return _plces.GetOrAdd(key, newKey => new PipelineLayoutCacheEntry(gd, device, setDescriptors, usePushDescriptors));
|
||||
return _plces.GetOrAdd(key, newKey => new PipelineLayoutCacheEntry(gd, device, setDescriptors, usageInfo));
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
private const uint DefaultTexturePoolCapacity = 128 * DescriptorSetManager.MaxSets;
|
||||
private const uint DefaultImagePoolCapacity = 8 * DescriptorSetManager.MaxSets;
|
||||
|
||||
private const int MaxPoolSizesPerSet = 2;
|
||||
private const int MaxPoolSizesPerSet = 3;
|
||||
|
||||
private readonly VulkanRenderer _gd;
|
||||
private readonly Device _device;
|
||||
|
@ -31,6 +31,9 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
private int _dsLastCbIndex;
|
||||
private int _dsLastSubmissionCount;
|
||||
|
||||
private readonly uint _bindlessTexturesCount;
|
||||
private readonly uint _bindlessSamplersCount;
|
||||
|
||||
private PipelineLayoutCacheEntry(VulkanRenderer gd, Device device, int setsCount)
|
||||
{
|
||||
_gd = gd;
|
||||
|
@ -44,7 +47,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
|
||||
for (int j = 0; j < _dsCache[i].Length; j++)
|
||||
{
|
||||
_dsCache[i][j] = new List<Auto<DescriptorSetCollection>>();
|
||||
_dsCache[i][j] = new();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,9 +58,9 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
VulkanRenderer gd,
|
||||
Device device,
|
||||
ReadOnlyCollection<ResourceDescriptorCollection> setDescriptors,
|
||||
bool usePushDescriptors) : this(gd, device, setDescriptors.Count)
|
||||
PipelineLayoutUsageInfo usageInfo) : this(gd, device, setDescriptors.Count)
|
||||
{
|
||||
(DescriptorSetLayouts, PipelineLayout) = PipelineLayoutFactory.Create(gd, device, setDescriptors, usePushDescriptors);
|
||||
(DescriptorSetLayouts, PipelineLayout) = PipelineLayoutFactory.Create(gd, device, setDescriptors, usageInfo);
|
||||
|
||||
_consumedDescriptorsPerSet = new int[setDescriptors.Count];
|
||||
|
||||
|
@ -72,6 +75,9 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
|
||||
_consumedDescriptorsPerSet[setIndex] = count;
|
||||
}
|
||||
|
||||
_bindlessSamplersCount = usageInfo.BindlessSamplersCount;
|
||||
_bindlessTexturesCount = usageInfo.BindlessTexturesCount;
|
||||
}
|
||||
|
||||
public void UpdateCommandBufferIndex(int commandBufferIndex)
|
||||
|
@ -99,13 +105,18 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
|
||||
int consumedDescriptors = _consumedDescriptorsPerSet[setIndex];
|
||||
|
||||
// All bindless resources have the update after bind flag set,
|
||||
// this is required for Intel, otherwise it just crashes when binding the descriptor sets
|
||||
// for bindless resources (maybe because it is above the limit?)
|
||||
bool updateAfterBind = setIndex >= PipelineBase.BindlessBufferTextureSetIndex;
|
||||
|
||||
var dsc = _gd.DescriptorSetManager.AllocateDescriptorSet(
|
||||
_gd.Api,
|
||||
DescriptorSetLayouts[setIndex],
|
||||
poolSizes,
|
||||
setIndex,
|
||||
consumedDescriptors,
|
||||
false);
|
||||
updateAfterBind);
|
||||
|
||||
list.Add(dsc);
|
||||
isNew = true;
|
||||
|
@ -116,7 +127,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
return list[index];
|
||||
}
|
||||
|
||||
private static Span<DescriptorPoolSize> GetDescriptorPoolSizes(Span<DescriptorPoolSize> output, int setIndex)
|
||||
private Span<DescriptorPoolSize> GetDescriptorPoolSizes(Span<DescriptorPoolSize> output, int setIndex)
|
||||
{
|
||||
int count = 1;
|
||||
|
||||
|
@ -138,6 +149,24 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
output[1] = new(DescriptorType.StorageTexelBuffer, DefaultImagePoolCapacity);
|
||||
count = 2;
|
||||
break;
|
||||
case PipelineBase.BindlessTexturesSetIndex:
|
||||
output[0] = new(DescriptorType.UniformBuffer, 1);
|
||||
output[1] = new(DescriptorType.StorageBuffer, 1);
|
||||
output[2] = new(DescriptorType.SampledImage, _bindlessTexturesCount);
|
||||
count = 3;
|
||||
break;
|
||||
case PipelineBase.BindlessBufferTextureSetIndex:
|
||||
output[0] = new(DescriptorType.UniformTexelBuffer, _bindlessTexturesCount);
|
||||
break;
|
||||
case PipelineBase.BindlessSamplersSetIndex:
|
||||
output[0] = new(DescriptorType.Sampler, _bindlessSamplersCount);
|
||||
break;
|
||||
case PipelineBase.BindlessImagesSetIndex:
|
||||
output[0] = new(DescriptorType.StorageImage, _bindlessTexturesCount);
|
||||
break;
|
||||
case PipelineBase.BindlessBufferImageSetIndex:
|
||||
output[0] = new(DescriptorType.StorageTexelBuffer, _bindlessTexturesCount);
|
||||
break;
|
||||
}
|
||||
|
||||
return output[..count];
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
VulkanRenderer gd,
|
||||
Device device,
|
||||
ReadOnlyCollection<ResourceDescriptorCollection> setDescriptors,
|
||||
bool usePushDescriptors)
|
||||
PipelineLayoutUsageInfo usageInfo)
|
||||
{
|
||||
DescriptorSetLayout[] layouts = new DescriptorSetLayout[setDescriptors.Count];
|
||||
|
||||
|
@ -30,6 +30,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
}
|
||||
}
|
||||
|
||||
bool hasRuntimeArray = false;
|
||||
|
||||
DescriptorSetLayoutBinding[] layoutBindings = new DescriptorSetLayoutBinding[rdc.Descriptors.Count];
|
||||
|
||||
for (int descIndex = 0; descIndex < rdc.Descriptors.Count; descIndex++)
|
||||
|
@ -45,11 +47,22 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
stages = activeStages;
|
||||
}
|
||||
|
||||
uint count = (uint)descriptor.Count;
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
count = descriptor.Type == ResourceType.Sampler
|
||||
? usageInfo.BindlessSamplersCount
|
||||
: usageInfo.BindlessTexturesCount;
|
||||
|
||||
hasRuntimeArray = true;
|
||||
}
|
||||
|
||||
layoutBindings[descIndex] = new DescriptorSetLayoutBinding
|
||||
{
|
||||
Binding = (uint)descriptor.Binding,
|
||||
DescriptorType = descriptor.Type.Convert(),
|
||||
DescriptorCount = (uint)descriptor.Count,
|
||||
DescriptorCount = count,
|
||||
StageFlags = stages.Convert(),
|
||||
};
|
||||
}
|
||||
|
@ -61,12 +74,42 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
SType = StructureType.DescriptorSetLayoutCreateInfo,
|
||||
PBindings = pLayoutBindings,
|
||||
BindingCount = (uint)layoutBindings.Length,
|
||||
Flags = usePushDescriptors && setIndex == 0 ? DescriptorSetLayoutCreateFlags.PushDescriptorBitKhr : DescriptorSetLayoutCreateFlags.None,
|
||||
Flags = usageInfo.UsePushDescriptors && setIndex == 0 ? DescriptorSetLayoutCreateFlags.PushDescriptorBitKhr : DescriptorSetLayoutCreateFlags.None,
|
||||
};
|
||||
|
||||
if (hasRuntimeArray)
|
||||
{
|
||||
var bindingFlags = new DescriptorBindingFlags[rdc.Descriptors.Count];
|
||||
|
||||
for (int descIndex = 0; descIndex < rdc.Descriptors.Count; descIndex++)
|
||||
{
|
||||
if (rdc.Descriptors.Count == 0)
|
||||
{
|
||||
bindingFlags[descIndex] = DescriptorBindingFlags.UpdateAfterBindBit;
|
||||
}
|
||||
}
|
||||
|
||||
fixed (DescriptorBindingFlags* pBindingFlags = bindingFlags)
|
||||
{
|
||||
var descriptorSetLayoutFlagsCreateInfo = new DescriptorSetLayoutBindingFlagsCreateInfo()
|
||||
{
|
||||
SType = StructureType.DescriptorSetLayoutBindingFlagsCreateInfo,
|
||||
PBindingFlags = pBindingFlags,
|
||||
BindingCount = (uint)bindingFlags.Length,
|
||||
};
|
||||
|
||||
descriptorSetLayoutCreateInfo.PNext = &descriptorSetLayoutFlagsCreateInfo;
|
||||
descriptorSetLayoutCreateInfo.Flags |= DescriptorSetLayoutCreateFlags.UpdateAfterBindPoolBit;
|
||||
|
||||
gd.Api.CreateDescriptorSetLayout(device, descriptorSetLayoutCreateInfo, null, out layouts[setIndex]).ThrowOnError();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
gd.Api.CreateDescriptorSetLayout(device, descriptorSetLayoutCreateInfo, null, out layouts[setIndex]).ThrowOnError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PipelineLayout layout;
|
||||
|
||||
|
|
35
src/Ryujinx.Graphics.Vulkan/PipelineLayoutUsageInfo.cs
Normal file
35
src/Ryujinx.Graphics.Vulkan/PipelineLayoutUsageInfo.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -311,9 +311,20 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFBF) | ((value ? 1UL : 0UL) << 6);
|
||||
}
|
||||
|
||||
public uint BindlessTexturesCount
|
||||
{
|
||||
get => (uint)((Internal.Id10 >> 0) & 0xFFFFFFFF);
|
||||
set => Internal.Id10 = (Internal.Id10 & 0xFFFFFFFF00000000) | ((ulong)value << 0);
|
||||
}
|
||||
|
||||
public uint BindlessSamplersCount
|
||||
{
|
||||
get => (uint)((Internal.Id10 >> 32) & 0xFFFFFFFF);
|
||||
set => Internal.Id10 = (Internal.Id10 & 0xFFFFFFFF) | ((ulong)value << 32);
|
||||
}
|
||||
|
||||
public NativeArray<PipelineShaderStageCreateInfo> Stages;
|
||||
public NativeArray<PipelineShaderStageRequiredSubgroupSizeCreateInfoEXT> StageRequiredSubgroupSizes;
|
||||
public PipelineLayout PipelineLayout;
|
||||
public SpecData SpecializationData;
|
||||
|
||||
private Array32<VertexInputAttributeDescription> _vertexAttributeDescriptions2;
|
||||
|
@ -339,6 +350,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
LineWidth = 1f;
|
||||
SamplesCount = 1;
|
||||
DepthMode = true;
|
||||
Internal.Id11 = 0; // Unused.
|
||||
}
|
||||
|
||||
public unsafe Auto<DisposablePipeline> CreateComputePipeline(
|
||||
|
@ -357,7 +369,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
SType = StructureType.ComputePipelineCreateInfo,
|
||||
Stage = Stages[0],
|
||||
BasePipelineIndex = -1,
|
||||
Layout = PipelineLayout,
|
||||
Layout = program.GetPipelineLayout(gd, BindlessTexturesCount, BindlessSamplersCount),
|
||||
};
|
||||
|
||||
Pipeline pipelineHandle = default;
|
||||
|
@ -625,7 +637,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
PDepthStencilState = &depthStencilState,
|
||||
PColorBlendState = &colorBlendState,
|
||||
PDynamicState = &pipelineDynamicStateCreateInfo,
|
||||
Layout = PipelineLayout,
|
||||
Layout = program.GetPipelineLayout(gd, BindlessTexturesCount, BindlessSamplersCount),
|
||||
RenderPass = renderPass,
|
||||
BasePipelineIndex = -1,
|
||||
};
|
||||
|
|
|
@ -21,6 +21,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
|
||||
public ulong Id8;
|
||||
public ulong Id9;
|
||||
public ulong Id10;
|
||||
public ulong Id11;
|
||||
|
||||
private readonly uint VertexAttributeDescriptionsCount => (byte)((Id6 >> 38) & 0xFF);
|
||||
private readonly uint VertexBindingDescriptionsCount => (byte)((Id6 >> 46) & 0xFF);
|
||||
|
@ -44,7 +46,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
{
|
||||
if (!Unsafe.As<ulong, Vector256<byte>>(ref Id0).Equals(Unsafe.As<ulong, Vector256<byte>>(ref other.Id0)) ||
|
||||
!Unsafe.As<ulong, Vector256<byte>>(ref Id4).Equals(Unsafe.As<ulong, Vector256<byte>>(ref other.Id4)) ||
|
||||
!Unsafe.As<ulong, Vector128<byte>>(ref Id8).Equals(Unsafe.As<ulong, Vector128<byte>>(ref other.Id8)))
|
||||
!Unsafe.As<ulong, Vector256<byte>>(ref Id8).Equals(Unsafe.As<ulong, Vector256<byte>>(ref other.Id8)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -88,7 +90,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
Id6 * 23 ^
|
||||
Id7 * 23 ^
|
||||
Id8 * 23 ^
|
||||
Id9 * 23;
|
||||
Id9 * 23 ^
|
||||
Id10 * 23;
|
||||
|
||||
for (int i = 0; i < (int)VertexAttributeDescriptionsCount; i++)
|
||||
{
|
||||
|
|
|
@ -15,9 +15,9 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
private readonly Shader[] _shaders;
|
||||
|
||||
private readonly PipelineLayoutCacheEntry _plce;
|
||||
private readonly ReadOnlyCollection<ResourceDescriptorCollection> _setDescriptors;
|
||||
|
||||
public PipelineLayout PipelineLayout => _plce.PipelineLayout;
|
||||
|
||||
public bool HasBindless { get; }
|
||||
public bool HasMinimalLayout { get; }
|
||||
public bool UsePushDescriptors { get; }
|
||||
public bool IsCompute { get; }
|
||||
|
@ -62,6 +62,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
ShaderSource[] shaders,
|
||||
ResourceLayout resourceLayout,
|
||||
SpecDescription[] specDescription = null,
|
||||
bool hasBindless = false,
|
||||
bool isMinimal = false)
|
||||
{
|
||||
_gd = gd;
|
||||
|
@ -109,8 +110,10 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
|
||||
bool usePushDescriptors = !isMinimal && VulkanConfiguration.UsePushDescriptors && _gd.Capabilities.SupportsPushDescriptors;
|
||||
|
||||
_plce = gd.PipelineLayoutCache.GetOrCreate(gd, device, resourceLayout.Sets, usePushDescriptors);
|
||||
_plce = gd.PipelineLayoutCache.GetOrCreate(gd, device, resourceLayout.Sets, new PipelineLayoutUsageInfo(0, 0, usePushDescriptors));
|
||||
_setDescriptors = resourceLayout.Sets;
|
||||
|
||||
HasBindless = hasBindless;
|
||||
HasMinimalLayout = isMinimal;
|
||||
UsePushDescriptors = usePushDescriptors;
|
||||
|
||||
|
@ -129,7 +132,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
ShaderSource[] sources,
|
||||
ResourceLayout resourceLayout,
|
||||
ProgramPipelineState state,
|
||||
bool fromCache) : this(gd, device, sources, resourceLayout)
|
||||
bool hasBindless,
|
||||
bool fromCache) : this(gd, device, sources, resourceLayout, null, hasBindless)
|
||||
{
|
||||
_state = state;
|
||||
|
||||
|
@ -325,7 +329,12 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
|
||||
pipeline.Stages[0] = _shaders[0].GetInfo();
|
||||
pipeline.StagesCount = 1;
|
||||
pipeline.PipelineLayout = PipelineLayout;
|
||||
|
||||
if (HasBindless)
|
||||
{
|
||||
pipeline.BindlessTexturesCount = BindlessManager.MinimumTexturesCount;
|
||||
pipeline.BindlessSamplersCount = BindlessManager.MinimumSamplersCount;
|
||||
}
|
||||
|
||||
pipeline.CreateComputePipeline(_gd, _device, this, (_gd.Pipeline as PipelineBase).PipelineCache);
|
||||
pipeline.Dispose();
|
||||
|
@ -353,7 +362,12 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
}
|
||||
|
||||
pipeline.StagesCount = (uint)_shaders.Length;
|
||||
pipeline.PipelineLayout = PipelineLayout;
|
||||
|
||||
if (HasBindless)
|
||||
{
|
||||
pipeline.BindlessTexturesCount = BindlessManager.MinimumTexturesCount;
|
||||
pipeline.BindlessSamplersCount = BindlessManager.MinimumSamplersCount;
|
||||
}
|
||||
|
||||
pipeline.CreateGraphicsPipeline(_gd, _device, this, (_gd.Pipeline as PipelineBase).PipelineCache, renderPass.Value);
|
||||
pipeline.Dispose();
|
||||
|
@ -474,6 +488,26 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
return _plce.GetNewDescriptorSetCollection(setIndex, out isNew);
|
||||
}
|
||||
|
||||
public PipelineLayout GetPipelineLayout(VulkanRenderer gd, uint bindlessTextureCount, uint bindlessSamplersCount)
|
||||
{
|
||||
return GetPipelineLayoutCacheEntry(gd, bindlessTextureCount, bindlessSamplersCount).PipelineLayout;
|
||||
}
|
||||
|
||||
public PipelineLayoutCacheEntry GetPipelineLayoutCacheEntry(VulkanRenderer gd, uint bindlessTextureCount, uint bindlessSamplersCount)
|
||||
{
|
||||
if ((bindlessTextureCount | bindlessSamplersCount) == 0)
|
||||
{
|
||||
return _plce;
|
||||
}
|
||||
|
||||
var usageInfo = new PipelineLayoutUsageInfo(
|
||||
bindlessTextureCount,
|
||||
bindlessSamplersCount,
|
||||
UsePushDescriptors);
|
||||
|
||||
return gd.PipelineLayoutCache.GetOrCreate(gd, _device, _setDescriptors, usageInfo);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
|
|
|
@ -23,6 +23,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
|
||||
public GAL.Format Format { get; }
|
||||
public VkFormat VkFormat { get; }
|
||||
|
||||
public TextureBuffer(VulkanRenderer gd, TextureCreateInfo info)
|
||||
|
@ -30,6 +31,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
_gd = gd;
|
||||
Width = info.Width;
|
||||
Height = info.Height;
|
||||
Format = info.Format;
|
||||
VkFormat = FormatTable.GetFormat(info.Format);
|
||||
|
||||
gd.Textures.Add(this);
|
||||
|
|
|
@ -299,6 +299,14 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
|
||||
features2.PNext = &supportedFeaturesVk11;
|
||||
|
||||
PhysicalDeviceVulkan12Features supportedFeaturesVk12 = new()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceVulkan12Features,
|
||||
PNext = features2.PNext
|
||||
};
|
||||
|
||||
features2.PNext = &supportedFeaturesVk12;
|
||||
|
||||
PhysicalDeviceCustomBorderColorFeaturesEXT supportedFeaturesCustomBorderColor = new()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceCustomBorderColorFeaturesExt,
|
||||
|
@ -451,8 +459,14 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
{
|
||||
SType = StructureType.PhysicalDeviceVulkan12Features,
|
||||
PNext = pExtendedFeatures,
|
||||
DescriptorBindingSampledImageUpdateAfterBind = supportedFeaturesVk12.DescriptorBindingSampledImageUpdateAfterBind,
|
||||
DescriptorBindingStorageImageUpdateAfterBind = supportedFeaturesVk12.DescriptorBindingStorageImageUpdateAfterBind,
|
||||
DescriptorBindingStorageTexelBufferUpdateAfterBind = supportedFeaturesVk12.DescriptorBindingStorageTexelBufferUpdateAfterBind,
|
||||
DescriptorBindingUniformTexelBufferUpdateAfterBind = supportedFeaturesVk12.DescriptorBindingUniformTexelBufferUpdateAfterBind,
|
||||
DescriptorIndexing = physicalDevice.IsDeviceExtensionPresent("VK_EXT_descriptor_indexing"),
|
||||
DrawIndirectCount = physicalDevice.IsDeviceExtensionPresent(KhrDrawIndirectCount.ExtensionName),
|
||||
RuntimeDescriptorArray = supportedFeaturesVk12.RuntimeDescriptorArray,
|
||||
SamplerMirrorClampToEdge = supportedFeaturesVk12.SamplerMirrorClampToEdge,
|
||||
UniformBufferStandardLayout = physicalDevice.IsDeviceExtensionPresent("VK_KHR_uniform_buffer_standard_layout"),
|
||||
};
|
||||
|
||||
|
|
|
@ -347,7 +347,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
|
||||
CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex);
|
||||
|
||||
DescriptorSetManager = new DescriptorSetManager(_device, PipelineBase.DescriptorSetLayouts);
|
||||
DescriptorSetManager = new DescriptorSetManager(_device, PipelineBase.DescriptorSetLayoutsBindless);
|
||||
|
||||
PipelineLayoutCache = new PipelineLayoutCache();
|
||||
|
||||
|
@ -418,7 +418,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
|
||||
if (info.State.HasValue || isCompute)
|
||||
{
|
||||
return new ShaderCollection(this, _device, sources, info.ResourceLayout, info.State ?? default, info.FromCache);
|
||||
return new ShaderCollection(this, _device, sources, info.ResourceLayout, info.State ?? default, info.HasBindless, info.FromCache);
|
||||
}
|
||||
|
||||
return new ShaderCollection(this, _device, sources, info.ResourceLayout);
|
||||
|
|
Loading…
Reference in a new issue