This commit is contained in:
gdkchan 2024-03-29 15:13:07 +00:00 committed by GitHub
commit f9614b57c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
91 changed files with 4066 additions and 1054 deletions

View file

@ -0,0 +1,8 @@
namespace Ryujinx.Graphics.GAL
{
public interface IImageArray
{
void SetFormats(int index, Format[] imageFormats);
void SetImages(int index, ITexture[] images);
}
}

View file

@ -59,6 +59,7 @@ namespace Ryujinx.Graphics.GAL
void SetIndexBuffer(BufferRange buffer, IndexType type); void SetIndexBuffer(BufferRange buffer, IndexType type);
void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat); void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat);
void SetImageArray(ShaderStage stage, int binding, IImageArray array);
void SetLineParameters(float width, bool smooth); void SetLineParameters(float width, bool smooth);
@ -89,6 +90,7 @@ namespace Ryujinx.Graphics.GAL
void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers); void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers);
void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler); void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler);
void SetTextureArray(ShaderStage stage, int binding, ITextureArray array);
void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers); void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers);
void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers); void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers);

View file

@ -21,10 +21,14 @@ namespace Ryujinx.Graphics.GAL
BufferHandle CreateBuffer(nint pointer, int size); BufferHandle CreateBuffer(nint pointer, int size);
BufferHandle CreateBufferSparse(ReadOnlySpan<BufferRange> storageBuffers); BufferHandle CreateBufferSparse(ReadOnlySpan<BufferRange> storageBuffers);
IImageArray CreateImageArray(int size, bool isBuffer);
IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info); IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info);
ISampler CreateSampler(SamplerCreateInfo info); ISampler CreateSampler(SamplerCreateInfo info);
ITexture CreateTexture(TextureCreateInfo info); ITexture CreateTexture(TextureCreateInfo info);
ITextureArray CreateTextureArray(int size, bool isBuffer);
bool PrepareHostMapping(nint address, ulong size); bool PrepareHostMapping(nint address, ulong size);
void CreateSync(ulong id, bool strict); void CreateSync(ulong id, bool strict);

View file

@ -0,0 +1,8 @@
namespace Ryujinx.Graphics.GAL
{
public interface ITextureArray
{
void SetSamplers(int index, ISampler[] samplers);
void SetTextures(int index, ITexture[] textures);
}
}

View file

@ -1,10 +1,12 @@
using Ryujinx.Graphics.GAL.Multithreading.Commands; using Ryujinx.Graphics.GAL.Multithreading.Commands;
using Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer; using Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer;
using Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent; using Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent;
using Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray;
using Ryujinx.Graphics.GAL.Multithreading.Commands.Program; using Ryujinx.Graphics.GAL.Multithreading.Commands.Program;
using Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer; using Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer;
using Ryujinx.Graphics.GAL.Multithreading.Commands.Sampler; using Ryujinx.Graphics.GAL.Multithreading.Commands.Sampler;
using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture; using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture;
using Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray;
using Ryujinx.Graphics.GAL.Multithreading.Commands.Window; using Ryujinx.Graphics.GAL.Multithreading.Commands.Window;
using System; using System;
using System.Linq; using System.Linq;
@ -46,10 +48,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register<CreateBufferAccessCommand>(CommandType.CreateBufferAccess); Register<CreateBufferAccessCommand>(CommandType.CreateBufferAccess);
Register<CreateBufferSparseCommand>(CommandType.CreateBufferSparse); Register<CreateBufferSparseCommand>(CommandType.CreateBufferSparse);
Register<CreateHostBufferCommand>(CommandType.CreateHostBuffer); Register<CreateHostBufferCommand>(CommandType.CreateHostBuffer);
Register<CreateImageArrayCommand>(CommandType.CreateImageArray);
Register<CreateProgramCommand>(CommandType.CreateProgram); Register<CreateProgramCommand>(CommandType.CreateProgram);
Register<CreateSamplerCommand>(CommandType.CreateSampler); Register<CreateSamplerCommand>(CommandType.CreateSampler);
Register<CreateSyncCommand>(CommandType.CreateSync); Register<CreateSyncCommand>(CommandType.CreateSync);
Register<CreateTextureCommand>(CommandType.CreateTexture); Register<CreateTextureCommand>(CommandType.CreateTexture);
Register<CreateTextureArrayCommand>(CommandType.CreateTextureArray);
Register<GetCapabilitiesCommand>(CommandType.GetCapabilities); Register<GetCapabilitiesCommand>(CommandType.GetCapabilities);
Register<PreFrameCommand>(CommandType.PreFrame); Register<PreFrameCommand>(CommandType.PreFrame);
Register<ReportCounterCommand>(CommandType.ReportCounter); Register<ReportCounterCommand>(CommandType.ReportCounter);
@ -63,6 +67,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register<CounterEventDisposeCommand>(CommandType.CounterEventDispose); Register<CounterEventDisposeCommand>(CommandType.CounterEventDispose);
Register<CounterEventFlushCommand>(CommandType.CounterEventFlush); Register<CounterEventFlushCommand>(CommandType.CounterEventFlush);
Register<ImageArraySetFormatsCommand>(CommandType.ImageArraySetFormats);
Register<ImageArraySetImagesCommand>(CommandType.ImageArraySetImages);
Register<ProgramDisposeCommand>(CommandType.ProgramDispose); Register<ProgramDisposeCommand>(CommandType.ProgramDispose);
Register<ProgramGetBinaryCommand>(CommandType.ProgramGetBinary); Register<ProgramGetBinaryCommand>(CommandType.ProgramGetBinary);
Register<ProgramCheckLinkCommand>(CommandType.ProgramCheckLink); Register<ProgramCheckLinkCommand>(CommandType.ProgramCheckLink);
@ -82,6 +89,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register<TextureSetDataSliceRegionCommand>(CommandType.TextureSetDataSliceRegion); Register<TextureSetDataSliceRegionCommand>(CommandType.TextureSetDataSliceRegion);
Register<TextureSetStorageCommand>(CommandType.TextureSetStorage); Register<TextureSetStorageCommand>(CommandType.TextureSetStorage);
Register<TextureArraySetSamplersCommand>(CommandType.TextureArraySetSamplers);
Register<TextureArraySetTexturesCommand>(CommandType.TextureArraySetTextures);
Register<WindowPresentCommand>(CommandType.WindowPresent); Register<WindowPresentCommand>(CommandType.WindowPresent);
Register<BarrierCommand>(CommandType.Barrier); Register<BarrierCommand>(CommandType.Barrier);
@ -114,6 +124,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register<SetTransformFeedbackBuffersCommand>(CommandType.SetTransformFeedbackBuffers); Register<SetTransformFeedbackBuffersCommand>(CommandType.SetTransformFeedbackBuffers);
Register<SetUniformBuffersCommand>(CommandType.SetUniformBuffers); Register<SetUniformBuffersCommand>(CommandType.SetUniformBuffers);
Register<SetImageCommand>(CommandType.SetImage); Register<SetImageCommand>(CommandType.SetImage);
Register<SetImageArrayCommand>(CommandType.SetImageArray);
Register<SetIndexBufferCommand>(CommandType.SetIndexBuffer); Register<SetIndexBufferCommand>(CommandType.SetIndexBuffer);
Register<SetLineParametersCommand>(CommandType.SetLineParameters); Register<SetLineParametersCommand>(CommandType.SetLineParameters);
Register<SetLogicOpStateCommand>(CommandType.SetLogicOpState); Register<SetLogicOpStateCommand>(CommandType.SetLogicOpState);
@ -130,6 +141,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register<SetScissorsCommand>(CommandType.SetScissor); Register<SetScissorsCommand>(CommandType.SetScissor);
Register<SetStencilTestCommand>(CommandType.SetStencilTest); Register<SetStencilTestCommand>(CommandType.SetStencilTest);
Register<SetTextureAndSamplerCommand>(CommandType.SetTextureAndSampler); Register<SetTextureAndSamplerCommand>(CommandType.SetTextureAndSampler);
Register<SetTextureArrayCommand>(CommandType.SetTextureArray);
Register<SetUserClipDistanceCommand>(CommandType.SetUserClipDistance); Register<SetUserClipDistanceCommand>(CommandType.SetUserClipDistance);
Register<SetVertexAttribsCommand>(CommandType.SetVertexAttribs); Register<SetVertexAttribsCommand>(CommandType.SetVertexAttribs);
Register<SetVertexBuffersCommand>(CommandType.SetVertexBuffers); Register<SetVertexBuffersCommand>(CommandType.SetVertexBuffers);

View file

@ -7,10 +7,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
CreateBufferAccess, CreateBufferAccess,
CreateBufferSparse, CreateBufferSparse,
CreateHostBuffer, CreateHostBuffer,
CreateImageArray,
CreateProgram, CreateProgram,
CreateSampler, CreateSampler,
CreateSync, CreateSync,
CreateTexture, CreateTexture,
CreateTextureArray,
GetCapabilities, GetCapabilities,
Unused, Unused,
PreFrame, PreFrame,
@ -25,6 +27,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
CounterEventDispose, CounterEventDispose,
CounterEventFlush, CounterEventFlush,
ImageArraySetFormats,
ImageArraySetImages,
ProgramDispose, ProgramDispose,
ProgramGetBinary, ProgramGetBinary,
ProgramCheckLink, ProgramCheckLink,
@ -44,6 +49,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
TextureSetDataSliceRegion, TextureSetDataSliceRegion,
TextureSetStorage, TextureSetStorage,
TextureArraySetSamplers,
TextureArraySetTextures,
WindowPresent, WindowPresent,
Barrier, Barrier,
@ -76,6 +84,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
SetTransformFeedbackBuffers, SetTransformFeedbackBuffers,
SetUniformBuffers, SetUniformBuffers,
SetImage, SetImage,
SetImageArray,
SetIndexBuffer, SetIndexBuffer,
SetLineParameters, SetLineParameters,
SetLogicOpState, SetLogicOpState,
@ -92,6 +101,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
SetScissor, SetScissor,
SetStencilTest, SetStencilTest,
SetTextureAndSampler, SetTextureAndSampler,
SetTextureArray,
SetUserClipDistance, SetUserClipDistance,
SetVertexAttribs, SetVertexAttribs,
SetVertexBuffers, SetVertexBuffers,

View file

@ -0,0 +1,26 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray
{
struct ImageArraySetFormatsCommand : IGALCommand, IGALCommand<ImageArraySetFormatsCommand>
{
public readonly CommandType CommandType => CommandType.ImageArraySetFormats;
private TableRef<ThreadedImageArray> _imageArray;
private int _index;
private TableRef<Format[]> _imageFormats;
public void Set(TableRef<ThreadedImageArray> imageArray, int index, TableRef<Format[]> imageFormats)
{
_imageArray = imageArray;
_index = index;
_imageFormats = imageFormats;
}
public static void Run(ref ImageArraySetFormatsCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
ThreadedImageArray imageArray = command._imageArray.Get(threaded);
imageArray.Base.SetFormats(command._index, command._imageFormats.Get(threaded));
}
}
}

View file

@ -0,0 +1,27 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
using System.Linq;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray
{
struct ImageArraySetImagesCommand : IGALCommand, IGALCommand<ImageArraySetImagesCommand>
{
public readonly CommandType CommandType => CommandType.ImageArraySetImages;
private TableRef<ThreadedImageArray> _imageArray;
private int _index;
private TableRef<ITexture[]> _images;
public void Set(TableRef<ThreadedImageArray> imageArray, int index, TableRef<ITexture[]> images)
{
_imageArray = imageArray;
_index = index;
_images = images;
}
public static void Run(ref ImageArraySetImagesCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
ThreadedImageArray imageArray = command._imageArray.Get(threaded);
imageArray.Base.SetImages(command._index, command._images.Get(threaded).Select(texture => ((ThreadedTexture)texture)?.Base).ToArray());
}
}
}

View file

@ -0,0 +1,25 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
{
struct CreateImageArrayCommand : IGALCommand, IGALCommand<CreateImageArrayCommand>
{
public readonly CommandType CommandType => CommandType.CreateImageArray;
private TableRef<ThreadedImageArray> _imageArray;
private int _size;
private bool _isBuffer;
public void Set(TableRef<ThreadedImageArray> imageArray, int size, bool isBuffer)
{
_imageArray = imageArray;
_size = size;
_isBuffer = isBuffer;
}
public static void Run(ref CreateImageArrayCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
command._imageArray.Get(threaded).Base = renderer.CreateImageArray(command._size, command._isBuffer);
}
}
}

View file

@ -0,0 +1,25 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
{
struct CreateTextureArrayCommand : IGALCommand, IGALCommand<CreateTextureArrayCommand>
{
public readonly CommandType CommandType => CommandType.CreateTextureArray;
private TableRef<ThreadedTextureArray> _textureArray;
private int _size;
private bool _isBuffer;
public void Set(TableRef<ThreadedTextureArray> textureArray, int size, bool isBuffer)
{
_textureArray = textureArray;
_size = size;
_isBuffer = isBuffer;
}
public static void Run(ref CreateTextureArrayCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
command._textureArray.Get(threaded).Base = renderer.CreateTextureArray(command._size, command._isBuffer);
}
}
}

View file

@ -0,0 +1,26 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
using Ryujinx.Graphics.Shader;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{
struct SetImageArrayCommand : IGALCommand, IGALCommand<SetImageArrayCommand>
{
public readonly CommandType CommandType => CommandType.SetImageArray;
private ShaderStage _stage;
private int _binding;
private TableRef<IImageArray> _array;
public void Set(ShaderStage stage, int binding, TableRef<IImageArray> array)
{
_stage = stage;
_binding = binding;
_array = array;
}
public static void Run(ref SetImageArrayCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
renderer.Pipeline.SetImageArray(command._stage, command._binding, command._array.GetAs<ThreadedImageArray>(threaded)?.Base);
}
}
}

View file

@ -0,0 +1,26 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
using Ryujinx.Graphics.Shader;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{
struct SetTextureArrayCommand : IGALCommand, IGALCommand<SetTextureArrayCommand>
{
public readonly CommandType CommandType => CommandType.SetTextureArray;
private ShaderStage _stage;
private int _binding;
private TableRef<ITextureArray> _array;
public void Set(ShaderStage stage, int binding, TableRef<ITextureArray> array)
{
_stage = stage;
_binding = binding;
_array = array;
}
public static void Run(ref SetTextureArrayCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
renderer.Pipeline.SetTextureArray(command._stage, command._binding, command._array.GetAs<ThreadedTextureArray>(threaded)?.Base);
}
}
}

View file

@ -0,0 +1,27 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
using System.Linq;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray
{
struct TextureArraySetSamplersCommand : IGALCommand, IGALCommand<TextureArraySetSamplersCommand>
{
public readonly CommandType CommandType => CommandType.TextureArraySetSamplers;
private TableRef<ThreadedTextureArray> _textureArray;
private int _index;
private TableRef<ISampler[]> _samplers;
public void Set(TableRef<ThreadedTextureArray> textureArray, int index, TableRef<ISampler[]> samplers)
{
_textureArray = textureArray;
_index = index;
_samplers = samplers;
}
public static void Run(ref TextureArraySetSamplersCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
ThreadedTextureArray textureArray = command._textureArray.Get(threaded);
textureArray.Base.SetSamplers(command._index, command._samplers.Get(threaded).Select(sampler => ((ThreadedSampler)sampler)?.Base).ToArray());
}
}
}

View file

@ -0,0 +1,27 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
using System.Linq;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray
{
struct TextureArraySetTexturesCommand : IGALCommand, IGALCommand<TextureArraySetTexturesCommand>
{
public readonly CommandType CommandType => CommandType.TextureArraySetTextures;
private TableRef<ThreadedTextureArray> _textureArray;
private int _index;
private TableRef<ITexture[]> _textures;
public void Set(TableRef<ThreadedTextureArray> textureArray, int index, TableRef<ITexture[]> textures)
{
_textureArray = textureArray;
_index = index;
_textures = textures;
}
public static void Run(ref TextureArraySetTexturesCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
ThreadedTextureArray textureArray = command._textureArray.Get(threaded);
textureArray.Base.SetTextures(command._index, command._textures.Get(threaded).Select(texture => ((ThreadedTexture)texture)?.Base).ToArray());
}
}
}

View file

@ -0,0 +1,36 @@
using Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray;
using Ryujinx.Graphics.GAL.Multithreading.Model;
namespace Ryujinx.Graphics.GAL.Multithreading.Resources
{
/// <summary>
/// Threaded representation of a image array.
/// </summary>
class ThreadedImageArray : IImageArray
{
private readonly ThreadedRenderer _renderer;
public IImageArray Base;
public ThreadedImageArray(ThreadedRenderer renderer)
{
_renderer = renderer;
}
private TableRef<T> Ref<T>(T reference)
{
return new TableRef<T>(_renderer, reference);
}
public void SetFormats(int index, Format[] imageFormats)
{
_renderer.New<ImageArraySetFormatsCommand>().Set(Ref(this), index, Ref(imageFormats));
_renderer.QueueCommand();
}
public void SetImages(int index, ITexture[] images)
{
_renderer.New<ImageArraySetImagesCommand>().Set(Ref(this), index, Ref(images));
_renderer.QueueCommand();
}
}
}

View file

@ -0,0 +1,37 @@
using Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray;
using Ryujinx.Graphics.GAL.Multithreading.Model;
using System.Linq;
namespace Ryujinx.Graphics.GAL.Multithreading.Resources
{
/// <summary>
/// Threaded representation of a texture and sampler array.
/// </summary>
class ThreadedTextureArray : ITextureArray
{
private readonly ThreadedRenderer _renderer;
public ITextureArray Base;
public ThreadedTextureArray(ThreadedRenderer renderer)
{
_renderer = renderer;
}
private TableRef<T> Ref<T>(T reference)
{
return new TableRef<T>(_renderer, reference);
}
public void SetSamplers(int index, ISampler[] samplers)
{
_renderer.New<TextureArraySetSamplersCommand>().Set(Ref(this), index, Ref(samplers.ToArray()));
_renderer.QueueCommand();
}
public void SetTextures(int index, ITexture[] textures)
{
_renderer.New<TextureArraySetTexturesCommand>().Set(Ref(this), index, Ref(textures.ToArray()));
_renderer.QueueCommand();
}
}
}

View file

@ -183,6 +183,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public void SetImageArray(ShaderStage stage, int binding, IImageArray array)
{
_renderer.New<SetImageArrayCommand>().Set(stage, binding, Ref(array));
_renderer.QueueCommand();
}
public void SetIndexBuffer(BufferRange buffer, IndexType type) public void SetIndexBuffer(BufferRange buffer, IndexType type)
{ {
_renderer.New<SetIndexBufferCommand>().Set(buffer, type); _renderer.New<SetIndexBufferCommand>().Set(buffer, type);
@ -285,6 +291,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public void SetTextureArray(ShaderStage stage, int binding, ITextureArray array)
{
_renderer.New<SetTextureArrayCommand>().Set(stage, binding, Ref(array));
_renderer.QueueCommand();
}
public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers) public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers)
{ {
_renderer.New<SetTransformFeedbackBuffersCommand>().Set(_renderer.CopySpan(buffers)); _renderer.New<SetTransformFeedbackBuffersCommand>().Set(_renderer.CopySpan(buffers));

View file

@ -299,6 +299,15 @@ namespace Ryujinx.Graphics.GAL.Multithreading
return handle; return handle;
} }
public IImageArray CreateImageArray(int size, bool isBuffer)
{
var imageArray = new ThreadedImageArray(this);
New<CreateImageArrayCommand>().Set(Ref(imageArray), size, isBuffer);
QueueCommand();
return imageArray;
}
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info) public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
{ {
var program = new ThreadedProgram(this); var program = new ThreadedProgram(this);
@ -349,6 +358,14 @@ namespace Ryujinx.Graphics.GAL.Multithreading
return texture; return texture;
} }
} }
public ITextureArray CreateTextureArray(int size, bool isBuffer)
{
var textureArray = new ThreadedTextureArray(this);
New<CreateTextureArrayCommand>().Set(Ref(textureArray), size, isBuffer);
QueueCommand();
return textureArray;
}
public void DeleteBuffer(BufferHandle buffer) public void DeleteBuffer(BufferHandle buffer)
{ {

View file

@ -71,19 +71,21 @@ namespace Ryujinx.Graphics.GAL
public readonly struct ResourceUsage : IEquatable<ResourceUsage> public readonly struct ResourceUsage : IEquatable<ResourceUsage>
{ {
public int Binding { get; } public int Binding { get; }
public int ArrayLength { get; }
public ResourceType Type { get; } public ResourceType Type { get; }
public ResourceStages Stages { get; } public ResourceStages Stages { get; }
public ResourceUsage(int binding, ResourceType type, ResourceStages stages) public ResourceUsage(int binding, int arrayLength, ResourceType type, ResourceStages stages)
{ {
Binding = binding; Binding = binding;
ArrayLength = arrayLength;
Type = type; Type = type;
Stages = stages; Stages = stages;
} }
public override int GetHashCode() public override int GetHashCode()
{ {
return HashCode.Combine(Binding, Type, Stages); return HashCode.Combine(Binding, ArrayLength, Type, Stages);
} }
public override bool Equals(object obj) public override bool Equals(object obj)
@ -93,7 +95,7 @@ namespace Ryujinx.Graphics.GAL
public bool Equals(ResourceUsage other) public bool Equals(ResourceUsage other)
{ {
return Binding == other.Binding && Type == other.Type && Stages == other.Stages; return Binding == other.Binding && ArrayLength == other.ArrayLength && Type == other.Type && Stages == other.Stages;
} }
public static bool operator ==(ResourceUsage left, ResourceUsage right) public static bool operator ==(ResourceUsage left, ResourceUsage right)

View file

@ -89,5 +89,10 @@ namespace Ryujinx.Graphics.Gpu
/// Maximum size that an storage buffer is assumed to have when the correct size is unknown. /// Maximum size that an storage buffer is assumed to have when the correct size is unknown.
/// </summary> /// </summary>
public const ulong MaxUnknownStorageSize = 0x100000; public const ulong MaxUnknownStorageSize = 0x100000;
/// <summary>
/// Size of a bindless texture handle as exposed by guest graphics APIs.
/// </summary>
public const int TextureHandleSizeInBytes = sizeof(ulong);
} }
} }

View file

@ -126,6 +126,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
ulong samplerPoolGpuVa = ((ulong)_state.State.SetTexSamplerPoolAOffsetUpper << 32) | _state.State.SetTexSamplerPoolB; ulong samplerPoolGpuVa = ((ulong)_state.State.SetTexSamplerPoolAOffsetUpper << 32) | _state.State.SetTexSamplerPoolB;
ulong texturePoolGpuVa = ((ulong)_state.State.SetTexHeaderPoolAOffsetUpper << 32) | _state.State.SetTexHeaderPoolB; ulong texturePoolGpuVa = ((ulong)_state.State.SetTexHeaderPoolAOffsetUpper << 32) | _state.State.SetTexHeaderPoolB;
int samplerPoolMaximumId = _state.State.SetTexSamplerPoolCMaximumIndex;
GpuChannelPoolState poolState = new( GpuChannelPoolState poolState = new(
texturePoolGpuVa, texturePoolGpuVa,
_state.State.SetTexHeaderPoolCMaximumIndex, _state.State.SetTexHeaderPoolCMaximumIndex,
@ -139,7 +141,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
sharedMemorySize, sharedMemorySize,
_channel.BufferManager.HasUnalignedStorageBuffers); _channel.BufferManager.HasUnalignedStorageBuffers);
CachedShaderProgram cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, poolState, computeState, shaderGpuVa); CachedShaderProgram cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, samplerPoolMaximumId, poolState, computeState, shaderGpuVa);
_context.Renderer.Pipeline.SetProgram(cs.HostProgram); _context.Renderer.Pipeline.SetProgram(cs.HostProgram);
@ -184,7 +186,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
sharedMemorySize, sharedMemorySize,
_channel.BufferManager.HasUnalignedStorageBuffers); _channel.BufferManager.HasUnalignedStorageBuffers);
cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, poolState, computeState, shaderGpuVa); cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, samplerPoolMaximumId, poolState, computeState, shaderGpuVa);
_context.Renderer.Pipeline.SetProgram(cs.HostProgram); _context.Renderer.Pipeline.SetProgram(cs.HostProgram);
} }

View file

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

View file

@ -1429,7 +1429,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
addressesSpan[index] = baseAddress + shader.Offset; addressesSpan[index] = baseAddress + shader.Offset;
} }
CachedShaderProgram gs = shaderCache.GetGraphicsShader(ref _state.State, ref _pipeline, _channel, ref _currentSpecState.GetPoolState(), ref _currentSpecState.GetGraphicsState(), addresses); int samplerPoolMaximumId = _state.State.SamplerIndex == SamplerIndex.ViaHeaderIndex
? _state.State.TexturePoolState.MaximumId
: _state.State.SamplerPoolState.MaximumId;
CachedShaderProgram gs = shaderCache.GetGraphicsShader(
ref _state.State,
ref _pipeline,
_channel,
samplerPoolMaximumId,
ref _currentSpecState.GetPoolState(),
ref _currentSpecState.GetGraphicsState(),
addresses);
// Consume the modified flag for spec state so that it isn't checked again. // Consume the modified flag for spec state so that it isn't checked again.
_currentSpecState.SetShader(gs); _currentSpecState.SetShader(gs);

View file

@ -111,6 +111,21 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <returns>The GPU resource with the given ID</returns> /// <returns>The GPU resource with the given ID</returns>
public abstract T1 Get(int id); public abstract T1 Get(int id);
/// <summary>
/// Gets the cached item with the given ID, or null if there is no cached item for the specified ID.
/// </summary>
/// <param name="id">ID of the item. This is effectively a zero-based index</param>
/// <returns>The cached item with the given ID</returns>
public T1 GetCachedItem(int id)
{
if (!IsValidId(id))
{
return default;
}
return Items[id];
}
/// <summary> /// <summary>
/// Checks if a given ID is valid and inside the range of the pool. /// Checks if a given ID is valid and inside the range of the pool.
/// </summary> /// </summary>
@ -197,6 +212,18 @@ namespace Ryujinx.Graphics.Gpu.Image
return false; return false;
} }
public bool WasModified(ref int sequenceNumber)
{
if (sequenceNumber != ModifiedSequenceNumber)
{
sequenceNumber = ModifiedSequenceNumber;
return true;
}
return false;
}
protected abstract void InvalidateRangeImpl(ulong address, ulong size); protected abstract void InvalidateRangeImpl(ulong address, ulong size);
protected abstract void Delete(T1 item); protected abstract void Delete(T1 item);

View file

@ -24,6 +24,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
public int Binding { get; } public int Binding { get; }
/// <summary>
/// For array of textures, this indicates the length of the array. A value of one indicates it is not an array.
/// </summary>
public int ArrayLength { get; }
/// <summary> /// <summary>
/// Constant buffer slot with the texture handle. /// Constant buffer slot with the texture handle.
/// </summary> /// </summary>
@ -39,20 +44,27 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
public TextureUsageFlags Flags { get; } public TextureUsageFlags Flags { get; }
/// <summary>
/// Indicates that the binding is for a sampler.
/// </summary>
public bool IsSamplerOnly { get; }
/// <summary> /// <summary>
/// Constructs the texture binding information structure. /// Constructs the texture binding information structure.
/// </summary> /// </summary>
/// <param name="target">The shader sampler target type</param> /// <param name="target">The shader sampler target type</param>
/// <param name="format">Format of the image as declared on the shader</param> /// <param name="format">Format of the image as declared on the shader</param>
/// <param name="binding">The shader texture binding point</param> /// <param name="binding">The shader texture binding point</param>
/// <param name="arrayLength">For array of textures, this indicates the length of the array. A value of one indicates it is not an array</param>
/// <param name="cbufSlot">Constant buffer slot where the texture handle is located</param> /// <param name="cbufSlot">Constant buffer slot where the texture handle is located</param>
/// <param name="handle">The shader texture handle (read index into the texture constant buffer)</param> /// <param name="handle">The shader texture handle (read index into the texture constant buffer)</param>
/// <param name="flags">The texture's usage flags, indicating how it is used in the shader</param> /// <param name="flags">The texture's usage flags, indicating how it is used in the shader</param>
public TextureBindingInfo(Target target, Format format, int binding, int cbufSlot, int handle, TextureUsageFlags flags) public TextureBindingInfo(Target target, Format format, int binding, int arrayLength, int cbufSlot, int handle, TextureUsageFlags flags)
{ {
Target = target; Target = target;
Format = format; Format = format;
Binding = binding; Binding = binding;
ArrayLength = arrayLength;
CbufSlot = cbufSlot; CbufSlot = cbufSlot;
Handle = handle; Handle = handle;
Flags = flags; Flags = flags;
@ -63,11 +75,21 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
/// <param name="target">The shader sampler target type</param> /// <param name="target">The shader sampler target type</param>
/// <param name="binding">The shader texture binding point</param> /// <param name="binding">The shader texture binding point</param>
/// <param name="arrayLength">For array of textures, this indicates the length of the array. A value of one indicates it is not an array</param>
/// <param name="cbufSlot">Constant buffer slot where the texture handle is located</param> /// <param name="cbufSlot">Constant buffer slot where the texture handle is located</param>
/// <param name="handle">The shader texture handle (read index into the texture constant buffer)</param> /// <param name="handle">The shader texture handle (read index into the texture constant buffer)</param>
/// <param name="flags">The texture's usage flags, indicating how it is used in the shader</param> /// <param name="flags">The texture's usage flags, indicating how it is used in the shader</param>
public TextureBindingInfo(Target target, int binding, int cbufSlot, int handle, TextureUsageFlags flags) : this(target, (Format)0, binding, cbufSlot, handle, flags) /// <param name="isSamplerOnly">Indicates that the binding is for a sampler</param>
public TextureBindingInfo(
Target target,
int binding,
int arrayLength,
int cbufSlot,
int handle,
TextureUsageFlags flags,
bool isSamplerOnly) : this(target, 0, binding, arrayLength, cbufSlot, handle, flags)
{ {
IsSamplerOnly = isSamplerOnly;
} }
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -34,6 +34,8 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly TexturePoolCache _texturePoolCache; private readonly TexturePoolCache _texturePoolCache;
private readonly SamplerPoolCache _samplerPoolCache; private readonly SamplerPoolCache _samplerPoolCache;
private readonly TextureBindingsArrayCache _arrayBindingsCache;
private TexturePool _cachedTexturePool; private TexturePool _cachedTexturePool;
private SamplerPool _cachedSamplerPool; private SamplerPool _cachedSamplerPool;
@ -56,6 +58,8 @@ namespace Ryujinx.Graphics.Gpu.Image
private TextureState[] _textureState; private TextureState[] _textureState;
private TextureState[] _imageState; private TextureState[] _imageState;
private int[] _textureCounts;
private int _texturePoolSequence; private int _texturePoolSequence;
private int _samplerPoolSequence; private int _samplerPoolSequence;
@ -85,6 +89,8 @@ namespace Ryujinx.Graphics.Gpu.Image
_isCompute = isCompute; _isCompute = isCompute;
_arrayBindingsCache = new TextureBindingsArrayCache(context, channel, isCompute);
int stages = isCompute ? 1 : Constants.ShaderStages; int stages = isCompute ? 1 : Constants.ShaderStages;
_textureBindings = new TextureBindingInfo[stages][]; _textureBindings = new TextureBindingInfo[stages][];
@ -95,9 +101,11 @@ namespace Ryujinx.Graphics.Gpu.Image
for (int stage = 0; stage < stages; stage++) for (int stage = 0; stage < stages; stage++)
{ {
_textureBindings[stage] = new TextureBindingInfo[InitialTextureStateSize]; _textureBindings[stage] = Array.Empty<TextureBindingInfo>();
_imageBindings[stage] = new TextureBindingInfo[InitialImageStateSize]; _imageBindings[stage] = Array.Empty<TextureBindingInfo>();
} }
_textureCounts = Array.Empty<int>();
} }
/// <summary> /// <summary>
@ -109,6 +117,8 @@ namespace Ryujinx.Graphics.Gpu.Image
_textureBindings = bindings.TextureBindings; _textureBindings = bindings.TextureBindings;
_imageBindings = bindings.ImageBindings; _imageBindings = bindings.ImageBindings;
_textureCounts = bindings.TextureCounts;
SetMaxBindings(bindings.MaxTextureBinding, bindings.MaxImageBinding); SetMaxBindings(bindings.MaxTextureBinding, bindings.MaxImageBinding);
} }
@ -401,27 +411,6 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
} }
#pragma warning disable IDE0051 // Remove unused private member
/// <summary>
/// Counts the total number of texture bindings used by all shader stages.
/// </summary>
/// <returns>The total amount of textures used</returns>
private int GetTextureBindingsCount()
{
int count = 0;
foreach (TextureBindingInfo[] textureInfo in _textureBindings)
{
if (textureInfo != null)
{
count += textureInfo.Length;
}
}
return count;
}
#pragma warning restore IDE0051
/// <summary> /// <summary>
/// Ensures that the texture bindings are visible to the host GPU. /// Ensures that the texture bindings are visible to the host GPU.
/// Note: this actually performs the binding using the host graphics API. /// Note: this actually performs the binding using the host graphics API.
@ -465,6 +454,13 @@ namespace Ryujinx.Graphics.Gpu.Image
TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index]; TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index];
TextureUsageFlags usageFlags = bindingInfo.Flags; TextureUsageFlags usageFlags = bindingInfo.Flags;
if (bindingInfo.ArrayLength > 1)
{
_arrayBindingsCache.UpdateTextureArray(texturePool, samplerPool, stage, stageIndex, _textureBufferIndex, _samplerIndex, bindingInfo);
continue;
}
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex); (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex); UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex);
@ -582,7 +578,7 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
// Scales for images appear after the texture ones. // Scales for images appear after the texture ones.
int baseScaleIndex = _textureBindings[stageIndex].Length; int baseScaleIndex = _textureCounts[stageIndex];
int cachedTextureBufferIndex = -1; int cachedTextureBufferIndex = -1;
int cachedSamplerBufferIndex = -1; int cachedSamplerBufferIndex = -1;
@ -595,6 +591,14 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
TextureBindingInfo bindingInfo = _imageBindings[stageIndex][index]; TextureBindingInfo bindingInfo = _imageBindings[stageIndex][index];
TextureUsageFlags usageFlags = bindingInfo.Flags; TextureUsageFlags usageFlags = bindingInfo.Flags;
if (bindingInfo.ArrayLength > 1)
{
_arrayBindingsCache.UpdateImageArray(pool, stage, stageIndex, _textureBufferIndex, bindingInfo);
continue;
}
int scaleIndex = baseScaleIndex + index; int scaleIndex = baseScaleIndex + index;
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex); (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
@ -620,7 +624,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (isStore) if (isStore)
{ {
cachedTexture?.SignalModified(); cachedTexture.SignalModified();
} }
Format format = bindingInfo.Format == 0 ? cachedTexture.Format : bindingInfo.Format; Format format = bindingInfo.Format == 0 ? cachedTexture.Format : bindingInfo.Format;

View file

@ -432,7 +432,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
if (gpuVa != 0 && format != 0) if (gpuVa != 0 && format != 0)
{ {
Logger.Error?.Print(LogClass.Gpu, $"Invalid texture format 0x{format:X} (sRGB: {srgb})."); // Logger.Error?.Print(LogClass.Gpu, $"Invalid texture format 0x{format:X} (sRGB: {srgb}).");
} }
formatInfo = FormatInfo.Default; formatInfo = FormatInfo.Default;

View file

@ -1,12 +1,13 @@
using Ryujinx.Graphics.Shader; using Ryujinx.Graphics.Shader;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using System;
namespace Ryujinx.Graphics.Gpu.Memory namespace Ryujinx.Graphics.Gpu.Memory
{ {
/// <summary> /// <summary>
/// Memory range used for buffers. /// Memory range used for buffers.
/// </summary> /// </summary>
readonly struct BufferBounds readonly struct BufferBounds : IEquatable<BufferBounds>
{ {
/// <summary> /// <summary>
/// Physical memory ranges where the buffer is mapped. /// Physical memory ranges where the buffer is mapped.
@ -33,5 +34,25 @@ namespace Ryujinx.Graphics.Gpu.Memory
Range = range; Range = range;
Flags = flags; Flags = flags;
} }
public override bool Equals(object obj)
{
return obj is BufferBounds bounds && Equals(bounds);
}
public bool Equals(BufferBounds bounds)
{
return Range == bounds.Range && Flags == bounds.Flags;
}
public bool Equals(ref BufferBounds bounds)
{
return Range == bounds.Range && Flags == bounds.Flags;
}
public override int GetHashCode()
{
return HashCode.Combine(Range, Flags);
}
} }
} }

View file

@ -27,6 +27,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
private readonly VertexBuffer[] _vertexBuffers; private readonly VertexBuffer[] _vertexBuffers;
private readonly BufferBounds[] _transformFeedbackBuffers; private readonly BufferBounds[] _transformFeedbackBuffers;
private readonly List<BufferTextureBinding> _bufferTextures; private readonly List<BufferTextureBinding> _bufferTextures;
private readonly List<BufferTextureArrayBinding<ITextureArray>> _bufferTextureArrays;
private readonly List<BufferTextureArrayBinding<IImageArray>> _bufferImageArrays;
private readonly BufferAssignment[] _ranges; private readonly BufferAssignment[] _ranges;
/// <summary> /// <summary>
@ -140,11 +142,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
_bufferTextures = new List<BufferTextureBinding>(); _bufferTextures = new List<BufferTextureBinding>();
_bufferTextureArrays = new List<BufferTextureArrayBinding<ITextureArray>>();
_bufferImageArrays = new List<BufferTextureArrayBinding<IImageArray>>();
_ranges = new BufferAssignment[Constants.TotalGpUniformBuffers * Constants.ShaderStages]; _ranges = new BufferAssignment[Constants.TotalGpUniformBuffers * Constants.ShaderStages];
} }
/// <summary> /// <summary>
/// Sets the memory range with the index buffer data, to be used for subsequent draw calls. /// Sets the memory range with the index buffer data, to be used for subsequent draw calls.
/// </summary> /// </summary>
@ -418,6 +421,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
return _cpUniformBuffers.Buffers[index].Range.GetSubRange(0).Address; return _cpUniformBuffers.Buffers[index].Range.GetSubRange(0).Address;
} }
/// <summary>
/// Gets the size of the compute uniform buffer currently bound at the given index.
/// </summary>
/// <param name="index">Index of the uniform buffer binding</param>
/// <returns>The uniform buffer size, or an undefined value if the buffer is not currently bound</returns>
public int GetComputeUniformBufferSize(int index)
{
return (int)_cpUniformBuffers.Buffers[index].Range.GetSubRange(0).Size;
}
/// <summary> /// <summary>
/// Gets the address of the graphics uniform buffer currently bound at the given index. /// Gets the address of the graphics uniform buffer currently bound at the given index.
/// </summary> /// </summary>
@ -429,6 +442,17 @@ namespace Ryujinx.Graphics.Gpu.Memory
return _gpUniformBuffers[stage].Buffers[index].Range.GetSubRange(0).Address; return _gpUniformBuffers[stage].Buffers[index].Range.GetSubRange(0).Address;
} }
/// <summary>
/// Gets the size of the graphics uniform buffer currently bound at the given index.
/// </summary>
/// <param name="stage">Index of the shader stage</param>
/// <param name="index">Index of the uniform buffer binding</param>
/// <returns>The uniform buffer size, or an undefined value if the buffer is not currently bound</returns>
public int GetGraphicsUniformBufferSize(int stage, int index)
{
return (int)_gpUniformBuffers[stage].Buffers[index].Range.GetSubRange(0).Size;
}
/// <summary> /// <summary>
/// Gets the bounds of the uniform buffer currently bound at the given index. /// Gets the bounds of the uniform buffer currently bound at the given index.
/// </summary> /// </summary>
@ -459,7 +483,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
BindBuffers(bufferCache, _cpStorageBuffers, isStorage: true); BindBuffers(bufferCache, _cpStorageBuffers, isStorage: true);
BindBuffers(bufferCache, _cpUniformBuffers, isStorage: false); BindBuffers(bufferCache, _cpUniformBuffers, isStorage: false);
CommitBufferTextureBindings(); CommitBufferTextureBindings(bufferCache);
// Force rebind after doing compute work. // Force rebind after doing compute work.
Rebind(); Rebind();
@ -470,14 +494,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary> /// <summary>
/// Commit any queued buffer texture bindings. /// Commit any queued buffer texture bindings.
/// </summary> /// </summary>
private void CommitBufferTextureBindings() /// <param name="bufferCache">Buffer cache</param>
private void CommitBufferTextureBindings(BufferCache bufferCache)
{ {
if (_bufferTextures.Count > 0) if (_bufferTextures.Count > 0)
{ {
foreach (var binding in _bufferTextures) foreach (var binding in _bufferTextures)
{ {
var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore); var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
var range = _channel.MemoryManager.Physical.BufferCache.GetBufferRange(binding.Range, isStore); var range = bufferCache.GetBufferRange(binding.Range, isStore);
binding.Texture.SetStorage(range); binding.Texture.SetStorage(range);
// The texture must be rebound to use the new storage if it was updated. // The texture must be rebound to use the new storage if it was updated.
@ -494,6 +519,33 @@ namespace Ryujinx.Graphics.Gpu.Memory
_bufferTextures.Clear(); _bufferTextures.Clear();
} }
if (_bufferTextureArrays.Count > 0 || _bufferImageArrays.Count > 0)
{
ITexture[] textureArray = new ITexture[1];
foreach (var binding in _bufferTextureArrays)
{
var range = bufferCache.GetBufferRange(binding.Range);
binding.Texture.SetStorage(range);
textureArray[0] = binding.Texture;
binding.Array.SetTextures(binding.Index, textureArray);
}
foreach (var binding in _bufferImageArrays)
{
var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
var range = bufferCache.GetBufferRange(binding.Range, isStore);
binding.Texture.SetStorage(range);
textureArray[0] = binding.Texture;
binding.Array.SetImages(binding.Index, textureArray);
}
_bufferTextureArrays.Clear();
_bufferImageArrays.Clear();
}
} }
/// <summary> /// <summary>
@ -676,7 +728,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
UpdateBuffers(_gpUniformBuffers); UpdateBuffers(_gpUniformBuffers);
} }
CommitBufferTextureBindings(); CommitBufferTextureBindings(bufferCache);
_rebind = false; _rebind = false;
@ -828,6 +880,50 @@ namespace Ryujinx.Graphics.Gpu.Memory
_bufferTextures.Add(new BufferTextureBinding(stage, texture, range, bindingInfo, format, isImage)); _bufferTextures.Add(new BufferTextureBinding(stage, texture, range, bindingInfo, format, isImage));
} }
/// <summary>
/// Sets the buffer storage of a buffer texture array element. This will be bound when the buffer manager commits bindings.
/// </summary>
/// <param name="array">Texture array where the element will be inserted</param>
/// <param name="texture">Buffer texture</param>
/// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
/// <param name="bindingInfo">Binding info for the buffer texture</param>
/// <param name="index">Index of the binding on the array</param>
/// <param name="format">Format of the buffer texture</param>
public void SetBufferTextureStorage(
ITextureArray array,
ITexture texture,
MultiRange range,
TextureBindingInfo bindingInfo,
int index,
Format format)
{
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range);
_bufferTextureArrays.Add(new BufferTextureArrayBinding<ITextureArray>(array, texture, range, bindingInfo, index, format));
}
/// <summary>
/// Sets the buffer storage of a buffer image array element. This will be bound when the buffer manager commits bindings.
/// </summary>
/// <param name="array">Image array where the element will be inserted</param>
/// <param name="texture">Buffer texture</param>
/// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
/// <param name="bindingInfo">Binding info for the buffer texture</param>
/// <param name="index">Index of the binding on the array</param>
/// <param name="format">Format of the buffer texture</param>
public void SetBufferTextureStorage(
IImageArray array,
ITexture texture,
MultiRange range,
TextureBindingInfo bindingInfo,
int index,
Format format)
{
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range);
_bufferImageArrays.Add(new BufferTextureArrayBinding<IImageArray>(array, texture, range, bindingInfo, index, format));
}
/// <summary> /// <summary>
/// Force all bound textures and images to be rebound the next time CommitBindings is called. /// Force all bound textures and images to be rebound the next time CommitBindings is called.
/// </summary> /// </summary>

View file

@ -0,0 +1,65 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Shader;
using Ryujinx.Memory.Range;
using System;
namespace Ryujinx.Graphics.Gpu.Memory
{
/// <summary>
/// A buffer binding to apply to a buffer texture array element.
/// </summary>
readonly struct BufferTextureArrayBinding<T>
{
public T Array { get; }
/// <summary>
/// The buffer texture.
/// </summary>
public ITexture Texture { get; }
/// <summary>
/// Physical ranges of memory where the buffer texture data is located.
/// </summary>
public MultiRange Range { get; }
/// <summary>
/// The image or sampler binding info for the buffer texture.
/// </summary>
public TextureBindingInfo BindingInfo { get; }
/// <summary>
/// Index of the binding on the array.
/// </summary>
public int Index { get; }
/// <summary>
/// The image format for the binding.
/// </summary>
public Format Format { get; }
/// <summary>
/// Create a new buffer texture binding.
/// </summary>
/// <param name="texture">Buffer texture</param>
/// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
/// <param name="bindingInfo">Binding info</param>
/// <param name="index">Index of the binding on the array</param>
/// <param name="format">Binding format</param>
public BufferTextureArrayBinding(
T array,
ITexture texture,
MultiRange range,
TextureBindingInfo bindingInfo,
int index,
Format format)
{
Array = array;
Texture = texture;
Range = range;
BindingInfo = bindingInfo;
Index = index;
Format = format;
}
}
}

View file

@ -17,6 +17,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
public BufferDescriptor[][] ConstantBufferBindings { get; } public BufferDescriptor[][] ConstantBufferBindings { get; }
public BufferDescriptor[][] StorageBufferBindings { get; } public BufferDescriptor[][] StorageBufferBindings { get; }
public int[] TextureCounts { get; }
public int MaxTextureBinding { get; } public int MaxTextureBinding { get; }
public int MaxImageBinding { get; } public int MaxImageBinding { get; }
@ -34,6 +36,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
ConstantBufferBindings = new BufferDescriptor[stageCount][]; ConstantBufferBindings = new BufferDescriptor[stageCount][];
StorageBufferBindings = new BufferDescriptor[stageCount][]; StorageBufferBindings = new BufferDescriptor[stageCount][];
TextureCounts = new int[stageCount];
int maxTextureBinding = -1; int maxTextureBinding = -1;
int maxImageBinding = -1; int maxImageBinding = -1;
int offset = isCompute ? 0 : 1; int offset = isCompute ? 0 : 1;
@ -54,18 +58,25 @@ namespace Ryujinx.Graphics.Gpu.Shader
TextureBindings[i] = stage.Info.Textures.Select(descriptor => TextureBindings[i] = stage.Info.Textures.Select(descriptor =>
{ {
Target target = ShaderTexture.GetTarget(descriptor.Type); Target target = descriptor.Type != SamplerType.None ? ShaderTexture.GetTarget(descriptor.Type) : default;
var result = new TextureBindingInfo( var result = new TextureBindingInfo(
target, target,
descriptor.Binding, descriptor.Binding,
descriptor.ArrayLength,
descriptor.CbufSlot, descriptor.CbufSlot,
descriptor.HandleIndex, descriptor.HandleIndex,
descriptor.Flags); descriptor.Flags,
descriptor.Type == SamplerType.None);
if (descriptor.Binding > maxTextureBinding) if (descriptor.ArrayLength <= 1)
{ {
maxTextureBinding = descriptor.Binding; if (descriptor.Binding > maxTextureBinding)
{
maxTextureBinding = descriptor.Binding;
}
TextureCounts[i]++;
} }
return result; return result;
@ -80,11 +91,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
target, target,
format, format,
descriptor.Binding, descriptor.Binding,
descriptor.ArrayLength,
descriptor.CbufSlot, descriptor.CbufSlot,
descriptor.HandleIndex, descriptor.HandleIndex,
descriptor.Flags); descriptor.Flags);
if (descriptor.Binding > maxImageBinding) if (descriptor.ArrayLength <= 1 && descriptor.Binding > maxImageBinding)
{ {
maxImageBinding = descriptor.Binding; maxImageBinding = descriptor.Binding;
} }

View file

@ -27,6 +27,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// <param name="cb1Data">The constant buffer 1 data of the shader</param> /// <param name="cb1Data">The constant buffer 1 data of the shader</param>
/// <param name="oldSpecState">Shader specialization state of the cached shader</param> /// <param name="oldSpecState">Shader specialization state of the cached shader</param>
/// <param name="newSpecState">Shader specialization state of the recompiled shader</param> /// <param name="newSpecState">Shader specialization state of the recompiled shader</param>
/// <param name="counts">Resource counts shared across all shader stages</param>
/// <param name="stageIndex">Shader stage index</param> /// <param name="stageIndex">Shader stage index</param>
public DiskCacheGpuAccessor( public DiskCacheGpuAccessor(
GpuContext context, GpuContext context,
@ -109,11 +110,10 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
} }
/// <inheritdoc/> /// <inheritdoc/>
public TextureFormat QueryTextureFormat(int handle, int cbufSlot) /// <exception cref="DiskCacheLoadException">Pool length is not available on the cache</exception>
public int QuerySamplerArrayLengthFromPool()
{ {
_newSpecState.RecordTextureFormat(_stageIndex, handle, cbufSlot); return QueryArrayLengthFromPool(isSampler: true);
(uint format, bool formatSrgb) = _oldSpecState.GetFormat(_stageIndex, handle, cbufSlot);
return ConvertToTextureFormat(format, formatSrgb);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -123,6 +123,36 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return _oldSpecState.GetTextureTarget(_stageIndex, handle, cbufSlot).ConvertSamplerType(); return _oldSpecState.GetTextureTarget(_stageIndex, handle, cbufSlot).ConvertSamplerType();
} }
/// <inheritdoc/>
/// <exception cref="DiskCacheLoadException">Constant buffer derived length is not available on the cache</exception>
public int QueryTextureArrayLengthFromBuffer(int slot)
{
if (!_oldSpecState.TextureArrayFromBufferRegistered(_stageIndex, 0, slot))
{
throw new DiskCacheLoadException(DiskCacheLoadResult.MissingTextureArrayLength);
}
int arrayLength = _oldSpecState.GetTextureArrayFromBufferLength(_stageIndex, 0, slot);
_newSpecState.RegisterTextureArrayLengthFromBuffer(_stageIndex, 0, slot, arrayLength);
return arrayLength;
}
/// <inheritdoc/>
/// <exception cref="DiskCacheLoadException">Pool length is not available on the cache</exception>
public int QueryTextureArrayLengthFromPool()
{
return QueryArrayLengthFromPool(isSampler: false);
}
/// <inheritdoc/>
public TextureFormat QueryTextureFormat(int handle, int cbufSlot)
{
_newSpecState.RecordTextureFormat(_stageIndex, handle, cbufSlot);
(uint format, bool formatSrgb) = _oldSpecState.GetFormat(_stageIndex, handle, cbufSlot);
return ConvertToTextureFormat(format, formatSrgb);
}
/// <inheritdoc/> /// <inheritdoc/>
public bool QueryTextureCoordNormalized(int handle, int cbufSlot) public bool QueryTextureCoordNormalized(int handle, int cbufSlot)
{ {
@ -155,6 +185,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
} }
/// <inheritdoc/> /// <inheritdoc/>
/// <exception cref="DiskCacheLoadException">Texture information is not available on the cache</exception>
public void RegisterTexture(int handle, int cbufSlot) public void RegisterTexture(int handle, int cbufSlot)
{ {
if (!_oldSpecState.TextureRegistered(_stageIndex, handle, cbufSlot)) if (!_oldSpecState.TextureRegistered(_stageIndex, handle, cbufSlot))
@ -167,5 +198,24 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
bool coordNormalized = _oldSpecState.GetCoordNormalized(_stageIndex, handle, cbufSlot); bool coordNormalized = _oldSpecState.GetCoordNormalized(_stageIndex, handle, cbufSlot);
_newSpecState.RegisterTexture(_stageIndex, handle, cbufSlot, format, formatSrgb, target, coordNormalized); _newSpecState.RegisterTexture(_stageIndex, handle, cbufSlot, format, formatSrgb, target, coordNormalized);
} }
/// <summary>
/// Gets the cached texture or sampler pool capacity.
/// </summary>
/// <param name="isSampler">True to get sampler pool length, false for texture pool length</param>
/// <returns>Pool length</returns>
/// <exception cref="DiskCacheLoadException">Pool length is not available on the cache</exception>
private int QueryArrayLengthFromPool(bool isSampler)
{
if (!_oldSpecState.TextureArrayFromPoolRegistered(isSampler))
{
throw new DiskCacheLoadException(DiskCacheLoadResult.MissingTextureArrayLength);
}
int arrayLength = _oldSpecState.GetTextureArrayFromPoolLength(isSampler);
_newSpecState.RegisterTextureArrayLengthFromPool(isSampler, arrayLength);
return arrayLength;
}
} }
} }

View file

@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2; private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
private const uint CodeGenVersion = 6462; private const uint CodeGenVersion = 6577;
private const string SharedTocFileName = "shared.toc"; private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data"; private const string SharedDataFileName = "shared.data";

View file

@ -20,6 +20,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// </summary> /// </summary>
InvalidCb1DataLength, InvalidCb1DataLength,
/// <summary>
/// The cache is missing the length of a texture array used by the shader.
/// </summary>
MissingTextureArrayLength,
/// <summary> /// <summary>
/// The cache is missing the descriptor of a texture used by the shader. /// The cache is missing the descriptor of a texture used by the shader.
/// </summary> /// </summary>
@ -60,6 +65,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
DiskCacheLoadResult.Success => "No error.", DiskCacheLoadResult.Success => "No error.",
DiskCacheLoadResult.NoAccess => "Could not access the cache file.", DiskCacheLoadResult.NoAccess => "Could not access the cache file.",
DiskCacheLoadResult.InvalidCb1DataLength => "Constant buffer 1 data length is too low.", DiskCacheLoadResult.InvalidCb1DataLength => "Constant buffer 1 data length is too low.",
DiskCacheLoadResult.MissingTextureArrayLength => "Texture array length missing from the cache file.",
DiskCacheLoadResult.MissingTextureDescriptor => "Texture descriptor missing from the cache file.", DiskCacheLoadResult.MissingTextureDescriptor => "Texture descriptor missing from the cache file.",
DiskCacheLoadResult.FileCorruptedGeneric => "The cache file is corrupted.", DiskCacheLoadResult.FileCorruptedGeneric => "The cache file is corrupted.",
DiskCacheLoadResult.FileCorruptedInvalidMagic => "Magic check failed, the cache file is corrupted.", DiskCacheLoadResult.FileCorruptedInvalidMagic => "Magic check failed, the cache file is corrupted.",

View file

@ -72,6 +72,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
public ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize) public ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize)
{ {
int size = Math.Max(minimumSize, 0x1000 - (int)(address & 0xfff)); int size = Math.Max(minimumSize, 0x1000 - (int)(address & 0xfff));
return MemoryMarshal.Cast<byte, ulong>(_channel.MemoryManager.GetSpan(address, size)); return MemoryMarshal.Cast<byte, ulong>(_channel.MemoryManager.GetSpan(address, size));
} }
@ -119,12 +120,13 @@ namespace Ryujinx.Graphics.Gpu.Shader
return _state.GraphicsState.HasUnalignedStorageBuffer || _state.ComputeState.HasUnalignedStorageBuffer; return _state.GraphicsState.HasUnalignedStorageBuffer || _state.ComputeState.HasUnalignedStorageBuffer;
} }
//// <inheritdoc/> /// <inheritdoc/>
public TextureFormat QueryTextureFormat(int handle, int cbufSlot) public int QuerySamplerArrayLengthFromPool()
{ {
_state.SpecializationState?.RecordTextureFormat(_stageIndex, handle, cbufSlot); int length = _state.SamplerPoolMaximumId + 1;
var descriptor = GetTextureDescriptor(handle, cbufSlot); _state.SpecializationState?.RegisterTextureArrayLengthFromPool(isSampler: true, length);
return ConvertToTextureFormat(descriptor.UnpackFormat(), descriptor.UnpackSrgb());
return length;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -134,6 +136,37 @@ namespace Ryujinx.Graphics.Gpu.Shader
return GetTextureDescriptor(handle, cbufSlot).UnpackTextureTarget().ConvertSamplerType(); return GetTextureDescriptor(handle, cbufSlot).UnpackTextureTarget().ConvertSamplerType();
} }
/// <inheritdoc/>
public int QueryTextureArrayLengthFromBuffer(int slot)
{
int size = _compute
? _channel.BufferManager.GetComputeUniformBufferSize(slot)
: _channel.BufferManager.GetGraphicsUniformBufferSize(_stageIndex, slot);
int arrayLength = size / Constants.TextureHandleSizeInBytes;
_state.SpecializationState?.RegisterTextureArrayLengthFromBuffer(_stageIndex, 0, slot, arrayLength);
return arrayLength;
}
/// <inheritdoc/>
public int QueryTextureArrayLengthFromPool()
{
int length = _state.PoolState.TexturePoolMaximumId + 1;
_state.SpecializationState?.RegisterTextureArrayLengthFromPool(isSampler: false, length);
return length;
}
//// <inheritdoc/>
public TextureFormat QueryTextureFormat(int handle, int cbufSlot)
{
_state.SpecializationState?.RecordTextureFormat(_stageIndex, handle, cbufSlot);
var descriptor = GetTextureDescriptor(handle, cbufSlot);
return ConvertToTextureFormat(descriptor.UnpackFormat(), descriptor.UnpackSrgb());
}
/// <inheritdoc/> /// <inheritdoc/>
public bool QueryTextureCoordNormalized(int handle, int cbufSlot) public bool QueryTextureCoordNormalized(int handle, int cbufSlot)
{ {

View file

@ -20,6 +20,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
private int _reservedTextures; private int _reservedTextures;
private int _reservedImages; private int _reservedImages;
private int _staticTexturesCount;
private int _staticImagesCount;
/// <summary> /// <summary>
/// Creates a new GPU accessor. /// Creates a new GPU accessor.
/// </summary> /// </summary>
@ -48,7 +51,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
_reservedImages = rrc.ReservedImages; _reservedImages = rrc.ReservedImages;
} }
public int QueryBindingConstantBuffer(int index) public int CreateConstantBufferBinding(int index)
{ {
int binding; int binding;
@ -64,7 +67,39 @@ namespace Ryujinx.Graphics.Gpu.Shader
return binding + _reservedConstantBuffers; return binding + _reservedConstantBuffers;
} }
public int QueryBindingStorageBuffer(int index) public int CreateImageBinding(int count, bool isBuffer)
{
int binding;
if (_context.Capabilities.Api == TargetApi.Vulkan)
{
if (count == 1)
{
int index = _staticImagesCount++;
if (isBuffer)
{
index += (int)_context.Capabilities.MaximumImagesPerStage;
}
binding = GetBindingFromIndex(index, _context.Capabilities.MaximumImagesPerStage * 2, "Image");
}
else
{
binding = (int)GetDynamicBaseIndexDual(_context.Capabilities.MaximumImagesPerStage) + _resourceCounts.ImagesCount++;
}
}
else
{
binding = _resourceCounts.ImagesCount;
_resourceCounts.ImagesCount += count;
}
return binding + _reservedImages;
}
public int CreateStorageBufferBinding(int index)
{ {
int binding; int binding;
@ -80,48 +115,38 @@ namespace Ryujinx.Graphics.Gpu.Shader
return binding + _reservedStorageBuffers; return binding + _reservedStorageBuffers;
} }
public int QueryBindingTexture(int index, bool isBuffer) public int CreateTextureBinding(int count, bool isBuffer)
{ {
int binding; int binding;
if (_context.Capabilities.Api == TargetApi.Vulkan) if (_context.Capabilities.Api == TargetApi.Vulkan)
{ {
if (isBuffer) if (count == 1)
{ {
index += (int)_context.Capabilities.MaximumTexturesPerStage; int index = _staticTexturesCount++;
}
binding = GetBindingFromIndex(index, _context.Capabilities.MaximumTexturesPerStage * 2, "Texture"); if (isBuffer)
{
index += (int)_context.Capabilities.MaximumTexturesPerStage;
}
binding = GetBindingFromIndex(index, _context.Capabilities.MaximumTexturesPerStage * 2, "Texture");
}
else
{
binding = (int)GetDynamicBaseIndexDual(_context.Capabilities.MaximumTexturesPerStage) + _resourceCounts.TexturesCount++;
}
} }
else else
{ {
binding = _resourceCounts.TexturesCount++; binding = _resourceCounts.TexturesCount;
_resourceCounts.TexturesCount += count;
} }
return binding + _reservedTextures; return binding + _reservedTextures;
} }
public int QueryBindingImage(int index, bool isBuffer)
{
int binding;
if (_context.Capabilities.Api == TargetApi.Vulkan)
{
if (isBuffer)
{
index += (int)_context.Capabilities.MaximumImagesPerStage;
}
binding = GetBindingFromIndex(index, _context.Capabilities.MaximumImagesPerStage * 2, "Image");
}
else
{
binding = _resourceCounts.ImagesCount++;
}
return binding + _reservedImages;
}
private int GetBindingFromIndex(int index, uint maxPerStage, string resourceName) private int GetBindingFromIndex(int index, uint maxPerStage, string resourceName)
{ {
if ((uint)index >= maxPerStage) if ((uint)index >= maxPerStage)
@ -148,6 +173,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
}; };
} }
private static uint GetDynamicBaseIndexDual(uint maxPerStage)
{
return GetDynamicBaseIndex(maxPerStage) * 2;
}
private static uint GetDynamicBaseIndex(uint maxPerStage)
{
return maxPerStage * Constants.ShaderStages;
}
public int QueryHostGatherBiasPrecision() => _context.Capabilities.GatherBiasPrecision; public int QueryHostGatherBiasPrecision() => _context.Capabilities.GatherBiasPrecision;
public bool QueryHostReducedPrecision() => _context.Capabilities.ReduceShaderPrecision; public bool QueryHostReducedPrecision() => _context.Capabilities.ReduceShaderPrecision;

View file

@ -5,6 +5,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary> /// </summary>
class GpuAccessorState class GpuAccessorState
{ {
/// <summary>
/// Maximum ID that a sampler pool entry may have.
/// </summary>
public readonly int SamplerPoolMaximumId;
/// <summary> /// <summary>
/// GPU texture pool state. /// GPU texture pool state.
/// </summary> /// </summary>
@ -38,18 +43,21 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <summary> /// <summary>
/// Creates a new GPU accessor state. /// Creates a new GPU accessor state.
/// </summary> /// </summary>
/// <param name="samplerPoolMaximumId">Maximum ID that a sampler pool entry may have</param>
/// <param name="poolState">GPU texture pool state</param> /// <param name="poolState">GPU texture pool state</param>
/// <param name="computeState">GPU compute state, for compute shaders</param> /// <param name="computeState">GPU compute state, for compute shaders</param>
/// <param name="graphicsState">GPU graphics state, for vertex, tessellation, geometry and fragment shaders</param> /// <param name="graphicsState">GPU graphics state, for vertex, tessellation, geometry and fragment shaders</param>
/// <param name="specializationState">Shader specialization state (shared by all stages)</param> /// <param name="specializationState">Shader specialization state (shared by all stages)</param>
/// <param name="transformFeedbackDescriptors">Transform feedback information, if the shader uses transform feedback. Otherwise, should be null</param> /// <param name="transformFeedbackDescriptors">Transform feedback information, if the shader uses transform feedback. Otherwise, should be null</param>
public GpuAccessorState( public GpuAccessorState(
int samplerPoolMaximumId,
GpuChannelPoolState poolState, GpuChannelPoolState poolState,
GpuChannelComputeState computeState, GpuChannelComputeState computeState,
GpuChannelGraphicsState graphicsState, GpuChannelGraphicsState graphicsState,
ShaderSpecializationState specializationState, ShaderSpecializationState specializationState,
TransformFeedbackDescriptor[] transformFeedbackDescriptors = null) TransformFeedbackDescriptor[] transformFeedbackDescriptors = null)
{ {
SamplerPoolMaximumId = samplerPoolMaximumId;
PoolState = poolState; PoolState = poolState;
GraphicsState = graphicsState; GraphicsState = graphicsState;
ComputeState = computeState; ComputeState = computeState;

View file

@ -2,7 +2,6 @@ using System;
namespace Ryujinx.Graphics.Gpu.Shader namespace Ryujinx.Graphics.Gpu.Shader
{ {
#pragma warning disable CS0659 // Class overrides Object.Equals(object o) but does not override Object.GetHashCode()
/// <summary> /// <summary>
/// State used by the <see cref="GpuAccessor"/>. /// State used by the <see cref="GpuAccessor"/>.
/// </summary> /// </summary>
@ -28,6 +27,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary> /// </summary>
/// <param name="texturePoolGpuVa">GPU virtual address of the texture pool</param> /// <param name="texturePoolGpuVa">GPU virtual address of the texture pool</param>
/// <param name="texturePoolMaximumId">Maximum ID of the texture pool</param> /// <param name="texturePoolMaximumId">Maximum ID of the texture pool</param>
/// <param name="samplerPoolMaximumId">Maximum ID of the sampler pool</param>
/// <param name="textureBufferIndex">Constant buffer slot where the texture handles are located</param> /// <param name="textureBufferIndex">Constant buffer slot where the texture handles are located</param>
public GpuChannelPoolState(ulong texturePoolGpuVa, int texturePoolMaximumId, int textureBufferIndex) public GpuChannelPoolState(ulong texturePoolGpuVa, int texturePoolMaximumId, int textureBufferIndex)
{ {
@ -52,6 +52,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
{ {
return obj is GpuChannelPoolState state && Equals(state); return obj is GpuChannelPoolState state && Equals(state);
} }
public override int GetHashCode()
{
return HashCode.Combine(TexturePoolGpuVa, TexturePoolMaximumId, TextureBufferIndex);
}
} }
#pragma warning restore CS0659
} }

View file

@ -192,12 +192,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// This automatically translates, compiles and adds the code to the cache if not present. /// This automatically translates, compiles and adds the code to the cache if not present.
/// </remarks> /// </remarks>
/// <param name="channel">GPU channel</param> /// <param name="channel">GPU channel</param>
/// <param name="samplerPoolMaximumId">Maximum ID that an entry in the sampler pool may have</param>
/// <param name="poolState">Texture pool state</param> /// <param name="poolState">Texture pool state</param>
/// <param name="computeState">Compute engine state</param> /// <param name="computeState">Compute engine state</param>
/// <param name="gpuVa">GPU virtual address of the binary shader code</param> /// <param name="gpuVa">GPU virtual address of the binary shader code</param>
/// <returns>Compiled compute shader code</returns> /// <returns>Compiled compute shader code</returns>
public CachedShaderProgram GetComputeShader( public CachedShaderProgram GetComputeShader(
GpuChannel channel, GpuChannel channel,
int samplerPoolMaximumId,
GpuChannelPoolState poolState, GpuChannelPoolState poolState,
GpuChannelComputeState computeState, GpuChannelComputeState computeState,
ulong gpuVa) ulong gpuVa)
@ -214,7 +216,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
} }
ShaderSpecializationState specState = new(ref computeState); ShaderSpecializationState specState = new(ref computeState);
GpuAccessorState gpuAccessorState = new(poolState, computeState, default, specState); GpuAccessorState gpuAccessorState = new(samplerPoolMaximumId, poolState, computeState, default, specState);
GpuAccessor gpuAccessor = new(_context, channel, gpuAccessorState); GpuAccessor gpuAccessor = new(_context, channel, gpuAccessorState);
gpuAccessor.InitializeReservedCounts(tfEnabled: false, vertexAsCompute: false); gpuAccessor.InitializeReservedCounts(tfEnabled: false, vertexAsCompute: false);
@ -291,6 +293,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="state">GPU state</param> /// <param name="state">GPU state</param>
/// <param name="pipeline">Pipeline state</param> /// <param name="pipeline">Pipeline state</param>
/// <param name="channel">GPU channel</param> /// <param name="channel">GPU channel</param>
/// <param name="samplerPoolMaximumId">Maximum ID that an entry in the sampler pool may have</param>
/// <param name="poolState">Texture pool state</param> /// <param name="poolState">Texture pool state</param>
/// <param name="graphicsState">3D engine state</param> /// <param name="graphicsState">3D engine state</param>
/// <param name="addresses">Addresses of the shaders for each stage</param> /// <param name="addresses">Addresses of the shaders for each stage</param>
@ -299,6 +302,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
ref ThreedClassState state, ref ThreedClassState state,
ref ProgramPipelineState pipeline, ref ProgramPipelineState pipeline,
GpuChannel channel, GpuChannel channel,
int samplerPoolMaximumId,
ref GpuChannelPoolState poolState, ref GpuChannelPoolState poolState,
ref GpuChannelGraphicsState graphicsState, ref GpuChannelGraphicsState graphicsState,
ShaderAddresses addresses) ShaderAddresses addresses)
@ -319,7 +323,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
UpdatePipelineInfo(ref state, ref pipeline, graphicsState, channel); UpdatePipelineInfo(ref state, ref pipeline, graphicsState, channel);
ShaderSpecializationState specState = new(ref graphicsState, ref pipeline, transformFeedbackDescriptors); ShaderSpecializationState specState = new(ref graphicsState, ref pipeline, transformFeedbackDescriptors);
GpuAccessorState gpuAccessorState = new(poolState, default, graphicsState, specState, transformFeedbackDescriptors); GpuAccessorState gpuAccessorState = new(samplerPoolMaximumId, poolState, default, graphicsState, specState, transformFeedbackDescriptors);
ReadOnlySpan<ulong> addressesSpan = addresses.AsSpan(); ReadOnlySpan<ulong> addressesSpan = addresses.AsSpan();

View file

@ -132,6 +132,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
AddDualDescriptor(stages, ResourceType.TextureAndSampler, ResourceType.BufferTexture, TextureSetIndex, textureBinding, texturesPerStage); AddDualDescriptor(stages, ResourceType.TextureAndSampler, ResourceType.BufferTexture, TextureSetIndex, textureBinding, texturesPerStage);
AddDualDescriptor(stages, ResourceType.Image, ResourceType.BufferImage, ImageSetIndex, imageBinding, imagesPerStage); AddDualDescriptor(stages, ResourceType.Image, ResourceType.BufferImage, ImageSetIndex, imageBinding, imagesPerStage);
AddArrayDescriptors(info.Textures, stages, TextureSetIndex, isImage: false);
AddArrayDescriptors(info.Images, stages, TextureSetIndex, isImage: true);
AddUsage(info.CBuffers, stages, UniformSetIndex, isStorage: false); AddUsage(info.CBuffers, stages, UniformSetIndex, isStorage: false);
AddUsage(info.SBuffers, stages, StorageSetIndex, isStorage: true); AddUsage(info.SBuffers, stages, StorageSetIndex, isStorage: true);
AddUsage(info.Textures, stages, TextureSetIndex, isImage: false); AddUsage(info.Textures, stages, TextureSetIndex, isImage: false);
@ -169,6 +172,26 @@ namespace Ryujinx.Graphics.Gpu.Shader
AddDescriptor(stages, type2, setIndex, binding + count, count); AddDescriptor(stages, type2, setIndex, binding + count, count);
} }
/// <summary>
/// Adds all array descriptors (those with an array length greater than one).
/// </summary>
/// <param name="textures">Textures to be added</param>
/// <param name="stages">Stages where the textures are used</param>
/// <param name="setIndex">Descriptor set index where the textures will be bound</param>
/// <param name="isImage">True for images, false for textures</param>
private void AddArrayDescriptors(IEnumerable<TextureDescriptor> textures, ResourceStages stages, int setIndex, bool isImage)
{
foreach (TextureDescriptor texture in textures)
{
if (texture.ArrayLength > 1)
{
ResourceType type = GetTextureResourceType(texture, isImage);
_resourceDescriptors[setIndex].Add(new ResourceDescriptor(texture.Binding, texture.ArrayLength, type, stages));
}
}
}
/// <summary> /// <summary>
/// Adds buffer usage information to the list of usages. /// Adds buffer usage information to the list of usages.
/// </summary> /// </summary>
@ -181,7 +204,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{ {
for (int index = 0; index < count; index++) for (int index = 0; index < count; index++)
{ {
_resourceUsages[setIndex].Add(new ResourceUsage(binding + index, type, stages)); _resourceUsages[setIndex].Add(new ResourceUsage(binding + index, 1, type, stages));
} }
} }
@ -198,6 +221,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{ {
_resourceUsages[setIndex].Add(new ResourceUsage( _resourceUsages[setIndex].Add(new ResourceUsage(
buffer.Binding, buffer.Binding,
1,
isStorage ? ResourceType.StorageBuffer : ResourceType.UniformBuffer, isStorage ? ResourceType.StorageBuffer : ResourceType.UniformBuffer,
stages)); stages));
} }
@ -214,16 +238,35 @@ namespace Ryujinx.Graphics.Gpu.Shader
{ {
foreach (TextureDescriptor texture in textures) foreach (TextureDescriptor texture in textures)
{ {
bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer; ResourceType type = GetTextureResourceType(texture, isImage);
ResourceType type = isBuffer _resourceUsages[setIndex].Add(new ResourceUsage(texture.Binding, texture.ArrayLength, type, stages));
? (isImage ? ResourceType.BufferImage : ResourceType.BufferTexture) }
: (isImage ? ResourceType.Image : ResourceType.TextureAndSampler); }
_resourceUsages[setIndex].Add(new ResourceUsage( private static ResourceType GetTextureResourceType(TextureDescriptor texture, bool isImage)
texture.Binding, {
type, bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer;
stages));
if (isBuffer)
{
return isImage ? ResourceType.BufferImage : ResourceType.BufferTexture;
}
else if (isImage)
{
return ResourceType.Image;
}
else if (texture.Type == SamplerType.None)
{
return ResourceType.Sampler;
}
else if (texture.Separate)
{
return ResourceType.Texture;
}
else
{
return ResourceType.TextureAndSampler;
} }
} }

View file

@ -30,6 +30,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
{ {
PrimitiveTopology = 1 << 1, PrimitiveTopology = 1 << 1,
TransformFeedback = 1 << 3, TransformFeedback = 1 << 3,
TextureArrayFromBuffer = 1 << 4,
TextureArrayFromPool = 1 << 5,
} }
private QueriedStateFlags _queriedState; private QueriedStateFlags _queriedState;
@ -153,6 +155,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
} }
private readonly Dictionary<TextureKey, Box<TextureSpecializationState>> _textureSpecialization; private readonly Dictionary<TextureKey, Box<TextureSpecializationState>> _textureSpecialization;
private readonly Dictionary<TextureKey, int> _textureArrayFromBufferSpecialization;
private readonly Dictionary<bool, int> _textureArrayFromPoolSpecialization;
private KeyValuePair<TextureKey, Box<TextureSpecializationState>>[] _allTextures; private KeyValuePair<TextureKey, Box<TextureSpecializationState>>[] _allTextures;
private Box<TextureSpecializationState>[][] _textureByBinding; private Box<TextureSpecializationState>[][] _textureByBinding;
private Box<TextureSpecializationState>[][] _imageByBinding; private Box<TextureSpecializationState>[][] _imageByBinding;
@ -163,6 +167,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
private ShaderSpecializationState() private ShaderSpecializationState()
{ {
_textureSpecialization = new Dictionary<TextureKey, Box<TextureSpecializationState>>(); _textureSpecialization = new Dictionary<TextureKey, Box<TextureSpecializationState>>();
_textureArrayFromBufferSpecialization = new Dictionary<TextureKey, int>();
_textureArrayFromPoolSpecialization = new Dictionary<bool, int>();
} }
/// <summary> /// <summary>
@ -323,6 +329,30 @@ namespace Ryujinx.Graphics.Gpu.Shader
state.Value.CoordNormalized = coordNormalized; state.Value.CoordNormalized = coordNormalized;
} }
/// <summary>
/// Registers the length of a texture array calculated from a constant buffer size.
/// </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>
/// <param name="length">Number of elements in the texture array</param>
public void RegisterTextureArrayLengthFromBuffer(int stageIndex, int handle, int cbufSlot, int length)
{
_textureArrayFromBufferSpecialization[new TextureKey(stageIndex, handle, cbufSlot)] = length;
_queriedState |= QueriedStateFlags.TextureArrayFromBuffer;
}
/// <summary>
/// Registers the length of a texture array calculated from a texture or sampler pool capacity.
/// </summary>
/// <param name="isSampler">True for sampler pool, false for texture pool</param>
/// <param name="length">Number of elements in the texture array</param>
public void RegisterTextureArrayLengthFromPool(bool isSampler, int length)
{
_textureArrayFromPoolSpecialization[isSampler] = length;
_queriedState |= QueriedStateFlags.TextureArrayFromPool;
}
/// <summary> /// <summary>
/// Indicates that the format of a given texture was used during the shader translation process. /// Indicates that the format of a given texture was used during the shader translation process.
/// </summary> /// </summary>
@ -379,12 +409,35 @@ namespace Ryujinx.Graphics.Gpu.Shader
return GetTextureSpecState(stageIndex, handle, cbufSlot) != null; return GetTextureSpecState(stageIndex, handle, cbufSlot) != null;
} }
/// <summary>
/// Checks if a given texture array (from constant buffer) was registerd on this specialization state.
/// </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>True if the length for the given buffer and stage exists, false otherwise</returns>
public bool TextureArrayFromBufferRegistered(int stageIndex, int handle, int cbufSlot)
{
return _textureArrayFromBufferSpecialization.ContainsKey(new TextureKey(stageIndex, handle, cbufSlot));
}
/// <summary>
/// Checks if a given texture array (from a sampler pool or texture pool) was registerd on this specialization state.
/// </summary>
/// <param name="isSampler">True for sampler pool, false for texture pool</param>
/// <returns>True if the length for the given pool, false otherwise</returns>
public bool TextureArrayFromPoolRegistered(bool isSampler)
{
return _textureArrayFromPoolSpecialization.ContainsKey(isSampler);
}
/// <summary> /// <summary>
/// Gets the recorded format of a given texture. /// Gets the recorded format of a given texture.
/// </summary> /// </summary>
/// <param name="stageIndex">Shader stage where the texture is used</param> /// <param name="stageIndex">Shader stage where the texture is used</param>
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param> /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param> /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
/// <returns>Format and sRGB tuple</returns>
public (uint, bool) GetFormat(int stageIndex, int handle, int cbufSlot) public (uint, bool) GetFormat(int stageIndex, int handle, int cbufSlot)
{ {
TextureSpecializationState state = GetTextureSpecState(stageIndex, handle, cbufSlot).Value; TextureSpecializationState state = GetTextureSpecState(stageIndex, handle, cbufSlot).Value;
@ -397,6 +450,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="stageIndex">Shader stage where the texture is used</param> /// <param name="stageIndex">Shader stage where the texture is used</param>
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param> /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param> /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
/// <returns>Texture target</returns>
public TextureTarget GetTextureTarget(int stageIndex, int handle, int cbufSlot) public TextureTarget GetTextureTarget(int stageIndex, int handle, int cbufSlot)
{ {
return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.TextureTarget; return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.TextureTarget;
@ -408,11 +462,34 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="stageIndex">Shader stage where the texture is used</param> /// <param name="stageIndex">Shader stage where the texture is used</param>
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param> /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param> /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
/// <returns>True if coordinates are normalized, false otherwise</returns>
public bool GetCoordNormalized(int stageIndex, int handle, int cbufSlot) public bool GetCoordNormalized(int stageIndex, int handle, int cbufSlot)
{ {
return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.CoordNormalized; return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.CoordNormalized;
} }
/// <summary>
/// Gets the recorded length of a given texture array (from constant buffer).
/// </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>Texture array length</returns>
public int GetTextureArrayFromBufferLength(int stageIndex, int handle, int cbufSlot)
{
return _textureArrayFromBufferSpecialization[new TextureKey(stageIndex, handle, cbufSlot)];
}
/// <summary>
/// Gets the recorded length of a given texture array (from a sampler or texture pool).
/// </summary>
/// <param name="isSampler">True to get the sampler pool length, false to get the texture pool length</param>
/// <returns>Texture array length</returns>
public int GetTextureArrayFromPoolLength(bool isSampler)
{
return _textureArrayFromPoolSpecialization[isSampler];
}
/// <summary> /// <summary>
/// Gets texture specialization state for a given texture, or create a new one if not present. /// Gets texture specialization state for a given texture, or create a new one if not present.
/// </summary> /// </summary>
@ -548,6 +625,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
return Matches(channel, ref poolState, checkTextures, isCompute: false); return Matches(channel, ref poolState, checkTextures, isCompute: false);
} }
/// <summary>
/// Converts special vertex attribute groups to their generic equivalents, for comparison purposes.
/// </summary>
/// <param name="channel">GPU channel</param>
/// <param name="type">Vertex attribute type</param>
/// <returns>Filtered attribute</returns>
private static AttributeType FilterAttributeType(GpuChannel channel, AttributeType type) private static AttributeType FilterAttributeType(GpuChannel channel, AttributeType type)
{ {
type &= ~(AttributeType.Packed | AttributeType.PackedRgb10A2Signed); type &= ~(AttributeType.Packed | AttributeType.PackedRgb10A2Signed);
@ -838,6 +921,38 @@ namespace Ryujinx.Graphics.Gpu.Shader
specState._textureSpecialization[textureKey] = textureState; specState._textureSpecialization[textureKey] = textureState;
} }
if (specState._queriedState.HasFlag(QueriedStateFlags.TextureArrayFromBuffer))
{
dataReader.Read(ref count);
for (int index = 0; index < count; index++)
{
TextureKey textureKey = default;
int length = 0;
dataReader.ReadWithMagicAndSize(ref textureKey, TexkMagic);
dataReader.Read(ref length);
specState._textureArrayFromBufferSpecialization[textureKey] = length;
}
}
if (specState._queriedState.HasFlag(QueriedStateFlags.TextureArrayFromPool))
{
dataReader.Read(ref count);
for (int index = 0; index < count; index++)
{
bool textureKey = default;
int length = 0;
dataReader.ReadWithMagicAndSize(ref textureKey, TexkMagic);
dataReader.Read(ref length);
specState._textureArrayFromPoolSpecialization[textureKey] = length;
}
}
return specState; return specState;
} }
@ -902,6 +1017,36 @@ namespace Ryujinx.Graphics.Gpu.Shader
dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic); dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic);
dataWriter.WriteWithMagicAndSize(ref textureState.Value, TexsMagic); dataWriter.WriteWithMagicAndSize(ref textureState.Value, TexsMagic);
} }
if (_queriedState.HasFlag(QueriedStateFlags.TextureArrayFromBuffer))
{
count = (ushort)_textureArrayFromBufferSpecialization.Count;
dataWriter.Write(ref count);
foreach (var kv in _textureArrayFromBufferSpecialization)
{
var textureKey = kv.Key;
var length = kv.Value;
dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic);
dataWriter.Write(ref length);
}
}
if (_queriedState.HasFlag(QueriedStateFlags.TextureArrayFromPool))
{
count = (ushort)_textureArrayFromPoolSpecialization.Count;
dataWriter.Write(ref count);
foreach (var kv in _textureArrayFromPoolSpecialization)
{
var textureKey = kv.Key;
var length = kv.Value;
dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic);
dataWriter.Write(ref length);
}
}
} }
} }
} }

View file

@ -0,0 +1,67 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Graphics.GAL;
using System;
namespace Ryujinx.Graphics.OpenGL.Image
{
class ImageArray : IImageArray
{
private record struct TextureRef
{
public int Handle;
public Format Format;
}
private readonly TextureRef[] _images;
public ImageArray(int size)
{
_images = new TextureRef[size];
}
public void SetFormats(int index, GAL.Format[] imageFormats)
{
for (int i = 0; i < imageFormats.Length; i++)
{
_images[index + i].Format = imageFormats[i];
}
}
public void SetImages(int index, ITexture[] images)
{
for (int i = 0; i < images.Length; i++)
{
ITexture image = images[i];
if (image is TextureBase imageBase)
{
_images[index + i].Handle = imageBase.Handle;
}
else
{
_images[index + i].Handle = 0;
}
}
}
public void Bind(int baseBinding)
{
for (int i = 0; i < _images.Length; i++)
{
if (_images[i].Handle == 0)
{
GL.BindImageTexture(baseBinding + i, 0, 0, true, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8);
}
else
{
SizedInternalFormat format = FormatTable.GetImageFormat(_images[i].Format);
if (format != 0)
{
GL.BindImageTexture(baseBinding + i, _images[i].Handle, 0, true, 0, TextureAccess.ReadWrite, format);
}
}
}
}
}
}

View file

@ -0,0 +1,52 @@
using Ryujinx.Graphics.GAL;
namespace Ryujinx.Graphics.OpenGL.Image
{
class TextureArray : ITextureArray
{
private record struct TextureRef
{
public TextureBase Texture;
public Sampler Sampler;
}
private readonly TextureRef[] _textureRefs;
public TextureArray(int size)
{
_textureRefs = new TextureRef[size];
}
public void SetSamplers(int index, ISampler[] samplers)
{
for (int i = 0; i < samplers.Length; i++)
{
_textureRefs[index + i].Sampler = samplers[i] as Sampler;
}
}
public void SetTextures(int index, ITexture[] textures)
{
for (int i = 0; i < textures.Length; i++)
{
_textureRefs[index + i].Texture = textures[i] as TextureBase;
}
}
public void Bind(int baseBinding)
{
for (int i = 0; i < _textureRefs.Length; i++)
{
if (_textureRefs[i].Texture != null)
{
_textureRefs[i].Texture.Bind(baseBinding + i);
_textureRefs[i].Sampler?.Bind(baseBinding + i);
}
else
{
TextureBase.ClearBinding(baseBinding + i);
}
}
}
}
}

View file

@ -90,6 +90,11 @@ namespace Ryujinx.Graphics.OpenGL
throw new NotSupportedException(); throw new NotSupportedException();
} }
public IImageArray CreateImageArray(int size, bool isBuffer)
{
return new ImageArray(size);
}
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info) public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
{ {
return new Program(shaders, info.FragmentOutputMap); return new Program(shaders, info.FragmentOutputMap);
@ -112,6 +117,11 @@ namespace Ryujinx.Graphics.OpenGL
} }
} }
public ITextureArray CreateTextureArray(int size, bool isBuffer)
{
return new TextureArray(size);
}
public void DeleteBuffer(BufferHandle buffer) public void DeleteBuffer(BufferHandle buffer)
{ {
PersistentBuffers.Unmap(buffer); PersistentBuffers.Unmap(buffer);

View file

@ -958,6 +958,11 @@ namespace Ryujinx.Graphics.OpenGL
} }
} }
public void SetImageArray(ShaderStage stage, int binding, IImageArray array)
{
(array as ImageArray).Bind(binding);
}
public void SetIndexBuffer(BufferRange buffer, IndexType type) public void SetIndexBuffer(BufferRange buffer, IndexType type)
{ {
_elementsType = type.Convert(); _elementsType = type.Convert();
@ -1302,6 +1307,10 @@ namespace Ryujinx.Graphics.OpenGL
} }
} }
public void SetTextureArray(ShaderStage stage, int binding, ITextureArray array)
{
(array as TextureArray).Bind(binding);
}
public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers) public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers)
{ {

View file

@ -6,7 +6,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Numerics;
namespace Ryujinx.Graphics.Shader.CodeGen.Glsl namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{ {
@ -339,27 +338,20 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
private static void DeclareSamplers(CodeGenContext context, IEnumerable<TextureDefinition> definitions) private static void DeclareSamplers(CodeGenContext context, IEnumerable<TextureDefinition> definitions)
{ {
int arraySize = 0;
foreach (var definition in definitions) foreach (var definition in definitions)
{ {
string indexExpr = string.Empty; string arrayDecl = string.Empty;
if (definition.Type.HasFlag(SamplerType.Indexed)) if (definition.ArrayLength > 1)
{ {
if (arraySize == 0) arrayDecl = $"[{NumberFormatter.FormatInt(definition.ArrayLength)}]";
{ }
arraySize = ResourceManager.SamplerArraySize; else if (definition.ArrayLength == 0)
} {
else if (--arraySize != 0) arrayDecl = "[]";
{
continue;
}
indexExpr = $"[{NumberFormatter.FormatInt(arraySize)}]";
} }
string samplerTypeName = definition.Type.ToGlslSamplerType(); string samplerTypeName = definition.Separate ? definition.Type.ToGlslTextureType() : definition.Type.ToGlslSamplerType();
string layout = string.Empty; string layout = string.Empty;
@ -368,30 +360,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
layout = $", set = {definition.Set}"; layout = $", set = {definition.Set}";
} }
context.AppendLine($"layout (binding = {definition.Binding}{layout}) uniform {samplerTypeName} {definition.Name}{indexExpr};"); context.AppendLine($"layout (binding = {definition.Binding}{layout}) uniform {samplerTypeName} {definition.Name}{arrayDecl};");
} }
} }
private static void DeclareImages(CodeGenContext context, IEnumerable<TextureDefinition> definitions) private static void DeclareImages(CodeGenContext context, IEnumerable<TextureDefinition> definitions)
{ {
int arraySize = 0;
foreach (var definition in definitions) foreach (var definition in definitions)
{ {
string indexExpr = string.Empty; string arrayDecl = string.Empty;
if (definition.Type.HasFlag(SamplerType.Indexed)) if (definition.ArrayLength > 1)
{ {
if (arraySize == 0) arrayDecl = $"[{NumberFormatter.FormatInt(definition.ArrayLength)}]";
{ }
arraySize = ResourceManager.SamplerArraySize; else if (definition.ArrayLength == 0)
} {
else if (--arraySize != 0) arrayDecl = "[]";
{
continue;
}
indexExpr = $"[{NumberFormatter.FormatInt(arraySize)}]";
} }
string imageTypeName = definition.Type.ToGlslImageType(definition.Format.GetComponentType()); string imageTypeName = definition.Type.ToGlslImageType(definition.Format.GetComponentType());
@ -413,7 +398,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
layout = $", set = {definition.Set}{layout}"; layout = $", set = {definition.Set}{layout}";
} }
context.AppendLine($"layout (binding = {definition.Binding}{layout}) uniform {imageTypeName} {definition.Name}{indexExpr};"); context.AppendLine($"layout (binding = {definition.Binding}{layout}) uniform {imageTypeName} {definition.Name}{arrayDecl};");
} }
} }

View file

@ -38,7 +38,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
AggregateType type = GetSrcVarType(operation.Inst, 0); AggregateType type = GetSrcVarType(operation.Inst, 0);
string srcExpr = GetSoureExpr(context, src, type); string srcExpr = GetSourceExpr(context, src, type);
string zero; string zero;
if (type == AggregateType.FP64) if (type == AggregateType.FP64)
@ -80,7 +80,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
for (int argIndex = operation.SourcesCount - arity + 2; argIndex < operation.SourcesCount; argIndex++) for (int argIndex = operation.SourcesCount - arity + 2; argIndex < operation.SourcesCount; argIndex++)
{ {
builder.Append($", {GetSoureExpr(context, operation.GetSource(argIndex), dstType)}"); builder.Append($", {GetSourceExpr(context, operation.GetSource(argIndex), dstType)}");
} }
} }
else else
@ -94,7 +94,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
AggregateType dstType = GetSrcVarType(inst, argIndex); AggregateType dstType = GetSrcVarType(inst, argIndex);
builder.Append(GetSoureExpr(context, operation.GetSource(argIndex), dstType)); builder.Append(GetSourceExpr(context, operation.GetSource(argIndex), dstType));
} }
} }
@ -107,7 +107,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
// Return may optionally have a return value (and in this case it is unary). // Return may optionally have a return value (and in this case it is unary).
if (inst == Instruction.Return && operation.SourcesCount != 0) if (inst == Instruction.Return && operation.SourcesCount != 0)
{ {
return $"{op} {GetSoureExpr(context, operation.GetSource(0), context.CurrentFunction.ReturnType)}"; return $"{op} {GetSourceExpr(context, operation.GetSource(0), context.CurrentFunction.ReturnType)}";
} }
int arity = (int)(info.Type & InstType.ArityMask); int arity = (int)(info.Type & InstType.ArityMask);
@ -118,7 +118,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{ {
IAstNode src = operation.GetSource(index); IAstNode src = operation.GetSource(index);
string srcExpr = GetSoureExpr(context, src, GetSrcVarType(inst, index)); string srcExpr = GetSourceExpr(context, src, GetSrcVarType(inst, index));
bool isLhs = arity == 2 && index == 0; bool isLhs = arity == 2 && index == 0;

View file

@ -12,7 +12,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{ {
AggregateType dstType = GetSrcVarType(operation.Inst, 0); AggregateType dstType = GetSrcVarType(operation.Inst, 0);
string arg = GetSoureExpr(context, operation.GetSource(0), dstType); string arg = GetSourceExpr(context, operation.GetSource(0), dstType);
char component = "xyzw"[operation.Index]; char component = "xyzw"[operation.Index];
if (context.HostCapabilities.SupportsShaderBallot) if (context.HostCapabilities.SupportsShaderBallot)

View file

@ -20,7 +20,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
for (int i = 0; i < args.Length; i++) for (int i = 0; i < args.Length; i++)
{ {
args[i] = GetSoureExpr(context, operation.GetSource(i + 1), function.GetArgumentType(i)); args[i] = GetSourceExpr(context, operation.GetSource(i + 1), function.GetArgumentType(i));
} }
return $"{function.Name}({string.Join(", ", args)})"; return $"{function.Name}({string.Join(", ", args)})";

View file

@ -140,7 +140,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return _infoTable[(int)(inst & Instruction.Mask)]; return _infoTable[(int)(inst & Instruction.Mask)];
} }
public static string GetSoureExpr(CodeGenContext context, IAstNode node, AggregateType dstType) public static string GetSourceExpr(CodeGenContext context, IAstNode node, AggregateType dstType)
{ {
return ReinterpretCast(context, node, OperandManager.GetNodeDestType(context, node), dstType); return ReinterpretCast(context, node, OperandManager.GetNodeDestType(context, node), dstType);
} }

View file

@ -14,35 +14,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{ {
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
// TODO: Bindless texture support. For now we just return 0/do nothing.
if (isBindless)
{
switch (texOp.Inst)
{
case Instruction.ImageStore:
return "// imageStore(bindless)";
case Instruction.ImageLoad:
AggregateType componentType = texOp.Format.GetComponentType();
NumberFormatter.TryFormat(0, componentType, out string imageConst);
AggregateType outputType = texOp.GetVectorType(componentType);
if ((outputType & AggregateType.ElementCountMask) != 0)
{
return $"{Declarations.GetVarTypeName(context, outputType, precise: false)}({imageConst})";
}
return imageConst;
default:
return NumberFormatter.FormatInt(0);
}
}
bool isArray = (texOp.Type & SamplerType.Array) != 0; bool isArray = (texOp.Type & SamplerType.Array) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
var texCallBuilder = new StringBuilder(); var texCallBuilder = new StringBuilder();
@ -70,21 +42,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
texCallBuilder.Append(texOp.Inst == Instruction.ImageLoad ? "imageLoad" : "imageStore"); texCallBuilder.Append(texOp.Inst == Instruction.ImageLoad ? "imageLoad" : "imageStore");
} }
int srcIndex = isBindless ? 1 : 0; int srcIndex = 0;
string Src(AggregateType type) string Src(AggregateType type)
{ {
return GetSoureExpr(context, texOp.GetSource(srcIndex++), type); return GetSourceExpr(context, texOp.GetSource(srcIndex++), type);
} }
string indexExpr = null; string imageName = GetImageName(context, texOp, ref srcIndex);
if (isIndexed)
{
indexExpr = Src(AggregateType.S32);
}
string imageName = GetImageName(context.Properties, texOp, indexExpr);
texCallBuilder.Append('('); texCallBuilder.Append('(');
texCallBuilder.Append(imageName); texCallBuilder.Append(imageName);
@ -198,27 +163,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
int coordsCount = texOp.Type.GetDimensions(); int coordsCount = texOp.Type.GetDimensions();
int coordsIndex = 0;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; string samplerName = GetSamplerName(context, texOp, ref coordsIndex);
// TODO: Bindless texture support. For now we just return 0.
if (isBindless)
{
return NumberFormatter.FormatFloat(0);
}
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);
int coordsIndex = isBindless || isIndexed ? 1 : 0;
string coordsExpr; string coordsExpr;
@ -228,14 +175,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
for (int index = 0; index < coordsCount; index++) for (int index = 0; index < coordsCount; index++)
{ {
elems[index] = GetSoureExpr(context, texOp.GetSource(coordsIndex + index), AggregateType.FP32); elems[index] = GetSourceExpr(context, texOp.GetSource(coordsIndex + index), AggregateType.FP32);
} }
coordsExpr = "vec" + coordsCount + "(" + string.Join(", ", elems) + ")"; coordsExpr = "vec" + coordsCount + "(" + string.Join(", ", elems) + ")";
} }
else else
{ {
coordsExpr = GetSoureExpr(context, texOp.GetSource(coordsIndex), AggregateType.FP32); coordsExpr = GetSourceExpr(context, texOp.GetSource(coordsIndex), AggregateType.FP32);
} }
return $"textureQueryLod({samplerName}, {coordsExpr}){GetMask(texOp.Index)}"; return $"textureQueryLod({samplerName}, {coordsExpr}){GetMask(texOp.Index)}";
@ -250,7 +197,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{ {
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
bool isGather = (texOp.Flags & TextureFlags.Gather) != 0; bool isGather = (texOp.Flags & TextureFlags.Gather) != 0;
bool hasDerivatives = (texOp.Flags & TextureFlags.Derivatives) != 0; bool hasDerivatives = (texOp.Flags & TextureFlags.Derivatives) != 0;
bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
@ -260,12 +206,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0; bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0;
bool isArray = (texOp.Type & SamplerType.Array) != 0; bool isArray = (texOp.Type & SamplerType.Array) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0; bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0;
bool isShadow = (texOp.Type & SamplerType.Shadow) != 0; bool isShadow = (texOp.Type & SamplerType.Shadow) != 0;
bool colorIsVector = isGather || !isShadow;
SamplerType type = texOp.Type & SamplerType.Mask; SamplerType type = texOp.Type & SamplerType.Mask;
bool is2D = type == SamplerType.Texture2D; bool is2D = type == SamplerType.Texture2D;
@ -286,24 +229,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
hasLodLevel = false; hasLodLevel = false;
} }
// TODO: Bindless texture support. For now we just return 0.
if (isBindless)
{
string scalarValue = NumberFormatter.FormatFloat(0);
if (colorIsVector)
{
AggregateType outputType = texOp.GetVectorType(AggregateType.FP32);
if ((outputType & AggregateType.ElementCountMask) != 0)
{
return $"{Declarations.GetVarTypeName(context, outputType, precise: false)}({scalarValue})";
}
}
return scalarValue;
}
string texCall = intCoords ? "texelFetch" : "texture"; string texCall = intCoords ? "texelFetch" : "texture";
if (isGather) if (isGather)
@ -328,21 +253,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
texCall += "Offsets"; texCall += "Offsets";
} }
int srcIndex = isBindless ? 1 : 0; int srcIndex = 0;
string Src(AggregateType type) string Src(AggregateType type)
{ {
return GetSoureExpr(context, texOp.GetSource(srcIndex++), type); return GetSourceExpr(context, texOp.GetSource(srcIndex++), type);
} }
string indexExpr = null; string samplerName = GetSamplerName(context, texOp, ref srcIndex);
if (isIndexed)
{
indexExpr = Src(AggregateType.S32);
}
string samplerName = GetSamplerName(context.Properties, texOp, indexExpr);
texCall += "(" + samplerName; texCall += "(" + samplerName;
@ -512,6 +430,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
Append(Src(AggregateType.S32)); Append(Src(AggregateType.S32));
} }
bool colorIsVector = isGather || !isShadow;
texCall += ")" + (colorIsVector ? GetMaskMultiDest(texOp.Index) : ""); texCall += ")" + (colorIsVector ? GetMaskMultiDest(texOp.Index) : "");
return texCall; return texCall;
@ -521,24 +441,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{ {
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; int srcIndex = 0;
// TODO: Bindless texture support. For now we just return 0. string samplerName = GetSamplerName(context, texOp, ref srcIndex);
if (isBindless)
{
return NumberFormatter.FormatInt(0);
}
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);
return $"textureSamples({samplerName})"; return $"textureSamples({samplerName})";
} }
@ -547,24 +452,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{ {
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; int srcIndex = 0;
// TODO: Bindless texture support. For now we just return 0. string samplerName = GetSamplerName(context, texOp, ref srcIndex);
if (isBindless)
{
return NumberFormatter.FormatInt(0);
}
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);
if (texOp.Index == 3) if (texOp.Index == 3)
{ {
@ -578,9 +468,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
if (hasLod) if (hasLod)
{ {
int lodSrcIndex = isBindless || isIndexed ? 1 : 0; IAstNode lod = operation.GetSource(srcIndex);
IAstNode lod = operation.GetSource(lodSrcIndex); string lodExpr = GetSourceExpr(context, lod, GetSrcVarType(operation.Inst, srcIndex));
string lodExpr = GetSoureExpr(context, lod, GetSrcVarType(operation.Inst, lodSrcIndex));
texCall = $"textureSize({samplerName}, {lodExpr}){GetMask(texOp.Index)}"; texCall = $"textureSize({samplerName}, {lodExpr}){GetMask(texOp.Index)}";
} }
@ -697,12 +586,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
if (storageKind == StorageKind.Input) if (storageKind == StorageKind.Input)
{ {
string expr = GetSoureExpr(context, operation.GetSource(srcIndex++), AggregateType.S32); string expr = GetSourceExpr(context, operation.GetSource(srcIndex++), AggregateType.S32);
varName = $"gl_in[{expr}].{varName}"; varName = $"gl_in[{expr}].{varName}";
} }
else if (storageKind == StorageKind.Output) else if (storageKind == StorageKind.Output)
{ {
string expr = GetSoureExpr(context, operation.GetSource(srcIndex++), AggregateType.S32); string expr = GetSourceExpr(context, operation.GetSource(srcIndex++), AggregateType.S32);
varName = $"gl_out[{expr}].{varName}"; varName = $"gl_out[{expr}].{varName}";
} }
} }
@ -735,38 +624,53 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
} }
else else
{ {
varName += $"[{GetSoureExpr(context, src, AggregateType.S32)}]"; varName += $"[{GetSourceExpr(context, src, AggregateType.S32)}]";
} }
} }
if (isStore) if (isStore)
{ {
varType &= AggregateType.ElementTypeMask; varType &= AggregateType.ElementTypeMask;
varName = $"{varName} = {GetSoureExpr(context, operation.GetSource(srcIndex), varType)}"; varName = $"{varName} = {GetSourceExpr(context, operation.GetSource(srcIndex), varType)}";
} }
return varName; return varName;
} }
private static string GetSamplerName(ShaderProperties resourceDefinitions, AstTextureOperation texOp, string indexExpr) private static string GetSamplerName(CodeGenContext context, AstTextureOperation texOp, ref int srcIndex)
{ {
string name = resourceDefinitions.Textures[texOp.Binding].Name; TextureDefinition textureDefinition = context.Properties.Textures[texOp.Binding];
string name = textureDefinition.Name;
if (texOp.Type.HasFlag(SamplerType.Indexed)) if (textureDefinition.ArrayLength != 1)
{ {
name = $"{name}[{indexExpr}]"; name = $"{name}[{GetSourceExpr(context, texOp.GetSource(srcIndex++), AggregateType.S32)}]";
}
if (texOp.IsSeparate)
{
TextureDefinition samplerDefinition = context.Properties.Textures[texOp.SamplerBinding];
string samplerName = samplerDefinition.Name;
if (samplerDefinition.ArrayLength != 1)
{
samplerName = $"{samplerName}[{GetSourceExpr(context, texOp.GetSource(srcIndex++), AggregateType.S32)}]";
}
name = $"{texOp.Type.ToGlslSamplerType()}({name}, {samplerName})";
} }
return name; return name;
} }
private static string GetImageName(ShaderProperties resourceDefinitions, AstTextureOperation texOp, string indexExpr) private static string GetImageName(CodeGenContext context, AstTextureOperation texOp, ref int srcIndex)
{ {
string name = resourceDefinitions.Images[texOp.Binding].Name; TextureDefinition definition = context.Properties.Images[texOp.Binding];
string name = definition.Name;
if (texOp.Type.HasFlag(SamplerType.Indexed)) if (definition.ArrayLength != 1)
{ {
name = $"{name}[{indexExpr}]"; name = $"{name}[{GetSourceExpr(context, texOp.GetSource(srcIndex++), AggregateType.S32)}]";
} }
return name; return name;

View file

@ -13,8 +13,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
IAstNode src0 = operation.GetSource(0); IAstNode src0 = operation.GetSource(0);
IAstNode src1 = operation.GetSource(1); IAstNode src1 = operation.GetSource(1);
string src0Expr = GetSoureExpr(context, src0, GetSrcVarType(operation.Inst, 0)); string src0Expr = GetSourceExpr(context, src0, GetSrcVarType(operation.Inst, 0));
string src1Expr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 1)); string src1Expr = GetSourceExpr(context, src1, GetSrcVarType(operation.Inst, 1));
return $"packDouble2x32(uvec2({src0Expr}, {src1Expr}))"; return $"packDouble2x32(uvec2({src0Expr}, {src1Expr}))";
} }
@ -24,8 +24,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
IAstNode src0 = operation.GetSource(0); IAstNode src0 = operation.GetSource(0);
IAstNode src1 = operation.GetSource(1); IAstNode src1 = operation.GetSource(1);
string src0Expr = GetSoureExpr(context, src0, GetSrcVarType(operation.Inst, 0)); string src0Expr = GetSourceExpr(context, src0, GetSrcVarType(operation.Inst, 0));
string src1Expr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 1)); string src1Expr = GetSourceExpr(context, src1, GetSrcVarType(operation.Inst, 1));
return $"packHalf2x16(vec2({src0Expr}, {src1Expr}))"; return $"packHalf2x16(vec2({src0Expr}, {src1Expr}))";
} }
@ -34,7 +34,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{ {
IAstNode src = operation.GetSource(0); IAstNode src = operation.GetSource(0);
string srcExpr = GetSoureExpr(context, src, GetSrcVarType(operation.Inst, 0)); string srcExpr = GetSourceExpr(context, src, GetSrcVarType(operation.Inst, 0));
return $"unpackDouble2x32({srcExpr}){GetMask(operation.Index)}"; return $"unpackDouble2x32({srcExpr}){GetMask(operation.Index)}";
} }
@ -43,7 +43,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{ {
IAstNode src = operation.GetSource(0); IAstNode src = operation.GetSource(0);
string srcExpr = GetSoureExpr(context, src, GetSrcVarType(operation.Inst, 0)); string srcExpr = GetSourceExpr(context, src, GetSrcVarType(operation.Inst, 0));
return $"unpackHalf2x16({srcExpr}){GetMask(operation.Index)}"; return $"unpackHalf2x16({srcExpr}){GetMask(operation.Index)}";
} }

View file

@ -9,8 +9,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{ {
public static string Shuffle(CodeGenContext context, AstOperation operation) public static string Shuffle(CodeGenContext context, AstOperation operation)
{ {
string value = GetSoureExpr(context, operation.GetSource(0), AggregateType.FP32); string value = GetSourceExpr(context, operation.GetSource(0), AggregateType.FP32);
string index = GetSoureExpr(context, operation.GetSource(1), AggregateType.U32); string index = GetSourceExpr(context, operation.GetSource(1), AggregateType.U32);
if (context.HostCapabilities.SupportsShaderBallot) if (context.HostCapabilities.SupportsShaderBallot)
{ {

View file

@ -13,7 +13,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
IAstNode vector = operation.GetSource(0); IAstNode vector = operation.GetSource(0);
IAstNode index = operation.GetSource(1); IAstNode index = operation.GetSource(1);
string vectorExpr = GetSoureExpr(context, vector, OperandManager.GetNodeDestType(context, vector)); string vectorExpr = GetSourceExpr(context, vector, OperandManager.GetNodeDestType(context, vector));
if (index is AstOperand indexOperand && indexOperand.Type == OperandType.Constant) if (index is AstOperand indexOperand && indexOperand.Type == OperandType.Constant)
{ {
@ -23,7 +23,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
} }
else else
{ {
string indexExpr = GetSoureExpr(context, index, GetSrcVarType(operation.Inst, 1)); string indexExpr = GetSourceExpr(context, index, GetSrcVarType(operation.Inst, 1));
return $"{vectorExpr}[{indexExpr}]"; return $"{vectorExpr}[{indexExpr}]";
} }

View file

@ -146,9 +146,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
} }
else if (operation is AstTextureOperation texOp) else if (operation is AstTextureOperation texOp)
{ {
if (texOp.Inst == Instruction.ImageLoad || if (texOp.Inst.IsImage())
texOp.Inst == Instruction.ImageStore ||
texOp.Inst == Instruction.ImageAtomic)
{ {
return texOp.GetVectorType(texOp.Format.GetComponentType()); return texOp.GetVectorType(texOp.Format.GetComponentType());
} }

View file

@ -34,8 +34,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
public Dictionary<int, Instruction> SharedMemories { get; } = new(); public Dictionary<int, Instruction> SharedMemories { get; } = new();
public Dictionary<int, SamplerType> SamplersTypes { get; } = new(); public Dictionary<int, SamplerType> SamplersTypes { get; } = new();
public Dictionary<int, (Instruction, Instruction, Instruction)> Samplers { get; } = new(); public Dictionary<int, SamplerDeclaration> Samplers { get; } = new();
public Dictionary<int, (Instruction, Instruction)> Images { get; } = new(); public Dictionary<int, ImageDeclaration> Images { get; } = new();
public Dictionary<IoDefinition, Instruction> Inputs { get; } = new(); public Dictionary<IoDefinition, Instruction> Inputs { get; } = new();
public Dictionary<IoDefinition, Instruction> Outputs { get; } = new(); public Dictionary<IoDefinition, Instruction> Outputs { get; } = new();

View file

@ -160,30 +160,60 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{ {
int setIndex = context.TargetApi == TargetApi.Vulkan ? sampler.Set : 0; int setIndex = context.TargetApi == TargetApi.Vulkan ? sampler.Set : 0;
var dim = (sampler.Type & SamplerType.Mask) switch SpvInstruction imageType;
SpvInstruction sampledImageType;
if (sampler.Type != SamplerType.None)
{ {
SamplerType.Texture1D => Dim.Dim1D, var dim = (sampler.Type & SamplerType.Mask) switch
SamplerType.Texture2D => Dim.Dim2D, {
SamplerType.Texture3D => Dim.Dim3D, SamplerType.Texture1D => Dim.Dim1D,
SamplerType.TextureCube => Dim.Cube, SamplerType.Texture2D => Dim.Dim2D,
SamplerType.TextureBuffer => Dim.Buffer, SamplerType.Texture3D => Dim.Dim3D,
_ => throw new InvalidOperationException($"Invalid sampler type \"{sampler.Type & SamplerType.Mask}\"."), SamplerType.TextureCube => Dim.Cube,
}; SamplerType.TextureBuffer => Dim.Buffer,
_ => throw new InvalidOperationException($"Invalid sampler type \"{sampler.Type & SamplerType.Mask}\"."),
};
var imageType = context.TypeImage( imageType = context.TypeImage(
context.TypeFP32(), context.TypeFP32(),
dim, dim,
sampler.Type.HasFlag(SamplerType.Shadow), sampler.Type.HasFlag(SamplerType.Shadow),
sampler.Type.HasFlag(SamplerType.Array), sampler.Type.HasFlag(SamplerType.Array),
sampler.Type.HasFlag(SamplerType.Multisample), sampler.Type.HasFlag(SamplerType.Multisample),
1, 1,
ImageFormat.Unknown); ImageFormat.Unknown);
var sampledImageType = context.TypeSampledImage(imageType); sampledImageType = context.TypeSampledImage(imageType);
var sampledImagePointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageType); }
var sampledImageVariable = context.Variable(sampledImagePointerType, StorageClass.UniformConstant); else
{
imageType = sampledImageType = context.TypeSampler();
}
context.Samplers.Add(sampler.Binding, (imageType, sampledImageType, sampledImageVariable)); var sampledOrSeparateImageType = sampler.Separate ? imageType : sampledImageType;
var sampledImagePointerType = context.TypePointer(StorageClass.UniformConstant, sampledOrSeparateImageType);
var sampledImageArrayPointerType = sampledImagePointerType;
if (sampler.ArrayLength == 0)
{
var sampledImageArrayType = context.TypeRuntimeArray(sampledOrSeparateImageType);
sampledImageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageArrayType);
}
else if (sampler.ArrayLength != 1)
{
var sampledImageArrayType = context.TypeArray(sampledOrSeparateImageType, context.Constant(context.TypeU32(), sampler.ArrayLength));
sampledImageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageArrayType);
}
var sampledImageVariable = context.Variable(sampledImageArrayPointerType, StorageClass.UniformConstant);
context.Samplers.Add(sampler.Binding, new SamplerDeclaration(
imageType,
sampledImageType,
sampledImagePointerType,
sampledImageVariable,
sampler.ArrayLength != 1));
context.SamplersTypes.Add(sampler.Binding, sampler.Type); context.SamplersTypes.Add(sampler.Binding, sampler.Type);
context.Name(sampledImageVariable, sampler.Name); context.Name(sampledImageVariable, sampler.Name);
@ -211,9 +241,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
GetImageFormat(image.Format)); GetImageFormat(image.Format));
var imagePointerType = context.TypePointer(StorageClass.UniformConstant, imageType); var imagePointerType = context.TypePointer(StorageClass.UniformConstant, imageType);
var imageVariable = context.Variable(imagePointerType, StorageClass.UniformConstant); var imageArrayPointerType = imagePointerType;
context.Images.Add(image.Binding, (imageType, imageVariable)); if (image.ArrayLength == 0)
{
var imageArrayType = context.TypeRuntimeArray(imageType);
imageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, imageArrayType);
}
else if (image.ArrayLength != 1)
{
var imageArrayType = context.TypeArray(imageType, context.Constant(context.TypeU32(), image.ArrayLength));
imageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, imageArrayType);
}
var imageVariable = context.Variable(imageArrayPointerType, StorageClass.UniformConstant);
context.Images.Add(image.Binding, new ImageDeclaration(imageType, imagePointerType, imageVariable, image.ArrayLength != 1));
context.Name(imageVariable, image.Name); context.Name(imageVariable, image.Name);
context.Decorate(imageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex); context.Decorate(imageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex);

View file

@ -0,0 +1,20 @@
using Spv.Generator;
namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
readonly struct ImageDeclaration
{
public readonly Instruction ImageType;
public readonly Instruction ImagePointerType;
public readonly Instruction Image;
public readonly bool IsIndexed;
public ImageDeclaration(Instruction imageType, Instruction imagePointerType, Instruction image, bool isIndexed)
{
ImageType = imageType;
ImagePointerType = imagePointerType;
Image = image;
IsIndexed = isIndexed;
}
}
}

View file

@ -591,34 +591,28 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{ {
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
var componentType = texOp.Format.GetComponentType(); var componentType = texOp.Format.GetComponentType();
// TODO: Bindless texture support. For now we just return 0/do nothing.
if (isBindless)
{
return new OperationResult(componentType, componentType switch
{
AggregateType.S32 => context.Constant(context.TypeS32(), 0),
AggregateType.U32 => context.Constant(context.TypeU32(), 0u),
_ => context.Constant(context.TypeFP32(), 0f),
});
}
bool isArray = (texOp.Type & SamplerType.Array) != 0; bool isArray = (texOp.Type & SamplerType.Array) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
int srcIndex = isBindless ? 1 : 0; int srcIndex = 0;
SpvInstruction Src(AggregateType type) SpvInstruction Src(AggregateType type)
{ {
return context.Get(type, texOp.GetSource(srcIndex++)); return context.Get(type, texOp.GetSource(srcIndex++));
} }
if (isIndexed) ImageDeclaration declaration = context.Images[texOp.Binding];
SpvInstruction image = declaration.Image;
SpvInstruction resultType = context.GetType(componentType);
SpvInstruction imagePointerType = context.TypePointer(StorageClass.Image, resultType);
if (declaration.IsIndexed)
{ {
Src(AggregateType.S32); SpvInstruction textureIndex = Src(AggregateType.S32);
image = context.AccessChain(imagePointerType, image, textureIndex);
} }
int coordsCount = texOp.Type.GetDimensions(); int coordsCount = texOp.Type.GetDimensions();
@ -646,14 +640,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
SpvInstruction value = Src(componentType); SpvInstruction value = Src(componentType);
(var imageType, var imageVariable) = context.Images[texOp.Binding]; var pointer = context.ImageTexelPointer(imagePointerType, image, pCoords, context.Constant(context.TypeU32(), 0));
context.Load(imageType, imageVariable);
SpvInstruction resultType = context.GetType(componentType);
SpvInstruction imagePointerType = context.TypePointer(StorageClass.Image, resultType);
var pointer = context.ImageTexelPointer(imagePointerType, imageVariable, pCoords, context.Constant(context.TypeU32(), 0));
var one = context.Constant(context.TypeU32(), 1); var one = context.Constant(context.TypeU32(), 1);
var zero = context.Constant(context.TypeU32(), 0); var zero = context.Constant(context.TypeU32(), 0);
@ -683,31 +670,29 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{ {
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
var componentType = texOp.Format.GetComponentType(); var componentType = texOp.Format.GetComponentType();
// TODO: Bindless texture support. For now we just return 0/do nothing.
if (isBindless)
{
return GetZeroOperationResult(context, texOp, componentType, isVector: true);
}
bool isArray = (texOp.Type & SamplerType.Array) != 0; bool isArray = (texOp.Type & SamplerType.Array) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
int srcIndex = isBindless ? 1 : 0; int srcIndex = 0;
SpvInstruction Src(AggregateType type) SpvInstruction Src(AggregateType type)
{ {
return context.Get(type, texOp.GetSource(srcIndex++)); return context.Get(type, texOp.GetSource(srcIndex++));
} }
if (isIndexed) ImageDeclaration declaration = context.Images[texOp.Binding];
SpvInstruction image = declaration.Image;
if (declaration.IsIndexed)
{ {
Src(AggregateType.S32); SpvInstruction textureIndex = Src(AggregateType.S32);
image = context.AccessChain(declaration.ImagePointerType, image, textureIndex);
} }
image = context.Load(declaration.ImageType, image);
int coordsCount = texOp.Type.GetDimensions(); int coordsCount = texOp.Type.GetDimensions();
int pCount = coordsCount + (isArray ? 1 : 0); int pCount = coordsCount + (isArray ? 1 : 0);
@ -731,9 +716,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
pCoords = Src(AggregateType.S32); pCoords = Src(AggregateType.S32);
} }
(var imageType, var imageVariable) = context.Images[texOp.Binding];
var image = context.Load(imageType, imageVariable);
var imageComponentType = context.GetType(componentType); var imageComponentType = context.GetType(componentType);
var swizzledResultType = texOp.GetVectorType(componentType); var swizzledResultType = texOp.GetVectorType(componentType);
@ -747,29 +729,27 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{ {
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
// TODO: Bindless texture support. For now we just return 0/do nothing.
if (isBindless)
{
return OperationResult.Invalid;
}
bool isArray = (texOp.Type & SamplerType.Array) != 0; bool isArray = (texOp.Type & SamplerType.Array) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
int srcIndex = isBindless ? 1 : 0; int srcIndex = 0;
SpvInstruction Src(AggregateType type) SpvInstruction Src(AggregateType type)
{ {
return context.Get(type, texOp.GetSource(srcIndex++)); return context.Get(type, texOp.GetSource(srcIndex++));
} }
if (isIndexed) ImageDeclaration declaration = context.Images[texOp.Binding];
SpvInstruction image = declaration.Image;
if (declaration.IsIndexed)
{ {
Src(AggregateType.S32); SpvInstruction textureIndex = Src(AggregateType.S32);
image = context.AccessChain(declaration.ImagePointerType, image, textureIndex);
} }
image = context.Load(declaration.ImageType, image);
int coordsCount = texOp.Type.GetDimensions(); int coordsCount = texOp.Type.GetDimensions();
int pCount = coordsCount + (isArray ? 1 : 0); int pCount = coordsCount + (isArray ? 1 : 0);
@ -818,10 +798,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var texel = context.CompositeConstruct(context.TypeVector(context.GetType(componentType), ComponentsCount), cElems); var texel = context.CompositeConstruct(context.TypeVector(context.GetType(componentType), ComponentsCount), cElems);
(var imageType, var imageVariable) = context.Images[texOp.Binding];
var image = context.Load(imageType, imageVariable);
context.ImageWrite(image, pCoords, texel, ImageOperandsMask.MaskNone); context.ImageWrite(image, pCoords, texel, ImageOperandsMask.MaskNone);
return OperationResult.Invalid; return OperationResult.Invalid;
@ -854,16 +830,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{ {
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
// TODO: Bindless texture support. For now we just return 0.
if (isBindless)
{
return new OperationResult(AggregateType.S32, context.Constant(context.TypeS32(), 0));
}
int srcIndex = 0; int srcIndex = 0;
SpvInstruction Src(AggregateType type) SpvInstruction Src(AggregateType type)
@ -871,10 +837,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return context.Get(type, texOp.GetSource(srcIndex++)); return context.Get(type, texOp.GetSource(srcIndex++));
} }
if (isIndexed) SamplerDeclaration declaration = context.Samplers[texOp.Binding];
{ SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
Src(AggregateType.S32);
}
int pCount = texOp.Type.GetDimensions(); int pCount = texOp.Type.GetDimensions();
@ -897,10 +861,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
pCoords = Src(AggregateType.FP32); pCoords = Src(AggregateType.FP32);
} }
(_, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding];
var image = context.Load(sampledImageType, sampledImageVariable);
var resultType = context.TypeVector(context.TypeFP32(), 2); var resultType = context.TypeVector(context.TypeFP32(), 2);
var packed = context.ImageQueryLod(resultType, image, pCoords); var packed = context.ImageQueryLod(resultType, image, pCoords);
var result = context.CompositeExtract(context.TypeFP32(), packed, (SpvLiteralInteger)texOp.Index); var result = context.CompositeExtract(context.TypeFP32(), packed, (SpvLiteralInteger)texOp.Index);
@ -1182,7 +1142,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{ {
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
bool isGather = (texOp.Flags & TextureFlags.Gather) != 0; bool isGather = (texOp.Flags & TextureFlags.Gather) != 0;
bool hasDerivatives = (texOp.Flags & TextureFlags.Derivatives) != 0; bool hasDerivatives = (texOp.Flags & TextureFlags.Derivatives) != 0;
bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
@ -1192,29 +1151,18 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0; bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0;
bool isArray = (texOp.Type & SamplerType.Array) != 0; bool isArray = (texOp.Type & SamplerType.Array) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0; bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0;
bool isShadow = (texOp.Type & SamplerType.Shadow) != 0; bool isShadow = (texOp.Type & SamplerType.Shadow) != 0;
bool colorIsVector = isGather || !isShadow; int srcIndex = 0;
// TODO: Bindless texture support. For now we just return 0.
if (isBindless)
{
return GetZeroOperationResult(context, texOp, AggregateType.FP32, colorIsVector);
}
int srcIndex = isBindless ? 1 : 0;
SpvInstruction Src(AggregateType type) SpvInstruction Src(AggregateType type)
{ {
return context.Get(type, texOp.GetSource(srcIndex++)); return context.Get(type, texOp.GetSource(srcIndex++));
} }
if (isIndexed) SamplerDeclaration declaration = context.Samplers[texOp.Binding];
{ SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
Src(AggregateType.S32);
}
int coordsCount = texOp.Type.GetDimensions(); int coordsCount = texOp.Type.GetDimensions();
@ -1419,15 +1367,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
operandsList.Add(sample); operandsList.Add(sample);
} }
bool colorIsVector = isGather || !isShadow;
var resultType = colorIsVector ? context.TypeVector(context.TypeFP32(), 4) : context.TypeFP32(); var resultType = colorIsVector ? context.TypeVector(context.TypeFP32(), 4) : context.TypeFP32();
(var imageType, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding];
var image = context.Load(sampledImageType, sampledImageVariable);
if (intCoords) if (intCoords)
{ {
image = context.Image(imageType, image); image = context.Image(declaration.ImageType, image);
} }
var operands = operandsList.ToArray(); var operands = operandsList.ToArray();
@ -1485,25 +1431,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{ {
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; int srcIndex = 0;
// TODO: Bindless texture support. For now we just return 0. SamplerDeclaration declaration = context.Samplers[texOp.Binding];
if (isBindless) SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
{
return new OperationResult(AggregateType.S32, context.Constant(context.TypeS32(), 0));
}
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; image = context.Image(declaration.ImageType, image);
if (isIndexed)
{
context.GetS32(texOp.GetSource(0));
}
(var imageType, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding];
var image = context.Load(sampledImageType, sampledImageVariable);
image = context.Image(imageType, image);
SpvInstruction result = context.ImageQuerySamples(context.TypeS32(), image); SpvInstruction result = context.ImageQuerySamples(context.TypeS32(), image);
@ -1514,25 +1447,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{ {
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; int srcIndex = 0;
// TODO: Bindless texture support. For now we just return 0. SamplerDeclaration declaration = context.Samplers[texOp.Binding];
if (isBindless) SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
{
return new OperationResult(AggregateType.S32, context.Constant(context.TypeS32(), 0));
}
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; image = context.Image(declaration.ImageType, image);
if (isIndexed)
{
context.GetS32(texOp.GetSource(0));
}
(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) if (texOp.Index == 3)
{ {
@ -1556,8 +1476,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (hasLod) if (hasLod)
{ {
int lodSrcIndex = isBindless || isIndexed ? 1 : 0; var lod = context.GetS32(operation.GetSource(srcIndex));
var lod = context.GetS32(operation.GetSource(lodSrcIndex));
result = context.ImageQuerySizeLod(resultType, image, lod); result = context.ImageQuerySizeLod(resultType, image, lod);
} }
else else
@ -1929,38 +1848,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return context.Load(context.GetType(varType), context.Inputs[ioDefinition]); return context.Load(context.GetType(varType), context.Inputs[ioDefinition]);
} }
private static OperationResult GetZeroOperationResult(
CodeGenContext context,
AstTextureOperation texOp,
AggregateType scalarType,
bool isVector)
{
var zero = scalarType switch
{
AggregateType.S32 => context.Constant(context.TypeS32(), 0),
AggregateType.U32 => context.Constant(context.TypeU32(), 0u),
_ => context.Constant(context.TypeFP32(), 0f),
};
if (isVector)
{
AggregateType outputType = texOp.GetVectorType(scalarType);
if ((outputType & AggregateType.ElementCountMask) != 0)
{
int componentsCount = BitOperations.PopCount((uint)texOp.Index);
SpvInstruction[] values = new SpvInstruction[componentsCount];
values.AsSpan().Fill(zero);
return new OperationResult(outputType, context.ConstantComposite(context.GetType(outputType), values));
}
}
return new OperationResult(scalarType, zero);
}
private static SpvInstruction GetSwizzledResult(CodeGenContext context, SpvInstruction vector, AggregateType swizzledResultType, int mask) private static SpvInstruction GetSwizzledResult(CodeGenContext context, SpvInstruction vector, AggregateType swizzledResultType, int mask)
{ {
if ((swizzledResultType & AggregateType.ElementCountMask) != 0) if ((swizzledResultType & AggregateType.ElementCountMask) != 0)
@ -1987,6 +1874,43 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
} }
} }
private static SpvInstruction GenerateSampledImageLoad(CodeGenContext context, AstTextureOperation texOp, SamplerDeclaration declaration, ref int srcIndex)
{
SpvInstruction image = declaration.Image;
if (declaration.IsIndexed)
{
SpvInstruction textureIndex = context.Get(AggregateType.S32, texOp.GetSource(srcIndex++));
image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex);
}
if (texOp.IsSeparate)
{
image = context.Load(declaration.ImageType, image);
SamplerDeclaration samplerDeclaration = context.Samplers[texOp.SamplerBinding];
SpvInstruction sampler = samplerDeclaration.Image;
if (samplerDeclaration.IsIndexed)
{
SpvInstruction samplerIndex = context.Get(AggregateType.S32, texOp.GetSource(srcIndex++));
sampler = context.AccessChain(samplerDeclaration.SampledImagePointerType, sampler, samplerIndex);
}
sampler = context.Load(samplerDeclaration.ImageType, sampler);
image = context.SampledImage(declaration.SampledImageType, image, sampler);
}
else
{
image = context.Load(declaration.SampledImageType, image);
}
return image;
}
private static OperationResult GenerateUnary( private static OperationResult GenerateUnary(
CodeGenContext context, CodeGenContext context,
AstOperation operation, AstOperation operation,

View file

@ -0,0 +1,27 @@
using Spv.Generator;
namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
readonly struct SamplerDeclaration
{
public readonly Instruction ImageType;
public readonly Instruction SampledImageType;
public readonly Instruction SampledImagePointerType;
public readonly Instruction Image;
public readonly bool IsIndexed;
public SamplerDeclaration(
Instruction imageType,
Instruction sampledImageType,
Instruction sampledImagePointerType,
Instruction image,
bool isIndexed)
{
ImageType = imageType;
SampledImageType = sampledImageType;
SampledImagePointerType = sampledImagePointerType;
Image = image;
IsIndexed = isIndexed;
}
}
}

View file

@ -31,42 +31,30 @@ namespace Ryujinx.Graphics.Shader
/// </summary> /// </summary>
/// <param name="index">Constant buffer index</param> /// <param name="index">Constant buffer index</param>
/// <returns>Binding number</returns> /// <returns>Binding number</returns>
int QueryBindingConstantBuffer(int index) int CreateConstantBufferBinding(int index);
{
return index + 1; /// <summary>
} /// Queries the binding number of an image.
/// </summary>
/// <param name="count">For array of images, the number of elements of the array, otherwise it should be 1</param>
/// <param name="isBuffer">Indicates if the image is a buffer image</param>
/// <returns>Binding number</returns>
int CreateImageBinding(int count, bool isBuffer);
/// <summary> /// <summary>
/// Queries the binding number of a storage buffer. /// Queries the binding number of a storage buffer.
/// </summary> /// </summary>
/// <param name="index">Storage buffer index</param> /// <param name="index">Storage buffer index</param>
/// <returns>Binding number</returns> /// <returns>Binding number</returns>
int QueryBindingStorageBuffer(int index) int CreateStorageBufferBinding(int index);
{
return index;
}
/// <summary> /// <summary>
/// Queries the binding number of a texture. /// Queries the binding number of a texture.
/// </summary> /// </summary>
/// <param name="index">Texture index</param> /// <param name="count">For array of textures, the number of elements of the array, otherwise it should be 1</param>
/// <param name="isBuffer">Indicates if the texture is a buffer texture</param> /// <param name="isBuffer">Indicates if the texture is a buffer texture</param>
/// <returns>Binding number</returns> /// <returns>Binding number</returns>
int QueryBindingTexture(int index, bool isBuffer) int CreateTextureBinding(int count, bool isBuffer);
{
return index;
}
/// <summary>
/// Queries the binding number of an image.
/// </summary>
/// <param name="index">Image index</param>
/// <param name="isBuffer">Indicates if the image is a buffer image</param>
/// <returns>Binding number</returns>
int QueryBindingImage(int index, bool isBuffer)
{
return index;
}
/// <summary> /// <summary>
/// Queries Local Size X for compute shaders. /// Queries Local Size X for compute shaders.
@ -393,6 +381,12 @@ namespace Ryujinx.Graphics.Shader
return true; return true;
} }
/// <summary>
/// Gets the maximum number of samplers that the bound texture pool may have.
/// </summary>
/// <returns>Maximum amount of samplers that the pool may have</returns>
int QuerySamplerArrayLengthFromPool();
/// <summary> /// <summary>
/// Queries sampler type information. /// Queries sampler type information.
/// </summary> /// </summary>
@ -404,6 +398,19 @@ namespace Ryujinx.Graphics.Shader
return SamplerType.Texture2D; return SamplerType.Texture2D;
} }
/// <summary>
/// Gets the size in bytes of a bound constant buffer for the current shader stage.
/// </summary>
/// <param name="slot">The number of the constant buffer to get the size from</param>
/// <returns>Size in bytes</returns>
int QueryTextureArrayLengthFromBuffer(int slot);
/// <summary>
/// Gets the maximum number of textures that the bound texture pool may have.
/// </summary>
/// <returns>Maximum amount of textures that the pool may have</returns>
int QueryTextureArrayLengthFromPool();
/// <summary> /// <summary>
/// Queries texture coordinate normalization information. /// Queries texture coordinate normalization information.
/// </summary> /// </summary>

View file

@ -161,5 +161,17 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
inst &= Instruction.Mask; inst &= Instruction.Mask;
return inst == Instruction.Lod || inst == Instruction.TextureQuerySamples || inst == Instruction.TextureQuerySize; return inst == Instruction.Lod || inst == Instruction.TextureQuerySamples || inst == Instruction.TextureQuerySize;
} }
public static bool IsImage(this Instruction inst)
{
inst &= Instruction.Mask;
return inst == Instruction.ImageAtomic || inst == Instruction.ImageLoad || inst == Instruction.ImageStore;
}
public static bool IsImageStore(this Instruction inst)
{
inst &= Instruction.Mask;
return inst == Instruction.ImageAtomic || inst == Instruction.ImageStore;
}
} }
} }

View file

@ -20,13 +20,13 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
} }
set set
{ {
if (value != null && value.Type == OperandType.LocalVariable)
{
value.AsgOp = this;
}
if (value != null) if (value != null)
{ {
if (value.Type == OperandType.LocalVariable)
{
value.AsgOp = this;
}
_dests = new[] { value }; _dests = new[] { value };
} }
else else
@ -216,6 +216,11 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
newSources[index] = source; newSources[index] = source;
if (source != null && source.Type == OperandType.LocalVariable)
{
source.UseOps.Add(this);
}
_sources = newSources; _sources = newSources;
} }

View file

@ -9,6 +9,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
public TextureFlags Flags { get; private set; } public TextureFlags Flags { get; private set; }
public int Binding { get; private set; } public int Binding { get; private set; }
public int SamplerBinding { get; private set; }
public TextureOperation( public TextureOperation(
Instruction inst, Instruction inst,
@ -24,15 +25,22 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
Format = format; Format = format;
Flags = flags; Flags = flags;
Binding = binding; Binding = binding;
SamplerBinding = -1;
} }
public void TurnIntoIndexed(int binding) public void TurnIntoArray(int binding)
{ {
Type |= SamplerType.Indexed;
Flags &= ~TextureFlags.Bindless; Flags &= ~TextureFlags.Bindless;
Binding = binding; Binding = binding;
} }
public void TurnIntoArray(int textureBinding, int samplerBinding)
{
TurnIntoArray(textureBinding);
SamplerBinding = samplerBinding;
}
public void SetBinding(int binding) public void SetBinding(int binding)
{ {
if ((Flags & TextureFlags.Bindless) != 0) if ((Flags & TextureFlags.Bindless) != 0)

View file

@ -16,9 +16,8 @@ namespace Ryujinx.Graphics.Shader
Mask = 0xff, Mask = 0xff,
Array = 1 << 8, Array = 1 << 8,
Indexed = 1 << 9, Multisample = 1 << 9,
Multisample = 1 << 10, Shadow = 1 << 10,
Shadow = 1 << 11,
} }
static class SamplerTypeExtensions static class SamplerTypeExtensions
@ -40,6 +39,7 @@ namespace Ryujinx.Graphics.Shader
{ {
string typeName = (type & SamplerType.Mask) switch string typeName = (type & SamplerType.Mask) switch
{ {
SamplerType.None => "sampler",
SamplerType.Texture1D => "sampler1D", SamplerType.Texture1D => "sampler1D",
SamplerType.TextureBuffer => "samplerBuffer", SamplerType.TextureBuffer => "samplerBuffer",
SamplerType.Texture2D => "sampler2D", SamplerType.Texture2D => "sampler2D",
@ -66,6 +66,61 @@ namespace Ryujinx.Graphics.Shader
return typeName; return typeName;
} }
public static string ToShortSamplerType(this SamplerType type)
{
string typeName = (type & SamplerType.Mask) switch
{
SamplerType.Texture1D => "1d",
SamplerType.TextureBuffer => "b",
SamplerType.Texture2D => "2d",
SamplerType.Texture3D => "3d",
SamplerType.TextureCube => "cube",
_ => throw new ArgumentException($"Invalid sampler type \"{type}\"."),
};
if ((type & SamplerType.Multisample) != 0)
{
typeName += "ms";
}
if ((type & SamplerType.Array) != 0)
{
typeName += "a";
}
if ((type & SamplerType.Shadow) != 0)
{
typeName += "s";
}
return typeName;
}
public static string ToGlslTextureType(this SamplerType type)
{
string typeName = (type & SamplerType.Mask) switch
{
SamplerType.Texture1D => "texture1D",
SamplerType.TextureBuffer => "textureBuffer",
SamplerType.Texture2D => "texture2D",
SamplerType.Texture3D => "texture3D",
SamplerType.TextureCube => "textureCube",
_ => throw new ArgumentException($"Invalid texture type \"{type}\"."),
};
if ((type & SamplerType.Multisample) != 0)
{
typeName += "MS";
}
if ((type & SamplerType.Array) != 0)
{
typeName += "Array";
}
return typeName;
}
public static string ToGlslImageType(this SamplerType type, AggregateType componentType) public static string ToGlslImageType(this SamplerType type, AggregateType componentType)
{ {
string typeName = (type & SamplerType.Mask) switch string typeName = (type & SamplerType.Mask) switch

View file

@ -9,6 +9,9 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
public TextureFlags Flags { get; } public TextureFlags Flags { get; }
public int Binding { get; } public int Binding { get; }
public int SamplerBinding { get; }
public bool IsSeparate => SamplerBinding >= 0;
public AstTextureOperation( public AstTextureOperation(
Instruction inst, Instruction inst,
@ -16,6 +19,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
TextureFormat format, TextureFormat format,
TextureFlags flags, TextureFlags flags,
int binding, int binding,
int samplerBinding,
int index, int index,
params IAstNode[] sources) : base(inst, StorageKind.None, false, index, sources, sources.Length) params IAstNode[] sources) : base(inst, StorageKind.None, false, index, sources, sources.Length)
{ {
@ -23,6 +27,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
Format = format; Format = format;
Flags = flags; Flags = flags;
Binding = binding; Binding = binding;
SamplerBinding = samplerBinding;
} }
} }
} }

View file

@ -169,7 +169,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
AstTextureOperation GetAstTextureOperation(TextureOperation texOp) AstTextureOperation GetAstTextureOperation(TextureOperation texOp)
{ {
return new AstTextureOperation(inst, texOp.Type, texOp.Format, texOp.Flags, texOp.Binding, texOp.Index, sources); return new AstTextureOperation(inst, texOp.Type, texOp.Format, texOp.Flags, texOp.Binding, texOp.SamplerBinding, texOp.Index, sources);
} }
int componentsCount = BitOperations.PopCount((uint)operation.Index); int componentsCount = BitOperations.PopCount((uint)operation.Index);

View file

@ -4,24 +4,40 @@ namespace Ryujinx.Graphics.Shader
{ {
public int Set { get; } public int Set { get; }
public int Binding { get; } public int Binding { get; }
public int ArrayLength { get; }
public bool Separate { get; }
public string Name { get; } public string Name { get; }
public SamplerType Type { get; } public SamplerType Type { get; }
public TextureFormat Format { get; } public TextureFormat Format { get; }
public TextureUsageFlags Flags { get; } public TextureUsageFlags Flags { get; }
public TextureDefinition(int set, int binding, string name, SamplerType type, TextureFormat format, TextureUsageFlags flags) public TextureDefinition(
int set,
int binding,
int arrayLength,
bool separate,
string name,
SamplerType type,
TextureFormat format,
TextureUsageFlags flags)
{ {
Set = set; Set = set;
Binding = binding; Binding = binding;
ArrayLength = arrayLength;
Separate = separate;
Name = name; Name = name;
Type = type; Type = type;
Format = format; Format = format;
Flags = flags; Flags = flags;
} }
public TextureDefinition(int set, int binding, string name, SamplerType type) : this(set, binding, 1, false, name, type, TextureFormat.Unknown, TextureUsageFlags.None)
{
}
public TextureDefinition SetFlag(TextureUsageFlags flag) public TextureDefinition SetFlag(TextureUsageFlags flag)
{ {
return new TextureDefinition(Set, Binding, Name, Type, Format, Flags | flag); return new TextureDefinition(Set, Binding, ArrayLength, Separate, Name, Type, Format, Flags | flag);
} }
} }
} }

View file

@ -11,16 +11,29 @@ namespace Ryujinx.Graphics.Shader
public readonly int CbufSlot; public readonly int CbufSlot;
public readonly int HandleIndex; public readonly int HandleIndex;
public readonly int ArrayLength;
public readonly bool Separate;
public readonly TextureUsageFlags Flags; public readonly TextureUsageFlags Flags;
public TextureDescriptor(int binding, SamplerType type, TextureFormat format, int cbufSlot, int handleIndex, TextureUsageFlags flags) public TextureDescriptor(
int binding,
SamplerType type,
TextureFormat format,
int cbufSlot,
int handleIndex,
int arrayLength,
bool separate,
TextureUsageFlags flags)
{ {
Binding = binding; Binding = binding;
Type = type; Type = type;
Format = format; Format = format;
CbufSlot = cbufSlot; CbufSlot = cbufSlot;
HandleIndex = handleIndex; HandleIndex = handleIndex;
ArrayLength = arrayLength;
Separate = separate;
Flags = flags; Flags = flags;
} }
} }

View file

@ -9,6 +9,7 @@ namespace Ryujinx.Graphics.Shader
SeparateSamplerHandle = 1, SeparateSamplerHandle = 1,
SeparateSamplerId = 2, SeparateSamplerId = 2,
SeparateConstantSamplerHandle = 3, SeparateConstantSamplerHandle = 3,
Direct = 4,
} }
public static class TextureHandle public static class TextureHandle
@ -88,7 +89,7 @@ namespace Ryujinx.Graphics.Shader
{ {
(int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = UnpackOffsets(wordOffset); (int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = UnpackOffsets(wordOffset);
int handle = cachedTextureBuffer.Length != 0 ? cachedTextureBuffer[textureWordOffset] : 0; int handle = textureWordOffset < cachedTextureBuffer.Length ? cachedTextureBuffer[textureWordOffset] : 0;
// The "wordOffset" (which is really the immediate value used on texture instructions on the shader) // The "wordOffset" (which is really the immediate value used on texture instructions on the shader)
// is a 13-bit value. However, in order to also support separate samplers and textures (which uses // is a 13-bit value. However, in order to also support separate samplers and textures (which uses
@ -102,7 +103,7 @@ namespace Ryujinx.Graphics.Shader
if (handleType != TextureHandleType.SeparateConstantSamplerHandle) if (handleType != TextureHandleType.SeparateConstantSamplerHandle)
{ {
samplerHandle = cachedSamplerBuffer.Length != 0 ? cachedSamplerBuffer[samplerWordOffset] : 0; samplerHandle = samplerWordOffset < cachedSamplerBuffer.Length ? cachedSamplerBuffer[samplerWordOffset] : 0;
} }
else else
{ {

View file

@ -1,6 +1,7 @@
using Ryujinx.Graphics.Shader.Instructions; using Ryujinx.Graphics.Shader.Instructions;
using Ryujinx.Graphics.Shader.IntermediateRepresentation; using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr; using Ryujinx.Graphics.Shader.StructuredIr;
using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.Translation.Optimizations namespace Ryujinx.Graphics.Shader.Translation.Optimizations
@ -15,8 +16,12 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
// - The handle is a constant buffer value. // - The handle is a constant buffer value.
// - The handle is the result of a bitwise OR logical operation. // - The handle is the result of a bitwise OR logical operation.
// - Both sources of the OR operation comes from a constant buffer. // - Both sources of the OR operation comes from a constant buffer.
for (LinkedListNode<INode> node = block.Operations.First; node != null; node = node.Next) LinkedListNode<INode> nextNode;
for (LinkedListNode<INode> node = block.Operations.First; node != null; node = nextNode)
{ {
nextNode = node.Next;
if (node.Value is not TextureOperation texOp) if (node.Value is not TextureOperation texOp)
{ {
continue; continue;
@ -27,185 +32,283 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
continue; continue;
} }
if (texOp.Inst == Instruction.TextureSample || texOp.Inst.IsTextureQuery()) if (!TryConvertBindless(block, resourceManager, gpuAccessor, texOp) &&
!GenerateBindlessAccess(block, resourceManager, gpuAccessor, texOp, node))
{ {
Operand bindlessHandle = texOp.GetSource(0); // If we can't do bindless elimination, remove the texture operation.
// Set any destination variables to zero.
// In some cases the compiler uses a shuffle operation to get the handle, for (int destIndex = 0; destIndex < texOp.DestsCount; destIndex++)
// for some textureGrad implementations. In those cases, we can skip the shuffle.
if (bindlessHandle.AsgOp is Operation shuffleOp && shuffleOp.Inst == Instruction.Shuffle)
{ {
bindlessHandle = shuffleOp.GetSource(0); block.Operations.AddBefore(node, new Operation(Instruction.Copy, texOp.GetDest(destIndex), OperandHelper.Const(0)));
} }
bindlessHandle = Utils.FindLastOperation(bindlessHandle, block); Utils.DeleteNode(node, texOp);
}
}
}
// Some instructions do not encode an accurate sampler type: private static bool GenerateBindlessAccess(
// - Most instructions uses the same type for 1D and Buffer. BasicBlock block,
// - Query instructions may not have any type. ResourceManager resourceManager,
// For those cases, we need to try getting the type from current GPU state, IGpuAccessor gpuAccessor,
// as long bindless elimination is successful and we know where the texture descriptor is located. TextureOperation texOp,
bool rewriteSamplerType = LinkedListNode<INode> node)
texOp.Type == SamplerType.TextureBuffer || {
texOp.Inst == Instruction.TextureQuerySamples || Operand nvHandle = texOp.GetSource(0);
texOp.Inst == Instruction.TextureQuerySize;
if (bindlessHandle.Type == OperandType.ConstantBuffer) if (nvHandle.AsgOp is not Operation handleOp ||
handleOp.Inst != Instruction.Load ||
handleOp.StorageKind != StorageKind.Input)
{
// Right now, we only allow bindless access when the handle comes from a shader input.
// This is an artificial limitation to prevent it from being used in cases where it
// would have a large performance impact of loading all textures in the pool.
// It might be removed in the future, if we can mitigate the performance impact.
return false;
}
Operand textureHandle = OperandHelper.Local();
Operand samplerHandle = OperandHelper.Local();
Operand textureIndex = OperandHelper.Local();
block.Operations.AddBefore(node, new Operation(Instruction.BitwiseAnd, textureHandle, nvHandle, OperandHelper.Const(0xfffff)));
block.Operations.AddBefore(node, new Operation(Instruction.ShiftRightU32, samplerHandle, nvHandle, OperandHelper.Const(20)));
int texturePoolLength = Math.Max(BindlessToArray.MinimumArrayLength, gpuAccessor.QueryTextureArrayLengthFromPool());
block.Operations.AddBefore(node, new Operation(Instruction.MinimumU32, textureIndex, textureHandle, OperandHelper.Const(texturePoolLength - 1)));
texOp.SetSource(0, textureIndex);
bool hasSampler = !texOp.Inst.IsImage();
int textureBinding = resourceManager.GetTextureOrImageBinding(
texOp.Inst,
texOp.Type,
texOp.Format,
texOp.Flags & ~TextureFlags.Bindless,
0,
TextureHandle.PackOffsets(0, 0, TextureHandleType.Direct),
texturePoolLength,
hasSampler);
if (hasSampler)
{
Operand samplerIndex = OperandHelper.Local();
int samplerPoolLength = Math.Max(BindlessToArray.MinimumArrayLength, gpuAccessor.QuerySamplerArrayLengthFromPool());
block.Operations.AddBefore(node, new Operation(Instruction.MinimumU32, samplerIndex, samplerHandle, OperandHelper.Const(samplerPoolLength - 1)));
texOp.InsertSource(1, samplerIndex);
int samplerBinding = resourceManager.GetTextureOrImageBinding(
texOp.Inst,
SamplerType.None,
texOp.Format,
TextureFlags.None,
0,
TextureHandle.PackOffsets(0, 0, TextureHandleType.Direct),
samplerPoolLength);
texOp.TurnIntoArray(textureBinding, samplerBinding);
}
else
{
texOp.TurnIntoArray(textureBinding);
}
return true;
}
private static bool TryConvertBindless(BasicBlock block, ResourceManager resourceManager, IGpuAccessor gpuAccessor, TextureOperation texOp)
{
if (texOp.Inst == Instruction.TextureSample || texOp.Inst.IsTextureQuery())
{
Operand bindlessHandle = texOp.GetSource(0);
// In some cases the compiler uses a shuffle operation to get the handle,
// for some textureGrad implementations. In those cases, we can skip the shuffle.
if (bindlessHandle.AsgOp is Operation shuffleOp && shuffleOp.Inst == Instruction.Shuffle)
{
bindlessHandle = shuffleOp.GetSource(0);
}
bindlessHandle = Utils.FindLastOperation(bindlessHandle, block);
// Some instructions do not encode an accurate sampler type:
// - Most instructions uses the same type for 1D and Buffer.
// - Query instructions may not have any type.
// For those cases, we need to try getting the type from current GPU state,
// as long bindless elimination is successful and we know where the texture descriptor is located.
bool rewriteSamplerType =
texOp.Type == SamplerType.TextureBuffer ||
texOp.Inst == Instruction.TextureQuerySamples ||
texOp.Inst == Instruction.TextureQuerySize;
if (bindlessHandle.Type == OperandType.ConstantBuffer)
{
SetHandle(
resourceManager,
gpuAccessor,
texOp,
bindlessHandle.GetCbufOffset(),
bindlessHandle.GetCbufSlot(),
rewriteSamplerType,
isImage: false);
return true;
}
if (!TryGetOperation(bindlessHandle.AsgOp, out Operation handleCombineOp))
{
return false;
}
if (handleCombineOp.Inst != Instruction.BitwiseOr)
{
return false;
}
Operand src0 = Utils.FindLastOperation(handleCombineOp.GetSource(0), block);
Operand src1 = Utils.FindLastOperation(handleCombineOp.GetSource(1), block);
// For cases where we have a constant, ensure that the constant is always
// the second operand.
// Since this is a commutative operation, both are fine,
// and having a "canonical" representation simplifies some checks below.
if (src0.Type == OperandType.Constant && src1.Type != OperandType.Constant)
{
(src0, src1) = (src1, src0);
}
TextureHandleType handleType = TextureHandleType.SeparateSamplerHandle;
// Try to match the following patterns:
// Masked pattern:
// - samplerHandle = samplerHandle & 0xFFF00000;
// - textureHandle = textureHandle & 0xFFFFF;
// - combinedHandle = samplerHandle | textureHandle;
// Where samplerHandle and textureHandle comes from a constant buffer.
// Shifted pattern:
// - samplerHandle = samplerId << 20;
// - combinedHandle = samplerHandle | textureHandle;
// Where samplerId and textureHandle comes from a constant buffer.
// Constant pattern:
// - combinedHandle = samplerHandleConstant | textureHandle;
// Where samplerHandleConstant is a constant value, and textureHandle comes from a constant buffer.
if (src0.AsgOp is Operation src0AsgOp)
{
if (src1.AsgOp is Operation src1AsgOp &&
src0AsgOp.Inst == Instruction.BitwiseAnd &&
src1AsgOp.Inst == Instruction.BitwiseAnd)
{ {
SetHandle( src0 = GetSourceForMaskedHandle(src0AsgOp, 0xFFFFF);
resourceManager, src1 = GetSourceForMaskedHandle(src1AsgOp, 0xFFF00000);
gpuAccessor,
texOp,
bindlessHandle.GetCbufOffset(),
bindlessHandle.GetCbufSlot(),
rewriteSamplerType,
isImage: false);
continue; // The OR operation is commutative, so we can also try to swap the operands to get a match.
} if (src0 == null || src1 == null)
if (!TryGetOperation(bindlessHandle.AsgOp, out Operation handleCombineOp))
{
continue;
}
if (handleCombineOp.Inst != Instruction.BitwiseOr)
{
continue;
}
Operand src0 = Utils.FindLastOperation(handleCombineOp.GetSource(0), block);
Operand src1 = Utils.FindLastOperation(handleCombineOp.GetSource(1), block);
// For cases where we have a constant, ensure that the constant is always
// the second operand.
// Since this is a commutative operation, both are fine,
// and having a "canonical" representation simplifies some checks below.
if (src0.Type == OperandType.Constant && src1.Type != OperandType.Constant)
{
(src0, src1) = (src1, src0);
}
TextureHandleType handleType = TextureHandleType.SeparateSamplerHandle;
// Try to match the following patterns:
// Masked pattern:
// - samplerHandle = samplerHandle & 0xFFF00000;
// - textureHandle = textureHandle & 0xFFFFF;
// - combinedHandle = samplerHandle | textureHandle;
// Where samplerHandle and textureHandle comes from a constant buffer.
// Shifted pattern:
// - samplerHandle = samplerId << 20;
// - combinedHandle = samplerHandle | textureHandle;
// Where samplerId and textureHandle comes from a constant buffer.
// Constant pattern:
// - combinedHandle = samplerHandleConstant | textureHandle;
// Where samplerHandleConstant is a constant value, and textureHandle comes from a constant buffer.
if (src0.AsgOp is Operation src0AsgOp)
{
if (src1.AsgOp is Operation src1AsgOp &&
src0AsgOp.Inst == Instruction.BitwiseAnd &&
src1AsgOp.Inst == Instruction.BitwiseAnd)
{ {
src0 = GetSourceForMaskedHandle(src0AsgOp, 0xFFFFF); src0 = GetSourceForMaskedHandle(src1AsgOp, 0xFFFFF);
src1 = GetSourceForMaskedHandle(src1AsgOp, 0xFFF00000); src1 = GetSourceForMaskedHandle(src0AsgOp, 0xFFF00000);
// The OR operation is commutative, so we can also try to swap the operands to get a match.
if (src0 == null || src1 == null)
{
src0 = GetSourceForMaskedHandle(src1AsgOp, 0xFFFFF);
src1 = GetSourceForMaskedHandle(src0AsgOp, 0xFFF00000);
}
if (src0 == null || src1 == null)
{
continue;
}
} }
else if (src0AsgOp.Inst == Instruction.ShiftLeft)
{
Operand shift = src0AsgOp.GetSource(1);
if (shift.Type == OperandType.Constant && shift.Value == 20) if (src0 == null || src1 == null)
{ {
src0 = src1; return false;
src1 = src0AsgOp.GetSource(0);
handleType = TextureHandleType.SeparateSamplerId;
}
} }
} }
else if (src1.AsgOp is Operation src1AsgOp && src1AsgOp.Inst == Instruction.ShiftLeft) else if (src0AsgOp.Inst == Instruction.ShiftLeft)
{ {
Operand shift = src1AsgOp.GetSource(1); Operand shift = src0AsgOp.GetSource(1);
if (shift.Type == OperandType.Constant && shift.Value == 20) if (shift.Type == OperandType.Constant && shift.Value == 20)
{ {
src1 = src1AsgOp.GetSource(0); src0 = src1;
src1 = src0AsgOp.GetSource(0);
handleType = TextureHandleType.SeparateSamplerId; handleType = TextureHandleType.SeparateSamplerId;
} }
} }
else if (src1.Type == OperandType.Constant && (src1.Value & 0xfffff) == 0) }
{ else if (src1.AsgOp is Operation src1AsgOp && src1AsgOp.Inst == Instruction.ShiftLeft)
handleType = TextureHandleType.SeparateConstantSamplerHandle; {
} Operand shift = src1AsgOp.GetSource(1);
if (src0.Type != OperandType.ConstantBuffer) if (shift.Type == OperandType.Constant && shift.Value == 20)
{ {
continue; src1 = src1AsgOp.GetSource(0);
} handleType = TextureHandleType.SeparateSamplerId;
if (handleType == TextureHandleType.SeparateConstantSamplerHandle)
{
SetHandle(
resourceManager,
gpuAccessor,
texOp,
TextureHandle.PackOffsets(src0.GetCbufOffset(), ((src1.Value >> 20) & 0xfff), handleType),
TextureHandle.PackSlots(src0.GetCbufSlot(), 0),
rewriteSamplerType,
isImage: false);
}
else if (src1.Type == OperandType.ConstantBuffer)
{
SetHandle(
resourceManager,
gpuAccessor,
texOp,
TextureHandle.PackOffsets(src0.GetCbufOffset(), src1.GetCbufOffset(), handleType),
TextureHandle.PackSlots(src0.GetCbufSlot(), src1.GetCbufSlot()),
rewriteSamplerType,
isImage: false);
} }
} }
else if (texOp.Inst == Instruction.ImageLoad || else if (src1.Type == OperandType.Constant && (src1.Value & 0xfffff) == 0)
texOp.Inst == Instruction.ImageStore ||
texOp.Inst == Instruction.ImageAtomic)
{ {
Operand src0 = Utils.FindLastOperation(texOp.GetSource(0), block); handleType = TextureHandleType.SeparateConstantSamplerHandle;
}
if (src0.Type == OperandType.ConstantBuffer) if (src0.Type != OperandType.ConstantBuffer)
{ {
int cbufOffset = src0.GetCbufOffset(); return false;
int cbufSlot = src0.GetCbufSlot(); }
if (texOp.Format == TextureFormat.Unknown) if (handleType == TextureHandleType.SeparateConstantSamplerHandle)
{ {
if (texOp.Inst == Instruction.ImageAtomic) SetHandle(
{ resourceManager,
texOp.Format = ShaderProperties.GetTextureFormatAtomic(gpuAccessor, cbufOffset, cbufSlot); gpuAccessor,
} texOp,
else TextureHandle.PackOffsets(src0.GetCbufOffset(), ((src1.Value >> 20) & 0xfff), handleType),
{ TextureHandle.PackSlots(src0.GetCbufSlot(), 0),
texOp.Format = ShaderProperties.GetTextureFormat(gpuAccessor, cbufOffset, cbufSlot); rewriteSamplerType,
} isImage: false);
}
bool rewriteSamplerType = texOp.Type == SamplerType.TextureBuffer; return true;
}
else if (src1.Type == OperandType.ConstantBuffer)
{
SetHandle(
resourceManager,
gpuAccessor,
texOp,
TextureHandle.PackOffsets(src0.GetCbufOffset(), src1.GetCbufOffset(), handleType),
TextureHandle.PackSlots(src0.GetCbufSlot(), src1.GetCbufSlot()),
rewriteSamplerType,
isImage: false);
SetHandle(resourceManager, gpuAccessor, texOp, cbufOffset, cbufSlot, rewriteSamplerType, isImage: true); return true;
}
} }
} }
else if (texOp.Inst.IsImage())
{
Operand src0 = Utils.FindLastOperation(texOp.GetSource(0), block);
if (src0.Type == OperandType.ConstantBuffer)
{
int cbufOffset = src0.GetCbufOffset();
int cbufSlot = src0.GetCbufSlot();
if (texOp.Format == TextureFormat.Unknown)
{
if (texOp.Inst == Instruction.ImageAtomic)
{
texOp.Format = ShaderProperties.GetTextureFormatAtomic(gpuAccessor, cbufOffset, cbufSlot);
}
else
{
texOp.Format = ShaderProperties.GetTextureFormat(gpuAccessor, cbufOffset, cbufSlot);
}
}
bool rewriteSamplerType = texOp.Type == SamplerType.TextureBuffer;
SetHandle(resourceManager, gpuAccessor, texOp, cbufOffset, cbufSlot, rewriteSamplerType, isImage: true);
return true;
}
}
return false;
} }
private static bool TryGetOperation(INode asgOp, out Operation outOperation) private static bool TryGetOperation(INode asgOp, out Operation outOperation)

View file

@ -0,0 +1,236 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System;
using System.Collections.Generic;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
static class BindlessToArray
{
private const int NvnTextureBufferIndex = 2;
private const int HardcodedArrayLengthOgl = 4;
// 1 and 0 elements are not considered arrays anymore.
public const int MinimumArrayLength = 2;
public static void RunPassOgl(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;
}
TurnIntoArray(resourceManager, texOp, NvnTextureBufferIndex, addSrc1.Value / 4, HardcodedArrayLengthOgl);
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);
}
}
public static void RunPass(BasicBlock block, ResourceManager resourceManager, IGpuAccessor gpuAccessor)
{
// 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;
}
int secondaryCbufSlot = 0;
int secondaryCbufOffset = 0;
bool hasSecondaryHandle = false;
if (handleAsgOp.Inst == Instruction.BitwiseOr)
{
Operand src0 = handleAsgOp.GetSource(0);
Operand src1 = handleAsgOp.GetSource(1);
if (src0.Type == OperandType.ConstantBuffer && src1.AsgOp is Operation)
{
handleAsgOp = src1.AsgOp as Operation;
secondaryCbufSlot = src0.GetCbufSlot();
secondaryCbufOffset = src0.GetCbufOffset();
hasSecondaryHandle = true;
}
else if (src0.AsgOp is Operation && src1.Type == OperandType.ConstantBuffer)
{
handleAsgOp = src0.AsgOp as Operation;
secondaryCbufSlot = src1.GetCbufSlot();
secondaryCbufOffset = src1.GetCbufOffset();
hasSecondaryHandle = true;
}
}
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))
{
continue;
}
Operand ldcSrc1 = handleAsgOp.GetSource(1);
// We expect field index 0 to be accessed.
if (ldcSrc1.Type != OperandType.Constant || ldcSrc1.Value != 0)
{
continue;
}
Operand ldcVecIndex = handleAsgOp.GetSource(2);
Operand ldcElemIndex = handleAsgOp.GetSource(3);
if (ldcVecIndex.Type != OperandType.LocalVariable || ldcElemIndex.Type != OperandType.LocalVariable)
{
continue;
}
int cbufSlot;
int handleIndex;
if (hasSecondaryHandle)
{
cbufSlot = TextureHandle.PackSlots(src0CbufSlot, secondaryCbufSlot);
handleIndex = TextureHandle.PackOffsets(0, secondaryCbufOffset, TextureHandleType.SeparateSamplerHandle);
}
else
{
cbufSlot = src0CbufSlot;
handleIndex = 0;
}
int length = Math.Max(MinimumArrayLength, gpuAccessor.QueryTextureArrayLengthFromBuffer(src0CbufSlot));
TurnIntoArray(resourceManager, texOp, cbufSlot, handleIndex, length);
Operand vecIndex = Local();
Operand elemIndex = Local();
Operand index = Local();
Operand indexMin = Local();
block.Operations.AddBefore(node, new Operation(Instruction.ShiftLeft, vecIndex, ldcVecIndex, Const(1)));
block.Operations.AddBefore(node, new Operation(Instruction.ShiftRightU32, elemIndex, ldcElemIndex, Const(1)));
block.Operations.AddBefore(node, new Operation(Instruction.Add, index, vecIndex, elemIndex));
block.Operations.AddBefore(node, new Operation(Instruction.MinimumU32, indexMin, index, Const(length - 1)));
texOp.SetSource(0, indexMin);
}
}
private static void TurnIntoArray(ResourceManager resourceManager, TextureOperation texOp, int cbufSlot, int handleIndex, int length)
{
int binding = resourceManager.GetTextureOrImageBinding(
texOp.Inst,
texOp.Type,
texOp.Format,
texOp.Flags & ~TextureFlags.Bindless,
cbufSlot,
handleIndex,
length);
texOp.TurnIntoArray(binding);
}
}
}

View file

@ -1,118 +0,0 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System.Collections.Generic;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
static class BindlessToIndexed
{
private const int NvnTextureBufferIndex = 2;
public static void RunPass(BasicBlock block, ResourceManager resourceManager)
{
// We can turn a bindless texture access into a indexed access,
// as long the following conditions are true:
// - The handle is loaded using a LDC instruction.
// - The handle is loaded from the constant buffer with the handles (CB2 for NVN).
// - The load has a constant offset.
// The base offset of the array of handles on the constant buffer is the constant offset.
for (LinkedListNode<INode> node = block.Operations.First; node != null; node = node.Next)
{
if (node.Value is not TextureOperation texOp)
{
continue;
}
if ((texOp.Flags & TextureFlags.Bindless) == 0)
{
continue;
}
if (texOp.GetSource(0).AsgOp is not Operation handleAsgOp)
{
continue;
}
if (handleAsgOp.Inst != Instruction.Load ||
handleAsgOp.StorageKind != StorageKind.ConstantBuffer ||
handleAsgOp.SourcesCount != 4)
{
continue;
}
Operand ldcSrc0 = handleAsgOp.GetSource(0);
if (ldcSrc0.Type != OperandType.Constant ||
!resourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int src0CbufSlot) ||
src0CbufSlot != NvnTextureBufferIndex)
{
continue;
}
Operand ldcSrc1 = handleAsgOp.GetSource(1);
// We expect field index 0 to be accessed.
if (ldcSrc1.Type != OperandType.Constant || ldcSrc1.Value != 0)
{
continue;
}
Operand ldcSrc2 = handleAsgOp.GetSource(2);
// FIXME: This is missing some checks, for example, a check to ensure that the shift value is 2.
// Might be not worth fixing since if that doesn't kick in, the result will be no texture
// to access anyway which is also wrong.
// Plus this whole transform is fundamentally flawed as-is since we have no way to know the array size.
// Eventually, this should be entirely removed in favor of a implementation that supports true bindless
// texture access.
if (ldcSrc2.AsgOp is not Operation shrOp || shrOp.Inst != Instruction.ShiftRightU32)
{
continue;
}
if (shrOp.GetSource(0).AsgOp is not Operation shrOp2 || shrOp2.Inst != Instruction.ShiftRightU32)
{
continue;
}
if (shrOp2.GetSource(0).AsgOp is not Operation addOp || addOp.Inst != Instruction.Add)
{
continue;
}
Operand addSrc1 = addOp.GetSource(1);
if (addSrc1.Type != OperandType.Constant)
{
continue;
}
TurnIntoIndexed(resourceManager, texOp, addSrc1.Value / 4);
Operand index = Local();
Operand source = addOp.GetSource(0);
Operation shrBy3 = new(Instruction.ShiftRightU32, index, source, Const(3));
block.Operations.AddBefore(node, shrBy3);
texOp.SetSource(0, index);
}
}
private static void TurnIntoIndexed(ResourceManager resourceManager, TextureOperation texOp, int handle)
{
int binding = resourceManager.GetTextureOrImageBinding(
texOp.Inst,
texOp.Type | SamplerType.Indexed,
texOp.Format,
texOp.Flags & ~TextureFlags.Bindless,
NvnTextureBufferIndex,
handle);
texOp.TurnIntoIndexed(binding);
}
}
}

View file

@ -20,7 +20,15 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
// Those passes are looking for specific patterns and only needs to run once. // Those passes are looking for specific patterns and only needs to run once.
for (int blkIndex = 0; blkIndex < context.Blocks.Length; blkIndex++) for (int blkIndex = 0; blkIndex < context.Blocks.Length; blkIndex++)
{ {
BindlessToIndexed.RunPass(context.Blocks[blkIndex], context.ResourceManager); if (context.TargetApi == TargetApi.OpenGL)
{
BindlessToArray.RunPassOgl(context.Blocks[blkIndex], context.ResourceManager);
}
else
{
BindlessToArray.RunPass(context.Blocks[blkIndex], context.ResourceManager, context.GpuAccessor);
}
BindlessElimination.RunPass(context.Blocks[blkIndex], context.ResourceManager, context.GpuAccessor); BindlessElimination.RunPass(context.Blocks[blkIndex], context.ResourceManager, context.GpuAccessor);
// FragmentCoord only exists on fragment shaders, so we don't need to check other stages. // FragmentCoord only exists on fragment shaders, so we don't need to check other stages.

View file

@ -14,9 +14,6 @@ namespace Ryujinx.Graphics.Shader.Translation
private const int DefaultLocalMemorySize = 128; private const int DefaultLocalMemorySize = 128;
private const int DefaultSharedMemorySize = 4096; private const int DefaultSharedMemorySize = 4096;
// TODO: Non-hardcoded array size.
public const int SamplerArraySize = 4;
private static readonly string[] _stagePrefixes = new string[] { "cp", "vp", "tcp", "tep", "gp", "fp" }; private static readonly string[] _stagePrefixes = new string[] { "cp", "vp", "tcp", "tep", "gp", "fp" };
private readonly IGpuAccessor _gpuAccessor; private readonly IGpuAccessor _gpuAccessor;
@ -32,7 +29,7 @@ namespace Ryujinx.Graphics.Shader.Translation
private readonly HashSet<int> _usedConstantBufferBindings; private readonly HashSet<int> _usedConstantBufferBindings;
private readonly record struct TextureInfo(int CbufSlot, int Handle, bool Indexed, TextureFormat Format); private readonly record struct TextureInfo(int CbufSlot, int Handle, int ArrayLength, bool Separate, SamplerType Type, TextureFormat Format);
private struct TextureMeta private struct TextureMeta
{ {
@ -152,7 +149,7 @@ namespace Ryujinx.Graphics.Shader.Translation
int binding = _cbSlotToBindingMap[slot]; int binding = _cbSlotToBindingMap[slot];
if (binding < 0) if (binding < 0)
{ {
binding = _gpuAccessor.QueryBindingConstantBuffer(slot); binding = _gpuAccessor.CreateConstantBufferBinding(slot);
_cbSlotToBindingMap[slot] = binding; _cbSlotToBindingMap[slot] = binding;
string slotNumber = slot.ToString(CultureInfo.InvariantCulture); string slotNumber = slot.ToString(CultureInfo.InvariantCulture);
AddNewConstantBuffer(binding, $"{_stagePrefix}_c{slotNumber}"); AddNewConstantBuffer(binding, $"{_stagePrefix}_c{slotNumber}");
@ -173,7 +170,7 @@ namespace Ryujinx.Graphics.Shader.Translation
if (binding < 0) if (binding < 0)
{ {
binding = _gpuAccessor.QueryBindingStorageBuffer(slot); binding = _gpuAccessor.CreateStorageBufferBinding(slot);
_sbSlotToBindingMap[slot] = binding; _sbSlotToBindingMap[slot] = binding;
string slotNumber = slot.ToString(CultureInfo.InvariantCulture); string slotNumber = slot.ToString(CultureInfo.InvariantCulture);
AddNewStorageBuffer(binding, $"{_stagePrefix}_s{slotNumber}"); AddNewStorageBuffer(binding, $"{_stagePrefix}_s{slotNumber}");
@ -227,11 +224,13 @@ namespace Ryujinx.Graphics.Shader.Translation
TextureFormat format, TextureFormat format,
TextureFlags flags, TextureFlags flags,
int cbufSlot, int cbufSlot,
int handle) int handle,
int arrayLength = 1,
bool separate = false)
{ {
inst &= Instruction.Mask; inst &= Instruction.Mask;
bool isImage = inst == Instruction.ImageLoad || inst == Instruction.ImageStore || inst == Instruction.ImageAtomic; bool isImage = inst.IsImage();
bool isWrite = inst == Instruction.ImageStore || inst == Instruction.ImageAtomic; bool isWrite = inst.IsImageStore();
bool accurateType = !inst.IsTextureQuery(); bool accurateType = !inst.IsTextureQuery();
bool intCoords = isImage || flags.HasFlag(TextureFlags.IntCoords) || inst == Instruction.TextureQuerySize; bool intCoords = isImage || flags.HasFlag(TextureFlags.IntCoords) || inst == Instruction.TextureQuerySize;
bool coherent = flags.HasFlag(TextureFlags.Coherent); bool coherent = flags.HasFlag(TextureFlags.Coherent);
@ -241,7 +240,18 @@ namespace Ryujinx.Graphics.Shader.Translation
format = TextureFormat.Unknown; format = TextureFormat.Unknown;
} }
int binding = GetTextureOrImageBinding(cbufSlot, handle, type, format, isImage, intCoords, isWrite, accurateType, coherent); int binding = GetTextureOrImageBinding(
cbufSlot,
handle,
arrayLength,
type,
format,
isImage,
intCoords,
isWrite,
accurateType,
coherent,
separate);
_gpuAccessor.RegisterTexture(handle, cbufSlot); _gpuAccessor.RegisterTexture(handle, cbufSlot);
@ -251,16 +261,17 @@ namespace Ryujinx.Graphics.Shader.Translation
private int GetTextureOrImageBinding( private int GetTextureOrImageBinding(
int cbufSlot, int cbufSlot,
int handle, int handle,
int arrayLength,
SamplerType type, SamplerType type,
TextureFormat format, TextureFormat format,
bool isImage, bool isImage,
bool intCoords, bool intCoords,
bool write, bool write,
bool accurateType, bool accurateType,
bool coherent) bool coherent,
bool separate)
{ {
var dimensions = type.GetDimensions(); var dimensions = type == SamplerType.None ? 0 : type.GetDimensions();
var isIndexed = type.HasFlag(SamplerType.Indexed);
var dict = isImage ? _usedImages : _usedTextures; var dict = isImage ? _usedImages : _usedTextures;
var usageFlags = TextureUsageFlags.None; var usageFlags = TextureUsageFlags.None;
@ -269,7 +280,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{ {
usageFlags |= TextureUsageFlags.NeedsScaleValue; usageFlags |= TextureUsageFlags.NeedsScaleValue;
var canScale = _stage.SupportsRenderScale() && !isIndexed && !write && dimensions == 2; var canScale = _stage.SupportsRenderScale() && arrayLength == 1 && !write && dimensions == 2;
if (!canScale) if (!canScale)
{ {
@ -289,76 +300,80 @@ namespace Ryujinx.Graphics.Shader.Translation
usageFlags |= TextureUsageFlags.ImageCoherent; usageFlags |= TextureUsageFlags.ImageCoherent;
} }
int arraySize = isIndexed ? SamplerArraySize : 1; // For array textures, we also want to use type as key,
int firstBinding = -1; // since we may have texture handles stores in the same buffer, but for textures with different types.
var keyType = arrayLength > 1 ? type : SamplerType.None;
for (int layer = 0; layer < arraySize; layer++) var info = new TextureInfo(cbufSlot, handle, arrayLength, separate, keyType, format);
var meta = new TextureMeta()
{ {
var info = new TextureInfo(cbufSlot, handle + layer * 2, isIndexed, format); AccurateType = accurateType,
var meta = new TextureMeta() Type = type,
{ UsageFlags = usageFlags,
AccurateType = accurateType, };
Type = type,
UsageFlags = usageFlags,
};
int binding; int binding;
if (dict.TryGetValue(info, out var existingMeta)) if (dict.TryGetValue(info, out var existingMeta))
{ {
dict[info] = MergeTextureMeta(meta, existingMeta); dict[info] = MergeTextureMeta(meta, existingMeta);
binding = existingMeta.Binding; binding = existingMeta.Binding;
} }
else else
{ {
bool isBuffer = (type & SamplerType.Mask) == SamplerType.TextureBuffer; bool isBuffer = (type & SamplerType.Mask) == SamplerType.TextureBuffer;
binding = isImage binding = isImage
? _gpuAccessor.QueryBindingImage(dict.Count, isBuffer) ? _gpuAccessor.CreateImageBinding(arrayLength, isBuffer)
: _gpuAccessor.QueryBindingTexture(dict.Count, isBuffer); : _gpuAccessor.CreateTextureBinding(arrayLength, isBuffer);
meta.Binding = binding; meta.Binding = binding;
dict.Add(info, meta); dict.Add(info, meta);
}
string nameSuffix;
if (isImage)
{
nameSuffix = cbufSlot < 0
? $"i_tcb_{handle:X}_{format.ToGlslFormat()}"
: $"i_cb{cbufSlot}_{handle:X}_{format.ToGlslFormat()}";
}
else
{
nameSuffix = cbufSlot < 0 ? $"t_tcb_{handle:X}" : $"t_cb{cbufSlot}_{handle:X}";
}
var definition = new TextureDefinition(
isImage ? 3 : 2,
binding,
$"{_stagePrefix}_{nameSuffix}",
meta.Type,
info.Format,
meta.UsageFlags);
if (isImage)
{
Properties.AddOrUpdateImage(definition);
}
else
{
Properties.AddOrUpdateTexture(definition);
}
if (layer == 0)
{
firstBinding = binding;
}
} }
return firstBinding; string nameSuffix;
string prefix = isImage ? "i" : "t";
if (arrayLength != 1 && type != SamplerType.None)
{
prefix += type.ToShortSamplerType();
}
if (isImage)
{
nameSuffix = cbufSlot < 0
? $"{prefix}_tcb_{handle:X}_{format.ToGlslFormat()}"
: $"{prefix}_cb{cbufSlot}_{handle:X}_{format.ToGlslFormat()}";
}
else if (type == SamplerType.None)
{
nameSuffix = cbufSlot < 0 ? $"s_tcb_{handle:X}" : $"s_cb{cbufSlot}_{handle:X}";
}
else
{
nameSuffix = cbufSlot < 0 ? $"{prefix}_tcb_{handle:X}" : $"{prefix}_cb{cbufSlot}_{handle:X}";
}
var definition = new TextureDefinition(
isImage ? 3 : 2,
binding,
arrayLength,
separate,
$"{_stagePrefix}_{nameSuffix}",
meta.Type,
info.Format,
meta.UsageFlags);
if (isImage)
{
Properties.AddOrUpdateImage(definition);
}
else
{
Properties.AddOrUpdateTexture(definition);
}
return binding;
} }
private static TextureMeta MergeTextureMeta(TextureMeta meta, TextureMeta existingMeta) private static TextureMeta MergeTextureMeta(TextureMeta meta, TextureMeta existingMeta)
@ -399,8 +414,7 @@ namespace Ryujinx.Graphics.Shader.Translation
selectedMeta.UsageFlags |= TextureUsageFlags.NeedsScaleValue; selectedMeta.UsageFlags |= TextureUsageFlags.NeedsScaleValue;
var dimensions = type.GetDimensions(); var dimensions = type.GetDimensions();
var isIndexed = type.HasFlag(SamplerType.Indexed); var canScale = _stage.SupportsRenderScale() && selectedInfo.ArrayLength == 1 && dimensions == 2;
var canScale = _stage.SupportsRenderScale() && !isIndexed && dimensions == 2;
if (!canScale) if (!canScale)
{ {
@ -468,34 +482,63 @@ namespace Ryujinx.Graphics.Shader.Translation
return descriptors; return descriptors;
} }
public TextureDescriptor[] GetTextureDescriptors() public TextureDescriptor[] GetTextureDescriptors(bool includeArrays = true)
{ {
return GetDescriptors(_usedTextures, _usedTextures.Count); return GetDescriptors(_usedTextures, includeArrays);
} }
public TextureDescriptor[] GetImageDescriptors() public TextureDescriptor[] GetImageDescriptors(bool includeArrays = true)
{ {
return GetDescriptors(_usedImages, _usedImages.Count); return GetDescriptors(_usedImages, includeArrays);
} }
private static TextureDescriptor[] GetDescriptors(IReadOnlyDictionary<TextureInfo, TextureMeta> usedResources, int count) private static TextureDescriptor[] GetDescriptors(IReadOnlyDictionary<TextureInfo, TextureMeta> usedResources, bool includeArrays)
{ {
TextureDescriptor[] descriptors = new TextureDescriptor[count]; List<TextureDescriptor> descriptors = new();
int descriptorIndex = 0; bool hasAnyArray = false;
foreach ((TextureInfo info, TextureMeta meta) in usedResources) foreach ((TextureInfo info, TextureMeta meta) in usedResources)
{ {
descriptors[descriptorIndex++] = new TextureDescriptor( if (info.ArrayLength > 1)
{
hasAnyArray = true;
continue;
}
descriptors.Add(new TextureDescriptor(
meta.Binding, meta.Binding,
meta.Type, meta.Type,
info.Format, info.Format,
info.CbufSlot, info.CbufSlot,
info.Handle, info.Handle,
meta.UsageFlags); info.ArrayLength,
info.Separate,
meta.UsageFlags));
} }
return descriptors; if (hasAnyArray && includeArrays)
{
foreach ((TextureInfo info, TextureMeta meta) in usedResources)
{
if (info.ArrayLength <= 1)
{
continue;
}
descriptors.Add(new TextureDescriptor(
meta.Binding,
meta.Type,
info.Format,
info.CbufSlot,
info.Handle,
info.ArrayLength,
info.Separate,
meta.UsageFlags));
}
}
return descriptors.ToArray();
} }
public bool TryGetCbufSlotAndHandleForTexture(int binding, out int cbufSlot, out int handle) public bool TryGetCbufSlotAndHandleForTexture(int binding, out int cbufSlot, out int handle)
@ -531,6 +574,19 @@ namespace Ryujinx.Graphics.Shader.Translation
return FindDescriptorIndex(GetImageDescriptors(), binding); return FindDescriptorIndex(GetImageDescriptors(), binding);
} }
public bool IsArrayOfTexturesOrImages(int binding, bool isImage)
{
foreach ((TextureInfo info, TextureMeta meta) in isImage ? _usedImages : _usedTextures)
{
if (meta.Binding == binding)
{
return info.ArrayLength != 1;
}
}
return false;
}
private void AddNewConstantBuffer(int binding, string name) private void AddNewConstantBuffer(int binding, string name)
{ {
StructureType type = new(new[] StructureType type = new(new[]

View file

@ -9,6 +9,7 @@ namespace Ryujinx.Graphics.Shader.Translation
public readonly ShaderDefinitions Definitions; public readonly ShaderDefinitions Definitions;
public readonly ResourceManager ResourceManager; public readonly ResourceManager ResourceManager;
public readonly IGpuAccessor GpuAccessor; public readonly IGpuAccessor GpuAccessor;
public readonly TargetApi TargetApi;
public readonly TargetLanguage TargetLanguage; public readonly TargetLanguage TargetLanguage;
public readonly ShaderStage Stage; public readonly ShaderStage Stage;
public readonly ref FeatureFlags UsedFeatures; public readonly ref FeatureFlags UsedFeatures;
@ -19,6 +20,7 @@ namespace Ryujinx.Graphics.Shader.Translation
ShaderDefinitions definitions, ShaderDefinitions definitions,
ResourceManager resourceManager, ResourceManager resourceManager,
IGpuAccessor gpuAccessor, IGpuAccessor gpuAccessor,
TargetApi targetApi,
TargetLanguage targetLanguage, TargetLanguage targetLanguage,
ShaderStage stage, ShaderStage stage,
ref FeatureFlags usedFeatures) ref FeatureFlags usedFeatures)
@ -28,6 +30,7 @@ namespace Ryujinx.Graphics.Shader.Translation
Definitions = definitions; Definitions = definitions;
ResourceManager = resourceManager; ResourceManager = resourceManager;
GpuAccessor = gpuAccessor; GpuAccessor = gpuAccessor;
TargetApi = targetApi;
TargetLanguage = targetLanguage; TargetLanguage = targetLanguage;
Stage = stage; Stage = stage;
UsedFeatures = ref usedFeatures; UsedFeatures = ref usedFeatures;

View file

@ -23,7 +23,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
{ {
node = InsertCoordNormalization(context.Hfm, node, context.ResourceManager, context.GpuAccessor, context.Stage); node = InsertCoordNormalization(context.Hfm, node, context.ResourceManager, context.GpuAccessor, context.Stage);
node = InsertCoordGatherBias(node, context.ResourceManager, context.GpuAccessor); node = InsertCoordGatherBias(node, context.ResourceManager, context.GpuAccessor);
node = InsertConstOffsets(node, context.GpuAccessor, context.Stage); node = InsertConstOffsets(node, context.ResourceManager, context.GpuAccessor, context.Stage);
if (texOp.Type == SamplerType.TextureBuffer && !context.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat()) if (texOp.Type == SamplerType.TextureBuffer && !context.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat())
{ {
@ -45,13 +45,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
int coordsCount = texOp.Type.GetDimensions();
int coordsIndex = isBindless || isIndexed ? 1 : 0;
bool isImage = IsImageInstructionWithScale(texOp.Inst); bool isImage = IsImageInstructionWithScale(texOp.Inst);
bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage);
if ((texOp.Inst == Instruction.TextureSample || isImage) && if ((texOp.Inst == Instruction.TextureSample || isImage) &&
(intCoords || isImage) && (intCoords || isImage) &&
@ -62,9 +58,12 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
{ {
int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TexelFetchScale); int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TexelFetchScale);
int samplerIndex = isImage int samplerIndex = isImage
? resourceManager.GetTextureDescriptors().Length + resourceManager.FindImageDescriptorIndex(texOp.Binding) ? resourceManager.GetTextureDescriptors(includeArrays: false).Length + resourceManager.FindImageDescriptorIndex(texOp.Binding)
: resourceManager.FindTextureDescriptorIndex(texOp.Binding); : resourceManager.FindTextureDescriptorIndex(texOp.Binding);
int coordsCount = texOp.Type.GetDimensions();
int coordsIndex = isBindless ? 1 : 0;
for (int index = 0; index < coordsCount; index++) for (int index = 0; index < coordsCount; index++)
{ {
Operand scaledCoord = Local(); Operand scaledCoord = Local();
@ -97,7 +96,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
TextureOperation texOp = (TextureOperation)node.Value; TextureOperation texOp = (TextureOperation)node.Value;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage: false);
if (texOp.Inst == Instruction.TextureQuerySize && if (texOp.Inst == Instruction.TextureQuerySize &&
texOp.Index < 2 && texOp.Index < 2 &&
@ -152,8 +151,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
TextureOperation texOp = (TextureOperation)node.Value; TextureOperation texOp = (TextureOperation)node.Value;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage: false);
if (isBindless || !resourceManager.TryGetCbufSlotAndHandleForTexture(texOp.Binding, out int cbufSlot, out int handle)) if (isBindless || isIndexed || !resourceManager.TryGetCbufSlotAndHandleForTexture(texOp.Binding, out int cbufSlot, out int handle))
{ {
return node; return node;
} }
@ -167,10 +167,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
return node; return node;
} }
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
int coordsCount = texOp.Type.GetDimensions(); int coordsCount = texOp.Type.GetDimensions();
int coordsIndex = isBindless || isIndexed ? 1 : 0;
int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount; int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount;
@ -178,16 +175,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
{ {
Operand coordSize = Local(); Operand coordSize = Local();
Operand[] texSizeSources; Operand[] texSizeSources = new Operand[] { Const(0) };
if (isBindless || isIndexed)
{
texSizeSources = new Operand[] { texOp.GetSource(0), Const(0) };
}
else
{
texSizeSources = new Operand[] { Const(0) };
}
LinkedListNode<INode> textureSizeNode = node.List.AddBefore(node, new TextureOperation( LinkedListNode<INode> textureSizeNode = node.List.AddBefore(node, new TextureOperation(
Instruction.TextureQuerySize, Instruction.TextureQuerySize,
@ -201,13 +189,13 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
resourceManager.SetUsageFlagsForTextureQuery(texOp.Binding, texOp.Type); resourceManager.SetUsageFlagsForTextureQuery(texOp.Binding, texOp.Type);
Operand source = texOp.GetSource(coordsIndex + index); Operand source = texOp.GetSource(index);
Operand coordNormalized = Local(); Operand coordNormalized = Local();
node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Divide, coordNormalized, source, GenerateI2f(node, coordSize))); node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Divide, coordNormalized, source, GenerateI2f(node, coordSize)));
texOp.SetSource(coordsIndex + index, coordNormalized); texOp.SetSource(index, coordNormalized);
InsertTextureSizeUnscale(hfm, textureSizeNode, resourceManager, stage); InsertTextureSizeUnscale(hfm, textureSizeNode, resourceManager, stage);
} }
@ -234,7 +222,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
return node; return node;
} }
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage: false);
int coordsCount = texOp.Type.GetDimensions(); int coordsCount = texOp.Type.GetDimensions();
int coordsIndex = isBindless || isIndexed ? 1 : 0; int coordsIndex = isBindless || isIndexed ? 1 : 0;
@ -287,7 +275,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
return node; return node;
} }
private static LinkedListNode<INode> InsertConstOffsets(LinkedListNode<INode> node, IGpuAccessor gpuAccessor, ShaderStage stage) private static LinkedListNode<INode> InsertConstOffsets(LinkedListNode<INode> node, ResourceManager resourceManager, IGpuAccessor gpuAccessor, ShaderStage stage)
{ {
// Non-constant texture offsets are not allowed (according to the spec), // Non-constant texture offsets are not allowed (according to the spec),
// however some GPUs does support that. // however some GPUs does support that.
@ -321,7 +309,6 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
bool hasLodLevel = (texOp.Flags & TextureFlags.LodLevel) != 0; bool hasLodLevel = (texOp.Flags & TextureFlags.LodLevel) != 0;
bool isArray = (texOp.Type & SamplerType.Array) != 0; bool isArray = (texOp.Type & SamplerType.Array) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0; bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0;
bool isShadow = (texOp.Type & SamplerType.Shadow) != 0; bool isShadow = (texOp.Type & SamplerType.Shadow) != 0;
@ -342,6 +329,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
offsetsCount = 0; offsetsCount = 0;
} }
bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage: false);
Operand[] offsets = new Operand[offsetsCount]; Operand[] offsets = new Operand[offsetsCount];
Operand[] sources = new Operand[texOp.SourcesCount - offsetsCount]; Operand[] sources = new Operand[texOp.SourcesCount - offsetsCount];

View file

@ -294,6 +294,7 @@ namespace Ryujinx.Graphics.Shader.Translation
Definitions, Definitions,
resourceManager, resourceManager,
GpuAccessor, GpuAccessor,
Options.TargetApi,
Options.TargetLanguage, Options.TargetLanguage,
Definitions.Stage, Definitions.Stage,
ref usedFeatures); ref usedFeatures);
@ -412,7 +413,7 @@ namespace Ryujinx.Graphics.Shader.Translation
if (Stage == ShaderStage.Vertex) if (Stage == ShaderStage.Vertex)
{ {
int ibBinding = resourceManager.Reservations.IndexBufferTextureBinding; int ibBinding = resourceManager.Reservations.IndexBufferTextureBinding;
TextureDefinition indexBuffer = new(2, ibBinding, "ib_data", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None); TextureDefinition indexBuffer = new(2, ibBinding, "ib_data", SamplerType.TextureBuffer);
resourceManager.Properties.AddOrUpdateTexture(indexBuffer); resourceManager.Properties.AddOrUpdateTexture(indexBuffer);
int inputMap = _program.AttributeUsage.UsedInputAttributes; int inputMap = _program.AttributeUsage.UsedInputAttributes;
@ -421,7 +422,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{ {
int location = BitOperations.TrailingZeroCount(inputMap); int location = BitOperations.TrailingZeroCount(inputMap);
int binding = resourceManager.Reservations.GetVertexBufferTextureBinding(location); int binding = resourceManager.Reservations.GetVertexBufferTextureBinding(location);
TextureDefinition vaBuffer = new(2, binding, $"vb_data{location}", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None); TextureDefinition vaBuffer = new(2, binding, $"vb_data{location}", SamplerType.TextureBuffer);
resourceManager.Properties.AddOrUpdateTexture(vaBuffer); resourceManager.Properties.AddOrUpdateTexture(vaBuffer);
inputMap &= ~(1 << location); inputMap &= ~(1 << location);
@ -430,7 +431,7 @@ namespace Ryujinx.Graphics.Shader.Translation
else if (Stage == ShaderStage.Geometry) else if (Stage == ShaderStage.Geometry)
{ {
int trbBinding = resourceManager.Reservations.TopologyRemapBufferTextureBinding; int trbBinding = resourceManager.Reservations.TopologyRemapBufferTextureBinding;
TextureDefinition remapBuffer = new(2, trbBinding, "trb_data", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None); TextureDefinition remapBuffer = new(2, trbBinding, "trb_data", SamplerType.TextureBuffer);
resourceManager.Properties.AddOrUpdateTexture(remapBuffer); resourceManager.Properties.AddOrUpdateTexture(remapBuffer);
int geometryVbOutputSbBinding = resourceManager.Reservations.GeometryVertexOutputStorageBufferBinding; int geometryVbOutputSbBinding = resourceManager.Reservations.GeometryVertexOutputStorageBufferBinding;

View file

@ -6,7 +6,7 @@ namespace Ryujinx.Graphics.Vulkan
{ {
class DescriptorSetManager : IDisposable class DescriptorSetManager : IDisposable
{ {
public const uint MaxSets = 16; public const uint MaxSets = 8;
public class DescriptorPoolHolder : IDisposable public class DescriptorPoolHolder : IDisposable
{ {

View file

@ -43,11 +43,11 @@ namespace Ryujinx.Graphics.Vulkan
int binding = segment.Binding; int binding = segment.Binding;
int count = segment.Count; int count = segment.Count;
if (setIndex == PipelineBase.UniformSetIndex) if (IsBufferType(segment.Type))
{ {
entries[seg] = new DescriptorUpdateTemplateEntry() entries[seg] = new DescriptorUpdateTemplateEntry()
{ {
DescriptorType = DescriptorType.UniformBuffer, DescriptorType = segment.Type.Convert(),
DstBinding = (uint)binding, DstBinding = (uint)binding,
DescriptorCount = (uint)count, DescriptorCount = (uint)count,
Offset = structureOffset, Offset = structureOffset,
@ -56,76 +56,31 @@ namespace Ryujinx.Graphics.Vulkan
structureOffset += (nuint)(Unsafe.SizeOf<DescriptorBufferInfo>() * count); structureOffset += (nuint)(Unsafe.SizeOf<DescriptorBufferInfo>() * count);
} }
else if (setIndex == PipelineBase.StorageSetIndex) else if (IsBufferTextureType(segment.Type))
{ {
entries[seg] = new DescriptorUpdateTemplateEntry() entries[seg] = new DescriptorUpdateTemplateEntry()
{ {
DescriptorType = DescriptorType.StorageBuffer, DescriptorType = DescriptorType.UniformTexelBuffer,
DstBinding = (uint)binding, DstBinding = (uint)binding,
DescriptorCount = (uint)count, DescriptorCount = (uint)count,
Offset = structureOffset, Offset = structureOffset,
Stride = (nuint)Unsafe.SizeOf<DescriptorBufferInfo>() Stride = (nuint)Unsafe.SizeOf<BufferView>()
}; };
structureOffset += (nuint)(Unsafe.SizeOf<DescriptorBufferInfo>() * count); structureOffset += (nuint)(Unsafe.SizeOf<BufferView>() * count);
} }
else if (setIndex == PipelineBase.TextureSetIndex) else
{ {
if (segment.Type != ResourceType.BufferTexture) entries[seg] = new DescriptorUpdateTemplateEntry()
{ {
entries[seg] = new DescriptorUpdateTemplateEntry() DescriptorType = segment.Type.Convert(),
{ DstBinding = (uint)binding,
DescriptorType = DescriptorType.CombinedImageSampler, DescriptorCount = (uint)count,
DstBinding = (uint)binding, Offset = structureOffset,
DescriptorCount = (uint)count, Stride = (nuint)Unsafe.SizeOf<DescriptorImageInfo>()
Offset = structureOffset, };
Stride = (nuint)Unsafe.SizeOf<DescriptorImageInfo>()
};
structureOffset += (nuint)(Unsafe.SizeOf<DescriptorImageInfo>() * count); structureOffset += (nuint)(Unsafe.SizeOf<DescriptorImageInfo>() * count);
}
else
{
entries[seg] = new DescriptorUpdateTemplateEntry()
{
DescriptorType = DescriptorType.UniformTexelBuffer,
DstBinding = (uint)binding,
DescriptorCount = (uint)count,
Offset = structureOffset,
Stride = (nuint)Unsafe.SizeOf<BufferView>()
};
structureOffset += (nuint)(Unsafe.SizeOf<BufferView>() * count);
}
}
else if (setIndex == PipelineBase.ImageSetIndex)
{
if (segment.Type != ResourceType.BufferImage)
{
entries[seg] = new DescriptorUpdateTemplateEntry()
{
DescriptorType = DescriptorType.StorageImage,
DstBinding = (uint)binding,
DescriptorCount = (uint)count,
Offset = structureOffset,
Stride = (nuint)Unsafe.SizeOf<DescriptorImageInfo>()
};
structureOffset += (nuint)(Unsafe.SizeOf<DescriptorImageInfo>() * count);
}
else
{
entries[seg] = new DescriptorUpdateTemplateEntry()
{
DescriptorType = DescriptorType.StorageTexelBuffer,
DstBinding = (uint)binding,
DescriptorCount = (uint)count,
Offset = structureOffset,
Stride = (nuint)Unsafe.SizeOf<BufferView>()
};
structureOffset += (nuint)(Unsafe.SizeOf<BufferView>() * count);
}
} }
} }
@ -237,6 +192,16 @@ namespace Ryujinx.Graphics.Vulkan
Template = result; Template = result;
} }
private static bool IsBufferType(ResourceType type)
{
return type == ResourceType.UniformBuffer || type == ResourceType.StorageBuffer;
}
private static bool IsBufferTextureType(ResourceType type)
{
return type == ResourceType.BufferTexture || type == ResourceType.BufferImage;
}
public unsafe void Dispose() public unsafe void Dispose()
{ {
_gd.Api.DestroyDescriptorUpdateTemplate(_device, Template, null); _gd.Api.DestroyDescriptorUpdateTemplate(_device, Template, null);

View file

@ -14,6 +14,9 @@ namespace Ryujinx.Graphics.Vulkan
class DescriptorSetUpdater class DescriptorSetUpdater
{ {
private const ulong StorageBufferMaxMirrorable = 0x2000; private const ulong StorageBufferMaxMirrorable = 0x2000;
private const int ArrayGrowthSize = 16;
private record struct BufferRef private record struct BufferRef
{ {
public Auto<DisposableBuffer> Buffer; public Auto<DisposableBuffer> Buffer;
@ -65,6 +68,18 @@ namespace Ryujinx.Graphics.Vulkan
} }
} }
private record struct ArrayRef<T>
{
public ShaderStage Stage;
public T Array;
public ArrayRef(ShaderStage stage, T array)
{
Stage = stage;
Array = array;
}
}
private readonly VulkanRenderer _gd; private readonly VulkanRenderer _gd;
private readonly Device _device; private readonly Device _device;
private readonly PipelineBase _pipeline; private readonly PipelineBase _pipeline;
@ -78,6 +93,9 @@ namespace Ryujinx.Graphics.Vulkan
private readonly TextureBuffer[] _bufferImageRefs; private readonly TextureBuffer[] _bufferImageRefs;
private readonly Format[] _bufferImageFormats; private readonly Format[] _bufferImageFormats;
private ArrayRef<TextureArray>[] _textureArrayRefs;
private ArrayRef<ImageArray>[] _imageArrayRefs;
private readonly DescriptorBufferInfo[] _uniformBuffers; private readonly DescriptorBufferInfo[] _uniformBuffers;
private readonly DescriptorBufferInfo[] _storageBuffers; private readonly DescriptorBufferInfo[] _storageBuffers;
private readonly DescriptorImageInfo[] _textures; private readonly DescriptorImageInfo[] _textures;
@ -130,6 +148,9 @@ namespace Ryujinx.Graphics.Vulkan
_bufferImageRefs = new TextureBuffer[Constants.MaxImageBindings * 2]; _bufferImageRefs = new TextureBuffer[Constants.MaxImageBindings * 2];
_bufferImageFormats = new Format[Constants.MaxImageBindings * 2]; _bufferImageFormats = new Format[Constants.MaxImageBindings * 2];
_textureArrayRefs = Array.Empty<ArrayRef<TextureArray>>();
_imageArrayRefs = Array.Empty<ArrayRef<ImageArray>>();
_uniformBuffers = new DescriptorBufferInfo[Constants.MaxUniformBufferBindings]; _uniformBuffers = new DescriptorBufferInfo[Constants.MaxUniformBufferBindings];
_storageBuffers = new DescriptorBufferInfo[Constants.MaxStorageBufferBindings]; _storageBuffers = new DescriptorBufferInfo[Constants.MaxStorageBufferBindings];
_textures = new DescriptorImageInfo[Constants.MaxTexturesPerStage]; _textures = new DescriptorImageInfo[Constants.MaxTexturesPerStage];
@ -263,10 +284,18 @@ namespace Ryujinx.Graphics.Vulkan
{ {
if (segment.Type == ResourceType.TextureAndSampler) if (segment.Type == ResourceType.TextureAndSampler)
{ {
for (int i = 0; i < segment.Count; i++) if (!segment.IsArray)
{ {
ref var texture = ref _textureRefs[segment.Binding + i]; for (int i = 0; i < segment.Count; i++)
texture.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, texture.Stage.ConvertToPipelineStageFlags()); {
ref var texture = ref _textureRefs[segment.Binding + i];
texture.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, texture.Stage.ConvertToPipelineStageFlags());
}
}
else
{
PipelineStageFlags stageFlags = _textureArrayRefs[segment.Binding].Stage.ConvertToPipelineStageFlags();
_textureArrayRefs[segment.Binding].Array?.QueueWriteToReadBarriers(cbs, stageFlags);
} }
} }
} }
@ -275,10 +304,18 @@ namespace Ryujinx.Graphics.Vulkan
{ {
if (segment.Type == ResourceType.Image) if (segment.Type == ResourceType.Image)
{ {
for (int i = 0; i < segment.Count; i++) if (!segment.IsArray)
{ {
ref var image = ref _imageRefs[segment.Binding + i]; for (int i = 0; i < segment.Count; i++)
image.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, image.Stage.ConvertToPipelineStageFlags()); {
ref var image = ref _imageRefs[segment.Binding + i];
image.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, image.Stage.ConvertToPipelineStageFlags());
}
}
else
{
PipelineStageFlags stageFlags = _imageArrayRefs[segment.Binding].Stage.ConvertToPipelineStageFlags();
_imageArrayRefs[segment.Binding].Array?.QueueWriteToReadBarriers(cbs, stageFlags);
} }
} }
} }
@ -455,6 +492,58 @@ namespace Ryujinx.Graphics.Vulkan
} }
} }
public void SetTextureArray(CommandBufferScoped cbs, ShaderStage stage, int binding, ITextureArray array)
{
if (_textureArrayRefs.Length <= binding)
{
Array.Resize(ref _textureArrayRefs, binding + ArrayGrowthSize);
}
if (_textureArrayRefs[binding].Stage != stage || _textureArrayRefs[binding].Array != array)
{
if (_textureArrayRefs[binding].Array != null)
{
_textureArrayRefs[binding].Array.Bound = false;
}
if (array is TextureArray textureArray)
{
textureArray.Bound = true;
textureArray.QueueWriteToReadBarriers(cbs, stage.ConvertToPipelineStageFlags());
}
_textureArrayRefs[binding] = new ArrayRef<TextureArray>(stage, array as TextureArray);
SignalDirty(DirtyFlags.Texture);
}
}
public void SetImageArray(CommandBufferScoped cbs, ShaderStage stage, int binding, IImageArray array)
{
if (_imageArrayRefs.Length <= binding)
{
Array.Resize(ref _imageArrayRefs, binding + ArrayGrowthSize);
}
if (_imageArrayRefs[binding].Stage != stage || _imageArrayRefs[binding].Array != array)
{
if (_imageArrayRefs[binding].Array != null)
{
_imageArrayRefs[binding].Array.Bound = false;
}
if (array is ImageArray imageArray)
{
imageArray.Bound = true;
imageArray.QueueWriteToReadBarriers(cbs, stage.ConvertToPipelineStageFlags());
}
_imageArrayRefs[binding] = new ArrayRef<ImageArray>(stage, array as ImageArray);
SignalDirty(DirtyFlags.Image);
}
}
public void SetUniformBuffers(CommandBuffer commandBuffer, ReadOnlySpan<BufferAssignment> buffers) public void SetUniformBuffers(CommandBuffer commandBuffer, ReadOnlySpan<BufferAssignment> buffers)
{ {
for (int i = 0; i < buffers.Length; i++) for (int i = 0; i < buffers.Length; i++)
@ -653,66 +742,94 @@ namespace Ryujinx.Graphics.Vulkan
} }
else if (setIndex == PipelineBase.TextureSetIndex) else if (setIndex == PipelineBase.TextureSetIndex)
{ {
if (segment.Type != ResourceType.BufferTexture) if (!segment.IsArray)
{ {
Span<DescriptorImageInfo> textures = _textures; if (segment.Type != ResourceType.BufferTexture)
for (int i = 0; i < count; i++)
{ {
ref var texture = ref textures[i]; Span<DescriptorImageInfo> textures = _textures;
ref var refs = ref _textureRefs[binding + i];
texture.ImageView = refs.View?.Get(cbs).Value ?? default; for (int i = 0; i < count; i++)
texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default;
if (texture.ImageView.Handle == 0)
{ {
texture.ImageView = _dummyTexture.GetImageView().Get(cbs).Value; ref var texture = ref textures[i];
ref var refs = ref _textureRefs[binding + i];
texture.ImageView = refs.View?.Get(cbs).Value ?? default;
texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default;
if (texture.ImageView.Handle == 0)
{
texture.ImageView = _dummyTexture.GetImageView().Get(cbs).Value;
}
if (texture.Sampler.Handle == 0)
{
texture.Sampler = _dummySampler.GetSampler().Get(cbs).Value;
}
} }
if (texture.Sampler.Handle == 0) tu.Push<DescriptorImageInfo>(textures[..count]);
{
texture.Sampler = _dummySampler.GetSampler().Get(cbs).Value;
}
} }
else
{
Span<BufferView> bufferTextures = _bufferTextures;
tu.Push<DescriptorImageInfo>(textures[..count]); for (int i = 0; i < count; i++)
{
bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs, false) ?? default;
}
tu.Push<BufferView>(bufferTextures[..count]);
}
} }
else else
{ {
Span<BufferView> bufferTextures = _bufferTextures; if (segment.Type != ResourceType.BufferTexture)
for (int i = 0; i < count; i++)
{ {
bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs, false) ?? default; tu.Push(_textureArrayRefs[binding].Array.GetImageInfos(_gd, cbs, _dummyTexture, _dummySampler));
}
else
{
tu.Push(_textureArrayRefs[binding].Array.GetBufferViews(cbs));
} }
tu.Push<BufferView>(bufferTextures[..count]);
} }
} }
else if (setIndex == PipelineBase.ImageSetIndex) else if (setIndex == PipelineBase.ImageSetIndex)
{ {
if (segment.Type != ResourceType.BufferImage) if (!segment.IsArray)
{ {
Span<DescriptorImageInfo> images = _images; if (segment.Type != ResourceType.BufferImage)
for (int i = 0; i < count; i++)
{ {
images[i].ImageView = _imageRefs[binding + i].View?.Get(cbs).Value ?? default; Span<DescriptorImageInfo> images = _images;
}
tu.Push<DescriptorImageInfo>(images[..count]); for (int i = 0; i < count; i++)
{
images[i].ImageView = _imageRefs[binding + i].View?.Get(cbs).Value ?? default;
}
tu.Push<DescriptorImageInfo>(images[..count]);
}
else
{
Span<BufferView> bufferImages = _bufferImages;
for (int i = 0; i < count; i++)
{
bufferImages[i] = _bufferImageRefs[binding + i]?.GetBufferView(cbs, _bufferImageFormats[binding + i], true) ?? default;
}
tu.Push<BufferView>(bufferImages[..count]);
}
} }
else else
{ {
Span<BufferView> bufferImages = _bufferImages; if (segment.Type != ResourceType.BufferTexture)
for (int i = 0; i < count; i++)
{ {
bufferImages[i] = _bufferImageRefs[binding + i]?.GetBufferView(cbs, _bufferImageFormats[binding + i], true) ?? default; tu.Push(_imageArrayRefs[binding].Array.GetImageInfos(_gd, cbs, _dummyTexture));
}
else
{
tu.Push(_imageArrayRefs[binding].Array.GetBufferViews(cbs));
} }
tu.Push<BufferView>(bufferImages[..count]);
} }
} }
} }
@ -825,6 +942,16 @@ namespace Ryujinx.Graphics.Vulkan
AdvancePdSequence(); AdvancePdSequence();
} }
public void ForceTextureDirty()
{
SignalDirty(DirtyFlags.Texture);
}
public void ForceImageDirty()
{
SignalDirty(DirtyFlags.Image);
}
private static void SwapBuffer(BufferRef[] list, Auto<DisposableBuffer> from, Auto<DisposableBuffer> to) private static void SwapBuffer(BufferRef[] list, Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
{ {
for (int i = 0; i < list.Length; i++) for (int i = 0; i < list.Length; i++)

View file

@ -0,0 +1,181 @@
using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan;
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Vulkan
{
class ImageArray : IImageArray
{
private readonly VulkanRenderer _gd;
private record struct TextureRef
{
public TextureStorage Storage;
public TextureView View;
public GAL.Format ImageFormat;
}
private readonly TextureRef[] _textureRefs;
private readonly TextureBuffer[] _bufferTextureRefs;
private readonly DescriptorImageInfo[] _textures;
private readonly BufferView[] _bufferTextures;
private HashSet<TextureStorage> _storages;
private int _cachedCommandBufferIndex;
private int _cachedSubmissionCount;
private readonly bool _isBuffer;
public bool Bound;
public bool IsDirty => _storages == null;
public ImageArray(VulkanRenderer gd, int size, bool isBuffer)
{
_gd = gd;
if (isBuffer)
{
_bufferTextureRefs = new TextureBuffer[size];
_bufferTextures = new BufferView[size];
}
else
{
_textureRefs = new TextureRef[size];
_textures = new DescriptorImageInfo[size];
}
_storages = null;
_cachedCommandBufferIndex = -1;
_cachedSubmissionCount = 0;
_isBuffer = isBuffer;
}
public void SetFormats(int index, GAL.Format[] imageFormats)
{
for (int i = 0; i < imageFormats.Length; i++)
{
_textureRefs[index + i].ImageFormat = imageFormats[i];
}
SetDirty();
}
public void SetImages(int index, ITexture[] images)
{
for (int i = 0; i < images.Length; i++)
{
ITexture image = images[i];
if (image is TextureBuffer textureBuffer)
{
_bufferTextureRefs[index + i] = textureBuffer;
}
else if (image is TextureView view)
{
_textureRefs[index + i].Storage = view.Storage;
_textureRefs[index + i].View = view;
}
else if (!_isBuffer)
{
_textureRefs[index + i].Storage = null;
_textureRefs[index + i].View = default;
}
else
{
_bufferTextureRefs[index + i] = null;
}
}
SetDirty();
}
private void SetDirty()
{
_cachedCommandBufferIndex = -1;
_storages = null;
_gd.PipelineInternal.ForceImageDirty();
}
public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags)
{
HashSet<TextureStorage> storages = _storages;
if (storages == null)
{
storages = new HashSet<TextureStorage>();
for (int index = 0; index < _textureRefs.Length; index++)
{
if (_textureRefs[index].Storage != null)
{
storages.Add(_textureRefs[index].Storage);
}
}
_storages = storages;
}
foreach (TextureStorage storage in storages)
{
storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stageFlags);
}
}
public ReadOnlySpan<DescriptorImageInfo> GetImageInfos(VulkanRenderer gd, CommandBufferScoped cbs, TextureView dummyTexture)
{
int submissionCount = gd.CommandBufferPool.GetSubmissionCount(cbs.CommandBufferIndex);
Span<DescriptorImageInfo> textures = _textures;
if (cbs.CommandBufferIndex == _cachedCommandBufferIndex && submissionCount == _cachedSubmissionCount)
{
return textures;
}
_cachedCommandBufferIndex = cbs.CommandBufferIndex;
_cachedSubmissionCount = submissionCount;
for (int i = 0; i < textures.Length; i++)
{
ref var texture = ref textures[i];
ref var refs = ref _textureRefs[i];
if (i > 0 && _textureRefs[i - 1].View == refs.View && _textureRefs[i - 1].ImageFormat == refs.ImageFormat)
{
texture = textures[i - 1];
continue;
}
texture.ImageLayout = ImageLayout.General;
texture.ImageView = refs.View?.GetView(refs.ImageFormat).GetIdentityImageView().Get(cbs).Value ?? default;
if (texture.ImageView.Handle == 0)
{
texture.ImageView = dummyTexture.GetImageView().Get(cbs).Value;
}
}
return textures;
}
public ReadOnlySpan<BufferView> GetBufferViews(CommandBufferScoped cbs)
{
Span<BufferView> bufferTextures = _bufferTextures;
for (int i = 0; i < bufferTextures.Length; i++)
{
bufferTextures[i] = _bufferTextureRefs[i]?.GetBufferView(cbs, _textureRefs[i].ImageFormat, true) ?? default;
}
return bufferTextures;
}
}
}

View file

@ -898,6 +898,11 @@ namespace Ryujinx.Graphics.Vulkan
_descriptorSetUpdater.SetImage(binding, image); _descriptorSetUpdater.SetImage(binding, image);
} }
public void SetImageArray(ShaderStage stage, int binding, IImageArray array)
{
_descriptorSetUpdater.SetImageArray(Cbs, stage, binding, array);
}
public void SetIndexBuffer(BufferRange buffer, IndexType type) public void SetIndexBuffer(BufferRange buffer, IndexType type)
{ {
if (buffer.Handle != BufferHandle.Null) if (buffer.Handle != BufferHandle.Null)
@ -1145,6 +1150,11 @@ namespace Ryujinx.Graphics.Vulkan
_descriptorSetUpdater.SetTextureAndSamplerIdentitySwizzle(Cbs, stage, binding, texture, sampler); _descriptorSetUpdater.SetTextureAndSamplerIdentitySwizzle(Cbs, stage, binding, texture, sampler);
} }
public void SetTextureArray(ShaderStage stage, int binding, ITextureArray array)
{
_descriptorSetUpdater.SetTextureArray(Cbs, stage, binding, array);
}
public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers) public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers)
{ {
PauseTransformFeedbackInternal(); PauseTransformFeedbackInternal();
@ -1374,6 +1384,16 @@ namespace Ryujinx.Graphics.Vulkan
SignalCommandBufferChange(); SignalCommandBufferChange();
} }
public void ForceTextureDirty()
{
_descriptorSetUpdater.ForceTextureDirty();
}
public void ForceImageDirty()
{
_descriptorSetUpdater.ForceImageDirty();
}
public unsafe void TextureBarrier() public unsafe void TextureBarrier()
{ {
MemoryBarrier memoryBarrier = new() MemoryBarrier memoryBarrier = new()

View file

@ -8,14 +8,7 @@ namespace Ryujinx.Graphics.Vulkan
{ {
class PipelineLayoutCacheEntry class PipelineLayoutCacheEntry
{ {
// Those were adjusted based on current descriptor usage and the descriptor counts usually used on pipeline layouts. private const int MaxPoolSizesPerSet = 8;
// It might be a good idea to tweak them again if those change, or maybe find a way to calculate an optimal value dynamically.
private const uint DefaultUniformBufferPoolCapacity = 19 * DescriptorSetManager.MaxSets;
private const uint DefaultStorageBufferPoolCapacity = 16 * DescriptorSetManager.MaxSets;
private const uint DefaultTexturePoolCapacity = 128 * DescriptorSetManager.MaxSets;
private const uint DefaultImagePoolCapacity = 8 * DescriptorSetManager.MaxSets;
private const int MaxPoolSizesPerSet = 2;
private readonly VulkanRenderer _gd; private readonly VulkanRenderer _gd;
private readonly Device _device; private readonly Device _device;
@ -24,6 +17,9 @@ namespace Ryujinx.Graphics.Vulkan
public PipelineLayout PipelineLayout { get; } public PipelineLayout PipelineLayout { get; }
private readonly int[] _consumedDescriptorsPerSet; private readonly int[] _consumedDescriptorsPerSet;
private readonly DescriptorPoolSize[][] _poolSizes;
private readonly DescriptorSetManager _descriptorSetManager;
private readonly List<Auto<DescriptorSetCollection>>[][] _dsCache; private readonly List<Auto<DescriptorSetCollection>>[][] _dsCache;
private List<Auto<DescriptorSetCollection>>[] _currentDsCache; private List<Auto<DescriptorSetCollection>>[] _currentDsCache;
@ -65,6 +61,9 @@ namespace Ryujinx.Graphics.Vulkan
(DescriptorSetLayouts, PipelineLayout) = PipelineLayoutFactory.Create(gd, device, setDescriptors, usePushDescriptors); (DescriptorSetLayouts, PipelineLayout) = PipelineLayoutFactory.Create(gd, device, setDescriptors, usePushDescriptors);
_consumedDescriptorsPerSet = new int[setDescriptors.Count]; _consumedDescriptorsPerSet = new int[setDescriptors.Count];
_poolSizes = new DescriptorPoolSize[setDescriptors.Count][];
Span<DescriptorPoolSize> poolSizes = stackalloc DescriptorPoolSize[MaxPoolSizesPerSet];
for (int setIndex = 0; setIndex < setDescriptors.Count; setIndex++) for (int setIndex = 0; setIndex < setDescriptors.Count; setIndex++)
{ {
@ -76,6 +75,7 @@ namespace Ryujinx.Graphics.Vulkan
} }
_consumedDescriptorsPerSet[setIndex] = count; _consumedDescriptorsPerSet[setIndex] = count;
_poolSizes[setIndex] = GetDescriptorPoolSizes(poolSizes, setDescriptors[setIndex], DescriptorSetManager.MaxSets).ToArray();
} }
if (usePushDescriptors) if (usePushDescriptors)
@ -83,6 +83,8 @@ namespace Ryujinx.Graphics.Vulkan
_pdDescriptors = setDescriptors[0]; _pdDescriptors = setDescriptors[0];
_pdTemplates = new(); _pdTemplates = new();
} }
_descriptorSetManager = new DescriptorSetManager(_device, setDescriptors.Count);
} }
public void UpdateCommandBufferIndex(int commandBufferIndex) public void UpdateCommandBufferIndex(int commandBufferIndex)
@ -105,17 +107,12 @@ namespace Ryujinx.Graphics.Vulkan
int index = _dsCacheCursor[setIndex]++; int index = _dsCacheCursor[setIndex]++;
if (index == list.Count) if (index == list.Count)
{ {
Span<DescriptorPoolSize> poolSizes = stackalloc DescriptorPoolSize[MaxPoolSizesPerSet]; var dsc = _descriptorSetManager.AllocateDescriptorSet(
poolSizes = GetDescriptorPoolSizes(poolSizes, setIndex);
int consumedDescriptors = _consumedDescriptorsPerSet[setIndex];
var dsc = _gd.DescriptorSetManager.AllocateDescriptorSet(
_gd.Api, _gd.Api,
DescriptorSetLayouts[setIndex], DescriptorSetLayouts[setIndex],
poolSizes, _poolSizes[setIndex],
setIndex, setIndex,
consumedDescriptors, _consumedDescriptorsPerSet[setIndex],
false); false);
list.Add(dsc); list.Add(dsc);
@ -127,28 +124,35 @@ namespace Ryujinx.Graphics.Vulkan
return list[index]; return list[index];
} }
private static Span<DescriptorPoolSize> GetDescriptorPoolSizes(Span<DescriptorPoolSize> output, int setIndex) private static Span<DescriptorPoolSize> GetDescriptorPoolSizes(Span<DescriptorPoolSize> output, ResourceDescriptorCollection setDescriptor, uint multiplier)
{ {
int count = 1; int count = 0;
switch (setIndex) for (int index = 0; index < setDescriptor.Descriptors.Count; index++)
{ {
case PipelineBase.UniformSetIndex: ResourceDescriptor descriptor = setDescriptor.Descriptors[index];
output[0] = new(DescriptorType.UniformBuffer, DefaultUniformBufferPoolCapacity); DescriptorType descriptorType = descriptor.Type.Convert();
break;
case PipelineBase.StorageSetIndex: bool found = false;
output[0] = new(DescriptorType.StorageBuffer, DefaultStorageBufferPoolCapacity);
break; for (int poolSizeIndex = 0; poolSizeIndex < count; poolSizeIndex++)
case PipelineBase.TextureSetIndex: {
output[0] = new(DescriptorType.CombinedImageSampler, DefaultTexturePoolCapacity); if (output[poolSizeIndex].Type == descriptorType)
output[1] = new(DescriptorType.UniformTexelBuffer, DefaultTexturePoolCapacity); {
count = 2; output[poolSizeIndex].DescriptorCount += (uint)descriptor.Count * multiplier;
break; found = true;
case PipelineBase.ImageSetIndex: break;
output[0] = new(DescriptorType.StorageImage, DefaultImagePoolCapacity); }
output[1] = new(DescriptorType.StorageTexelBuffer, DefaultImagePoolCapacity); }
count = 2;
break; if (!found)
{
output[count++] = new DescriptorPoolSize()
{
Type = descriptorType,
DescriptorCount = (uint)descriptor.Count,
};
}
} }
return output[..count]; return output[..count];
@ -206,6 +210,8 @@ namespace Ryujinx.Graphics.Vulkan
{ {
_gd.Api.DestroyDescriptorSetLayout(_device, DescriptorSetLayouts[i], null); _gd.Api.DestroyDescriptorSetLayout(_device, DescriptorSetLayouts[i], null);
} }
_descriptorSetManager.Dispose();
} }
} }

View file

@ -8,13 +8,15 @@ namespace Ryujinx.Graphics.Vulkan
public readonly int Count; public readonly int Count;
public readonly ResourceType Type; public readonly ResourceType Type;
public readonly ResourceStages Stages; public readonly ResourceStages Stages;
public readonly bool IsArray;
public ResourceBindingSegment(int binding, int count, ResourceType type, ResourceStages stages) public ResourceBindingSegment(int binding, int count, ResourceType type, ResourceStages stages, bool isArray)
{ {
Binding = binding; Binding = binding;
Count = count; Count = count;
Type = type; Type = type;
Stages = stages; Stages = stages;
IsArray = isArray;
} }
} }
} }

View file

@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Vulkan
}; };
_resourceDescriptors[setIndex].Add(new ResourceDescriptor(binding, 1, type, stages)); _resourceDescriptors[setIndex].Add(new ResourceDescriptor(binding, 1, type, stages));
_resourceUsages[setIndex].Add(new ResourceUsage(binding, type, stages)); _resourceUsages[setIndex].Add(new ResourceUsage(binding, 1, type, stages));
return this; return this;
} }

View file

@ -240,7 +240,9 @@ namespace Ryujinx.Graphics.Vulkan
if (currentDescriptor.Binding + currentCount != descriptor.Binding || if (currentDescriptor.Binding + currentCount != descriptor.Binding ||
currentDescriptor.Type != descriptor.Type || currentDescriptor.Type != descriptor.Type ||
currentDescriptor.Stages != descriptor.Stages) currentDescriptor.Stages != descriptor.Stages ||
currentDescriptor.Count > 1 ||
descriptor.Count > 1)
{ {
if (currentCount != 0) if (currentCount != 0)
{ {
@ -248,7 +250,8 @@ namespace Ryujinx.Graphics.Vulkan
currentDescriptor.Binding, currentDescriptor.Binding,
currentCount, currentCount,
currentDescriptor.Type, currentDescriptor.Type,
currentDescriptor.Stages)); currentDescriptor.Stages,
currentDescriptor.Count > 1));
} }
currentDescriptor = descriptor; currentDescriptor = descriptor;
@ -266,7 +269,8 @@ namespace Ryujinx.Graphics.Vulkan
currentDescriptor.Binding, currentDescriptor.Binding,
currentCount, currentCount,
currentDescriptor.Type, currentDescriptor.Type,
currentDescriptor.Stages)); currentDescriptor.Stages,
currentDescriptor.Count > 1));
} }
segments[setIndex] = currentSegments.ToArray(); segments[setIndex] = currentSegments.ToArray();
@ -292,7 +296,9 @@ namespace Ryujinx.Graphics.Vulkan
if (currentUsage.Binding + currentCount != usage.Binding || if (currentUsage.Binding + currentCount != usage.Binding ||
currentUsage.Type != usage.Type || currentUsage.Type != usage.Type ||
currentUsage.Stages != usage.Stages) currentUsage.Stages != usage.Stages ||
currentUsage.ArrayLength > 1 ||
usage.ArrayLength > 1)
{ {
if (currentCount != 0) if (currentCount != 0)
{ {
@ -300,11 +306,12 @@ namespace Ryujinx.Graphics.Vulkan
currentUsage.Binding, currentUsage.Binding,
currentCount, currentCount,
currentUsage.Type, currentUsage.Type,
currentUsage.Stages)); currentUsage.Stages,
currentUsage.ArrayLength > 1));
} }
currentUsage = usage; currentUsage = usage;
currentCount = 1; currentCount = usage.ArrayLength;
} }
else else
{ {
@ -318,7 +325,8 @@ namespace Ryujinx.Graphics.Vulkan
currentUsage.Binding, currentUsage.Binding,
currentCount, currentCount,
currentUsage.Type, currentUsage.Type,
currentUsage.Stages)); currentUsage.Stages,
currentUsage.ArrayLength > 1));
} }
segments[setIndex] = currentSegments.ToArray(); segments[setIndex] = currentSegments.ToArray();
@ -343,7 +351,12 @@ namespace Ryujinx.Graphics.Vulkan
if (segments != null && segments.Length > 0) if (segments != null && segments.Length > 0)
{ {
templates[setIndex] = new DescriptorSetTemplate(_gd, _device, segments, _plce, IsCompute ? PipelineBindPoint.Compute : PipelineBindPoint.Graphics, setIndex); templates[setIndex] = new DescriptorSetTemplate(
_gd,
_device,
segments,
_plce,
IsCompute ? PipelineBindPoint.Compute : PipelineBindPoint.Graphics, setIndex);
} }
} }

View file

@ -0,0 +1,194 @@
using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan;
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Vulkan
{
class TextureArray : ITextureArray
{
private readonly VulkanRenderer _gd;
private struct TextureRef
{
public TextureStorage Storage;
public Auto<DisposableImageView> View;
public Auto<DisposableSampler> Sampler;
}
private readonly TextureRef[] _textureRefs;
private readonly TextureBuffer[] _bufferTextureRefs;
private readonly DescriptorImageInfo[] _textures;
private readonly BufferView[] _bufferTextures;
private HashSet<TextureStorage> _storages;
private int _cachedCommandBufferIndex;
private int _cachedSubmissionCount;
private readonly bool _isBuffer;
public bool Bound;
public TextureArray(VulkanRenderer gd, int size, bool isBuffer)
{
_gd = gd;
if (isBuffer)
{
_bufferTextureRefs = new TextureBuffer[size];
_bufferTextures = new BufferView[size];
}
else
{
_textureRefs = new TextureRef[size];
_textures = new DescriptorImageInfo[size];
}
_storages = null;
_cachedCommandBufferIndex = -1;
_cachedSubmissionCount = 0;
_isBuffer = isBuffer;
}
public void SetSamplers(int index, ISampler[] samplers)
{
for (int i = 0; i < samplers.Length; i++)
{
ISampler sampler = samplers[i];
if (sampler is SamplerHolder samplerHolder)
{
_textureRefs[index + i].Sampler = samplerHolder.GetSampler();
}
else
{
_textureRefs[index + i].Sampler = default;
}
}
SetDirty();
}
public void SetTextures(int index, ITexture[] textures)
{
for (int i = 0; i < textures.Length; i++)
{
ITexture texture = textures[i];
if (texture is TextureBuffer textureBuffer)
{
_bufferTextureRefs[index + i] = textureBuffer;
}
else if (texture is TextureView view)
{
_textureRefs[index + i].Storage = view.Storage;
_textureRefs[index + i].View = view.GetImageView();
}
else if (!_isBuffer)
{
_textureRefs[index + i].Storage = null;
_textureRefs[index + i].View = default;
}
else
{
_bufferTextureRefs[index + i] = null;
}
}
SetDirty();
}
private void SetDirty()
{
_cachedCommandBufferIndex = -1;
_storages = null;
_gd.PipelineInternal.ForceTextureDirty();
}
public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags)
{
HashSet<TextureStorage> storages = _storages;
if (storages == null)
{
storages = new HashSet<TextureStorage>();
for (int index = 0; index < _textureRefs.Length; index++)
{
if (_textureRefs[index].Storage != null)
{
storages.Add(_textureRefs[index].Storage);
}
}
_storages = storages;
}
foreach (TextureStorage storage in storages)
{
storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stageFlags);
}
}
public ReadOnlySpan<DescriptorImageInfo> GetImageInfos(VulkanRenderer gd, CommandBufferScoped cbs, TextureView dummyTexture, SamplerHolder dummySampler)
{
int submissionCount = gd.CommandBufferPool.GetSubmissionCount(cbs.CommandBufferIndex);
Span<DescriptorImageInfo> textures = _textures;
if (cbs.CommandBufferIndex == _cachedCommandBufferIndex && submissionCount == _cachedSubmissionCount)
{
return textures;
}
_cachedCommandBufferIndex = cbs.CommandBufferIndex;
_cachedSubmissionCount = submissionCount;
for (int i = 0; i < textures.Length; i++)
{
ref var texture = ref textures[i];
ref var refs = ref _textureRefs[i];
if (i > 0 && _textureRefs[i - 1].View == refs.View && _textureRefs[i - 1].Sampler == refs.Sampler)
{
texture = textures[i - 1];
continue;
}
texture.ImageLayout = ImageLayout.General;
texture.ImageView = refs.View?.Get(cbs).Value ?? default;
texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default;
if (texture.ImageView.Handle == 0)
{
texture.ImageView = dummyTexture.GetImageView().Get(cbs).Value;
}
if (texture.Sampler.Handle == 0)
{
texture.Sampler = dummySampler.GetSampler().Get(cbs).Value;
}
}
return textures;
}
public ReadOnlySpan<BufferView> GetBufferViews(CommandBufferScoped cbs)
{
Span<BufferView> bufferTextures = _bufferTextures;
for (int i = 0; i < bufferTextures.Length; i++)
{
bufferTextures[i] = _bufferTextureRefs[i]?.GetBufferView(cbs, false) ?? default;
}
return bufferTextures;
}
}
}

View file

@ -48,7 +48,6 @@ namespace Ryujinx.Graphics.Vulkan
internal MemoryAllocator MemoryAllocator { get; private set; } internal MemoryAllocator MemoryAllocator { get; private set; }
internal HostMemoryAllocator HostMemoryAllocator { get; private set; } internal HostMemoryAllocator HostMemoryAllocator { get; private set; }
internal CommandBufferPool CommandBufferPool { get; private set; } internal CommandBufferPool CommandBufferPool { get; private set; }
internal DescriptorSetManager DescriptorSetManager { get; private set; }
internal PipelineLayoutCache PipelineLayoutCache { get; private set; } internal PipelineLayoutCache PipelineLayoutCache { get; private set; }
internal BackgroundResources BackgroundResources { get; private set; } internal BackgroundResources BackgroundResources { get; private set; }
internal Action<Action> InterruptAction { get; private set; } internal Action<Action> InterruptAction { get; private set; }
@ -414,8 +413,6 @@ namespace Ryujinx.Graphics.Vulkan
CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex); CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex);
DescriptorSetManager = new DescriptorSetManager(_device, PipelineBase.DescriptorSetLayouts);
PipelineLayoutCache = new PipelineLayoutCache(); PipelineLayoutCache = new PipelineLayoutCache();
BackgroundResources = new BackgroundResources(this, _device); BackgroundResources = new BackgroundResources(this, _device);
@ -507,6 +504,11 @@ namespace Ryujinx.Graphics.Vulkan
return BufferManager.CreateSparse(this, storageBuffers); return BufferManager.CreateSparse(this, storageBuffers);
} }
public IImageArray CreateImageArray(int size, bool isBuffer)
{
return new ImageArray(this, size, isBuffer);
}
public IProgram CreateProgram(ShaderSource[] sources, ShaderInfo info) public IProgram CreateProgram(ShaderSource[] sources, ShaderInfo info)
{ {
bool isCompute = sources.Length == 1 && sources[0].Stage == ShaderStage.Compute; bool isCompute = sources.Length == 1 && sources[0].Stage == ShaderStage.Compute;
@ -539,6 +541,11 @@ namespace Ryujinx.Graphics.Vulkan
return CreateTextureView(info); return CreateTextureView(info);
} }
public ITextureArray CreateTextureArray(int size, bool isBuffer)
{
return new TextureArray(this, size, isBuffer);
}
internal TextureView CreateTextureView(TextureCreateInfo info) internal TextureView CreateTextureView(TextureCreateInfo info)
{ {
// This should be disposed when all views are destroyed. // This should be disposed when all views are destroyed.
@ -925,7 +932,6 @@ namespace Ryujinx.Graphics.Vulkan
HelperShader.Dispose(); HelperShader.Dispose();
_pipeline.Dispose(); _pipeline.Dispose();
BufferManager.Dispose(); BufferManager.Dispose();
DescriptorSetManager.Dispose();
PipelineLayoutCache.Dispose(); PipelineLayoutCache.Dispose();
Barriers.Dispose(); Barriers.Dispose();

View file

@ -11,17 +11,67 @@ namespace Ryujinx.ShaderTools
{ {
private class GpuAccessor : IGpuAccessor private class GpuAccessor : IGpuAccessor
{ {
private const int DefaultArrayLength = 32;
private readonly byte[] _data; private readonly byte[] _data;
private int _texturesCount;
private int _imagesCount;
public GpuAccessor(byte[] data) public GpuAccessor(byte[] data)
{ {
_data = data; _data = data;
_texturesCount = 0;
_imagesCount = 0;
}
public int CreateConstantBufferBinding(int index)
{
return index + 1;
}
public int CreateImageBinding(int count, bool isBuffer)
{
int binding = _imagesCount;
_imagesCount += count;
return binding;
}
public int CreateStorageBufferBinding(int index)
{
return index;
}
public int CreateTextureBinding(int count, bool isBuffer)
{
int binding = _texturesCount;
_texturesCount += count;
return binding;
} }
public ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize) public ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize)
{ {
return MemoryMarshal.Cast<byte, ulong>(new ReadOnlySpan<byte>(_data)[(int)address..]); return MemoryMarshal.Cast<byte, ulong>(new ReadOnlySpan<byte>(_data)[(int)address..]);
} }
public int QuerySamplerArrayLengthFromPool()
{
return DefaultArrayLength;
}
public int QueryTextureArrayLengthFromBuffer(int slot)
{
return DefaultArrayLength;
}
public int QueryTextureArrayLengthFromPool()
{
return DefaultArrayLength;
}
} }
private class Options private class Options