diff --git a/Ryujinx.Ava/entryStorage b/Ryujinx.Ava/entryStorage
new file mode 100644
index 000000000..899e3c0bc
Binary files /dev/null and b/Ryujinx.Ava/entryStorage differ
diff --git a/Ryujinx.Ava/nodeStorage b/Ryujinx.Ava/nodeStorage
new file mode 100644
index 000000000..98a03f761
Binary files /dev/null and b/Ryujinx.Ava/nodeStorage differ
diff --git a/Ryujinx.Common/Configuration/GraphicsBackend.cs b/Ryujinx.Common/Configuration/GraphicsBackend.cs
new file mode 100644
index 000000000..26e4a28a9
--- /dev/null
+++ b/Ryujinx.Common/Configuration/GraphicsBackend.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Common.Configuration
+{
+ public enum GraphicsBackend
+ {
+ Vulkan,
+ OpenGl
+ }
+}
diff --git a/Ryujinx.Graphics.GAL/Format.cs b/Ryujinx.Graphics.GAL/Format.cs
index 50cc6d40c..db944844f 100644
--- a/Ryujinx.Graphics.GAL/Format.cs
+++ b/Ryujinx.Graphics.GAL/Format.cs
@@ -165,6 +165,120 @@ namespace Ryujinx.Graphics.GAL
public static class FormatExtensions
{
+ ///
+ /// Checks if the texture format is valid to use as image format.
+ ///
+ /// Texture format
+ /// True if the texture can be used as image, false otherwise
+ public static bool IsImageCompatible(this Format format)
+ {
+ switch (format)
+ {
+ case Format.R8Unorm:
+ case Format.R8Snorm:
+ case Format.R8Uint:
+ case Format.R8Sint:
+ case Format.R16Float:
+ case Format.R16Unorm:
+ case Format.R16Snorm:
+ case Format.R16Uint:
+ case Format.R16Sint:
+ case Format.R32Float:
+ case Format.R32Uint:
+ case Format.R32Sint:
+ case Format.R8G8Unorm:
+ case Format.R8G8Snorm:
+ case Format.R8G8Uint:
+ case Format.R8G8Sint:
+ case Format.R16G16Float:
+ case Format.R16G16Unorm:
+ case Format.R16G16Snorm:
+ case Format.R16G16Uint:
+ case Format.R16G16Sint:
+ case Format.R32G32Float:
+ case Format.R32G32Uint:
+ case Format.R32G32Sint:
+ case Format.R8G8B8A8Unorm:
+ case Format.R8G8B8A8Snorm:
+ case Format.R8G8B8A8Uint:
+ case Format.R8G8B8A8Sint:
+ case Format.R16G16B16A16Float:
+ case Format.R16G16B16A16Unorm:
+ case Format.R16G16B16A16Snorm:
+ case Format.R16G16B16A16Uint:
+ case Format.R16G16B16A16Sint:
+ case Format.R32G32B32A32Float:
+ case Format.R32G32B32A32Uint:
+ case Format.R32G32B32A32Sint:
+ case Format.R10G10B10A2Unorm:
+ case Format.R10G10B10A2Uint:
+ case Format.R11G11B10Float:
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Checks if the texture format is valid to use as render target color format.
+ ///
+ /// Texture format
+ /// True if the texture can be used as render target, false otherwise
+ public static bool IsRtColorCompatible(this Format format)
+ {
+ switch (format)
+ {
+ case Format.R32G32B32A32Float:
+ case Format.R32G32B32A32Sint:
+ case Format.R32G32B32A32Uint:
+ case Format.R16G16B16A16Unorm:
+ case Format.R16G16B16A16Snorm:
+ case Format.R16G16B16A16Sint:
+ case Format.R16G16B16A16Uint:
+ case Format.R16G16B16A16Float:
+ case Format.R32G32Float:
+ case Format.R32G32Sint:
+ case Format.R32G32Uint:
+ case Format.B8G8R8A8Unorm:
+ case Format.B8G8R8A8Srgb:
+ case Format.R10G10B10A2Unorm:
+ case Format.R10G10B10A2Uint:
+ case Format.R8G8B8A8Unorm:
+ case Format.R8G8B8A8Srgb:
+ case Format.R8G8B8A8Snorm:
+ case Format.R8G8B8A8Sint:
+ case Format.R8G8B8A8Uint:
+ case Format.R16G16Unorm:
+ case Format.R16G16Snorm:
+ case Format.R16G16Sint:
+ case Format.R16G16Uint:
+ case Format.R16G16Float:
+ case Format.R11G11B10Float:
+ case Format.R32Sint:
+ case Format.R32Uint:
+ case Format.R32Float:
+ case Format.B5G6R5Unorm:
+ case Format.B5G5R5A1Unorm:
+ case Format.R8G8Unorm:
+ case Format.R8G8Snorm:
+ case Format.R8G8Sint:
+ case Format.R8G8Uint:
+ case Format.R16Unorm:
+ case Format.R16Snorm:
+ case Format.R16Sint:
+ case Format.R16Uint:
+ case Format.R16Float:
+ case Format.R8Unorm:
+ case Format.R8Snorm:
+ case Format.R8Sint:
+ case Format.R8Uint:
+ case Format.B5G5R5X1Unorm:
+ return true;
+ }
+
+ return false;
+ }
+
///
/// Checks if the texture format is an ASTC format.
///
diff --git a/Ryujinx.Graphics.GAL/HardwareInfo.cs b/Ryujinx.Graphics.GAL/HardwareInfo.cs
new file mode 100644
index 000000000..0c247074a
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/HardwareInfo.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Ryujinx.Graphics.GAL
+{
+ public struct HardwareInfo
+ {
+ public string GpuVendor { get; }
+ public string GpuModel { get; }
+
+ public HardwareInfo(string gpuVendor, string gpuModel)
+ {
+ GpuVendor = gpuVendor;
+ GpuModel = gpuModel;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.GAL/IPipeline.cs b/Ryujinx.Graphics.GAL/IPipeline.cs
index 83afcaa3a..c88cc7017 100644
--- a/Ryujinx.Graphics.GAL/IPipeline.cs
+++ b/Ryujinx.Graphics.GAL/IPipeline.cs
@@ -77,15 +77,13 @@ namespace Ryujinx.Graphics.GAL
void SetRenderTargetColorMasks(ReadOnlySpan componentMask);
void SetRenderTargets(ITexture[] colors, ITexture depthStencil);
- void SetSampler(int binding, ISampler sampler);
-
- void SetScissor(int index, bool enable, int x, int y, int width, int height);
+ void SetScissors(ReadOnlySpan> regions);
void SetStencilTest(StencilTestDescriptor stencilTest);
void SetStorageBuffers(int first, ReadOnlySpan buffers);
- void SetTexture(int binding, ITexture texture);
+ void SetTextureAndSampler(int binding, ITexture texture, ISampler sampler);
void SetTransformFeedbackBuffers(ReadOnlySpan buffers);
void SetUniformBuffers(int first, ReadOnlySpan buffers);
diff --git a/Ryujinx.Graphics.GAL/IRenderer.cs b/Ryujinx.Graphics.GAL/IRenderer.cs
index b051e9dc8..8e48738db 100644
--- a/Ryujinx.Graphics.GAL/IRenderer.cs
+++ b/Ryujinx.Graphics.GAL/IRenderer.cs
@@ -30,6 +30,7 @@ namespace Ryujinx.Graphics.GAL
ReadOnlySpan GetBufferData(BufferHandle buffer, int offset, int size);
Capabilities GetCapabilities();
+ HardwareInfo GetHardwareInfo();
IProgram LoadProgramBinary(byte[] programBinary, bool hasFragmentShader, ShaderInfo info);
diff --git a/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs b/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs
index 442a90459..08c766df8 100644
--- a/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs
+++ b/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs
@@ -199,14 +199,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
SetRenderTargetScaleCommand.Run(ref GetCommand(memory), threaded, renderer);
_lookup[(int)CommandType.SetRenderTargets] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) =>
SetRenderTargetsCommand.Run(ref GetCommand(memory), threaded, renderer);
- _lookup[(int)CommandType.SetSampler] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) =>
- SetSamplerCommand.Run(ref GetCommand(memory), threaded, renderer);
_lookup[(int)CommandType.SetScissor] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) =>
- SetScissorCommand.Run(ref GetCommand(memory), threaded, renderer);
+ SetScissorsCommand.Run(ref GetCommand(memory), threaded, renderer);
_lookup[(int)CommandType.SetStencilTest] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) =>
SetStencilTestCommand.Run(ref GetCommand(memory), threaded, renderer);
- _lookup[(int)CommandType.SetTexture] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) =>
- SetTextureCommand.Run(ref GetCommand(memory), threaded, renderer);
+ _lookup[(int)CommandType.SetTextureAndSampler] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) =>
+ SetTextureAndSamplerCommand.Run(ref GetCommand(memory), threaded, renderer);
_lookup[(int)CommandType.SetUserClipDistance] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) =>
SetUserClipDistanceCommand.Run(ref GetCommand(memory), threaded, renderer);
_lookup[(int)CommandType.SetVertexAttribs] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) =>
diff --git a/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs b/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs
index 5c42abd12..69cda90c6 100644
--- a/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs
+++ b/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs
@@ -81,10 +81,9 @@
SetRenderTargetColorMasks,
SetRenderTargetScale,
SetRenderTargets,
- SetSampler,
SetScissor,
SetStencilTest,
- SetTexture,
+ SetTextureAndSampler,
SetUserClipDistance,
SetVertexAttribs,
SetVertexBuffers,
diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetSamplerCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetSamplerCommand.cs
deleted file mode 100644
index f3be24dbf..000000000
--- a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetSamplerCommand.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using Ryujinx.Graphics.GAL.Multithreading.Model;
-using Ryujinx.Graphics.GAL.Multithreading.Resources;
-
-namespace Ryujinx.Graphics.GAL.Multithreading.Commands
-{
- struct SetSamplerCommand : IGALCommand
- {
- public CommandType CommandType => CommandType.SetSampler;
- private int _index;
- private TableRef _sampler;
-
- public void Set(int index, TableRef sampler)
- {
- _index = index;
- _sampler = sampler;
- }
-
- public static void Run(ref SetSamplerCommand command, ThreadedRenderer threaded, IRenderer renderer)
- {
- renderer.Pipeline.SetSampler(command._index, command._sampler.GetAs(threaded)?.Base);
- }
- }
-}
diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetScissorCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetScissorCommand.cs
deleted file mode 100644
index 6c95d0969..000000000
--- a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetScissorCommand.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-namespace Ryujinx.Graphics.GAL.Multithreading.Commands
-{
- struct SetScissorCommand : IGALCommand
- {
- public CommandType CommandType => CommandType.SetScissor;
- private int _index;
- private bool _enable;
- private int _x;
- private int _y;
- private int _width;
- private int _height;
-
- public void Set(int index, bool enable, int x, int y, int width, int height)
- {
- _index = index;
- _enable = enable;
- _x = x;
- _y = y;
- _width = width;
- _height = height;
- }
-
- public static void Run(ref SetScissorCommand command, ThreadedRenderer threaded, IRenderer renderer)
- {
- renderer.Pipeline.SetScissor(command._index, command._enable, command._x, command._y, command._width, command._height);
- }
- }
-}
diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetScissorsCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetScissorsCommand.cs
new file mode 100644
index 000000000..6966df6d5
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetScissorsCommand.cs
@@ -0,0 +1,22 @@
+using Ryujinx.Graphics.GAL.Multithreading.Model;
+
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetScissorsCommand : IGALCommand
+ {
+ public CommandType CommandType => CommandType.SetScissor;
+ private SpanRef> _scissors;
+
+ public void Set(SpanRef> scissors)
+ {
+ _scissors = scissors;
+ }
+
+ public static void Run(ref SetScissorsCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.Pipeline.SetScissors(command._scissors.Get(threaded));
+
+ command._scissors.Dispose(threaded);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureAndSamplerCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureAndSamplerCommand.cs
new file mode 100644
index 000000000..27700278b
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureAndSamplerCommand.cs
@@ -0,0 +1,25 @@
+using Ryujinx.Graphics.GAL.Multithreading.Model;
+using Ryujinx.Graphics.GAL.Multithreading.Resources;
+
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetTextureAndSamplerCommand : IGALCommand
+ {
+ public CommandType CommandType => CommandType.SetTextureAndSampler;
+ private int _binding;
+ private TableRef _texture;
+ private TableRef _sampler;
+
+ public void Set(int binding, TableRef texture, TableRef sampler)
+ {
+ _binding = binding;
+ _texture = texture;
+ _sampler = sampler;
+ }
+
+ public static void Run(ref SetTextureAndSamplerCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.Pipeline.SetTextureAndSampler(command._binding, command._texture.GetAs(threaded)?.Base, command._sampler.GetAs(threaded)?.Base);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureCommand.cs
deleted file mode 100644
index e86f512be..000000000
--- a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureCommand.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using Ryujinx.Graphics.GAL.Multithreading.Model;
-using Ryujinx.Graphics.GAL.Multithreading.Resources;
-
-namespace Ryujinx.Graphics.GAL.Multithreading.Commands
-{
- struct SetTextureCommand : IGALCommand
- {
- public CommandType CommandType => CommandType.SetTexture;
- private int _binding;
- private TableRef _texture;
-
- public void Set(int binding, TableRef texture)
- {
- _binding = binding;
- _texture = texture;
- }
-
- public static void Run(ref SetTextureCommand command, ThreadedRenderer threaded, IRenderer renderer)
- {
- renderer.Pipeline.SetTexture(command._binding, command._texture.GetAs(threaded)?.Base);
- }
- }
-}
diff --git a/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs b/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs
index 2a1f474a9..a9f7c8e9d 100644
--- a/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs
+++ b/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs
@@ -244,15 +244,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand();
}
- public void SetSampler(int binding, ISampler sampler)
+ public void SetScissors(ReadOnlySpan> scissors)
{
- _renderer.New().Set(binding, Ref(sampler));
- _renderer.QueueCommand();
- }
-
- public void SetScissor(int index, bool enable, int x, int y, int width, int height)
- {
- _renderer.New().Set(index, enable, x, y, width, height);
+ _renderer.New().Set(_renderer.CopySpan(scissors));
_renderer.QueueCommand();
}
@@ -268,9 +262,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand();
}
- public void SetTexture(int binding, ITexture texture)
+ public void SetTextureAndSampler(int binding, ITexture texture, ISampler sampler)
{
- _renderer.New().Set(binding, Ref(texture));
+ _renderer.New().Set(binding, Ref(texture), Ref(sampler));
_renderer.QueueCommand();
}
diff --git a/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs b/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs
index 63b668bac..6a123108a 100644
--- a/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs
+++ b/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs
@@ -337,6 +337,11 @@ namespace Ryujinx.Graphics.GAL.Multithreading
return box.Result;
}
+ public HardwareInfo GetHardwareInfo()
+ {
+ return _baseRenderer.GetHardwareInfo();
+ }
+
///
/// Initialize the base renderer. Must be called on the render thread.
///
diff --git a/Ryujinx.Graphics.GAL/Rectangle.cs b/Ryujinx.Graphics.GAL/Rectangle.cs
new file mode 100644
index 000000000..375472da3
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/Rectangle.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public struct Rectangle where T : unmanaged
+ {
+ public T X { get; }
+ public T Y { get; }
+ public T Width { get; }
+ public T Height { get; }
+
+ public Rectangle(T x, T y, T width, T height)
+ {
+ X = x;
+ Y = y;
+ Width = width;
+ Height = height;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.GAL/ShaderBindings.cs b/Ryujinx.Graphics.GAL/ShaderBindings.cs
new file mode 100644
index 000000000..dc6099fe5
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/ShaderBindings.cs
@@ -0,0 +1,30 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.GAL
+{
+ public struct ShaderBindings
+ {
+ public IReadOnlyCollection UniformBufferBindings { get; }
+ public IReadOnlyCollection StorageBufferBindings { get; }
+ public IReadOnlyCollection TextureBindings { get; }
+ public IReadOnlyCollection ImageBindings { get; }
+ public IReadOnlyCollection BufferTextureBindings { get; }
+ public IReadOnlyCollection BufferImageBindings { get; }
+
+ public ShaderBindings(
+ IReadOnlyCollection uniformBufferBindings,
+ IReadOnlyCollection storageBufferBindings,
+ IReadOnlyCollection textureBindings,
+ IReadOnlyCollection imageBindings,
+ IReadOnlyCollection bufferTextureBindings,
+ IReadOnlyCollection bufferImageBindings)
+ {
+ UniformBufferBindings = uniformBufferBindings;
+ StorageBufferBindings = storageBufferBindings;
+ TextureBindings = textureBindings;
+ ImageBindings = imageBindings;
+ BufferTextureBindings = bufferTextureBindings;
+ BufferImageBindings = bufferImageBindings;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.GAL/ShaderSource.cs b/Ryujinx.Graphics.GAL/ShaderSource.cs
index 13b92f20a..c68ba80d6 100644
--- a/Ryujinx.Graphics.GAL/ShaderSource.cs
+++ b/Ryujinx.Graphics.GAL/ShaderSource.cs
@@ -7,22 +7,24 @@ namespace Ryujinx.Graphics.GAL
{
public string Code { get; }
public byte[] BinaryCode { get; }
+ public ShaderBindings Bindings { get; }
public ShaderStage Stage { get; }
public TargetLanguage Language { get; }
- public ShaderSource(string code, byte[] binaryCode, ShaderStage stage, TargetLanguage language)
+ public ShaderSource(string code, byte[] binaryCode, ShaderBindings bindings, ShaderStage stage, TargetLanguage language)
{
Code = code;
BinaryCode = binaryCode;
+ Bindings = bindings;
Stage = stage;
Language = language;
}
- public ShaderSource(string code, ShaderStage stage, TargetLanguage language) : this(code, null, stage, language)
+ public ShaderSource(string code, ShaderBindings bindings, ShaderStage stage, TargetLanguage language) : this(code, null, bindings, stage, language)
{
}
- public ShaderSource(byte[] binaryCode, ShaderStage stage, TargetLanguage language) : this(null, binaryCode, stage, language)
+ public ShaderSource(byte[] binaryCode, ShaderBindings bindings, ShaderStage stage, TargetLanguage language) : this(null, binaryCode, bindings, stage, language)
{
}
}
diff --git a/Ryujinx.Graphics.GAL/Viewport.cs b/Ryujinx.Graphics.GAL/Viewport.cs
index d9d6e20a4..58135db2c 100644
--- a/Ryujinx.Graphics.GAL/Viewport.cs
+++ b/Ryujinx.Graphics.GAL/Viewport.cs
@@ -2,7 +2,7 @@ namespace Ryujinx.Graphics.GAL
{
public struct Viewport
{
- public RectangleF Region { get; }
+ public Rectangle Region { get; }
public ViewportSwizzle SwizzleX { get; }
public ViewportSwizzle SwizzleY { get; }
@@ -13,13 +13,13 @@ namespace Ryujinx.Graphics.GAL
public float DepthFar { get; }
public Viewport(
- RectangleF region,
- ViewportSwizzle swizzleX,
- ViewportSwizzle swizzleY,
- ViewportSwizzle swizzleZ,
- ViewportSwizzle swizzleW,
- float depthNear,
- float depthFar)
+ Rectangle region,
+ ViewportSwizzle swizzleX,
+ ViewportSwizzle swizzleY,
+ ViewportSwizzle swizzleZ,
+ ViewportSwizzle swizzleW,
+ float depthNear,
+ float depthFar)
{
Region = region;
SwizzleX = swizzleX;
diff --git a/Ryujinx.Graphics.Gpu/Constants.cs b/Ryujinx.Graphics.Gpu/Constants.cs
index 026d12a92..455029f54 100644
--- a/Ryujinx.Graphics.Gpu/Constants.cs
+++ b/Ryujinx.Graphics.Gpu/Constants.cs
@@ -40,6 +40,22 @@ namespace Ryujinx.Graphics.Gpu
///
public const int TotalTransformFeedbackBuffers = 4;
+ ///
+ /// Maximum number of textures on a single shader stage.
+ ///
+ ///
+ /// The maximum number of textures is API limited, the hardware supports a unlimited amount.
+ ///
+ public const int TotalTextures = 32;
+
+ ///
+ /// Maximum number of images on a single shader stage.
+ ///
+ ///
+ /// The maximum number of images is API limited, the hardware supports a unlimited amount.
+ ///
+ public const int TotalImages = 8;
+
///
/// Maximum number of render target color buffers.
///
diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs
index f90baf99e..6dc5dca5b 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs
@@ -559,7 +559,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
scissorH = (int)MathF.Ceiling(scissorH * scale);
}
- _context.Renderer.Pipeline.SetScissor(0, true, scissorX, scissorY, scissorW, scissorH);
+ Span> scissors = stackalloc Rectangle[1];
+ scissors[0] = new Rectangle(scissorX, scissorY, scissorW, scissorH);
+
+ _context.Renderer.Pipeline.SetScissors(scissors);
}
if (clipMismatch)
diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
index c64c760ae..dbb5327ed 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
@@ -493,11 +493,21 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
///
public void UpdateScissorState()
{
+ const int MinX = 0;
+ const int MinY = 0;
+ const int MaxW = 0xffff;
+ const int MaxH = 0xffff;
+
+ Span> regions = stackalloc Rectangle[Constants.TotalViewports];
+
for (int index = 0; index < Constants.TotalViewports; index++)
{
ScissorState scissor = _state.State.ScissorState[index];
- bool enable = scissor.Enable && (scissor.X1 != 0 || scissor.Y1 != 0 || scissor.X2 != 0xffff || scissor.Y2 != 0xffff);
+ bool enable = scissor.Enable && (scissor.X1 != MinX ||
+ scissor.Y1 != MinY ||
+ scissor.X2 != MaxW ||
+ scissor.Y2 != MaxH);
if (enable)
{
@@ -527,13 +537,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
height = (int)MathF.Ceiling(height * scale);
}
- _context.Renderer.Pipeline.SetScissor(index, true, x, y, width, height);
+ regions[index] = new Rectangle(x, y, width, height);
}
else
{
- _context.Renderer.Pipeline.SetScissor(index, false, 0, 0, 0, 0);
+ regions[index] = new Rectangle(MinX, MinY, MaxW, MaxH);
}
}
+
+ _context.Renderer.Pipeline.SetScissors(regions);
}
///
@@ -592,7 +604,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
ref var scissor = ref _state.State.ScreenScissorState;
float rScale = _channel.TextureManager.RenderTargetScale;
- var scissorRect = new RectangleF(0, 0, (scissor.X + scissor.Width) * rScale, (scissor.Y + scissor.Height) * rScale);
+ var scissorRect = new Rectangle(0, 0, (scissor.X + scissor.Width) * rScale, (scissor.Y + scissor.Height) * rScale);
viewports[index] = new Viewport(scissorRect, ViewportSwizzle.PositiveX, ViewportSwizzle.PositiveY, ViewportSwizzle.PositiveZ, ViewportSwizzle.PositiveW, 0, 1);
continue;
@@ -629,7 +641,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
height *= scale;
}
- RectangleF region = new RectangleF(x, y, width, height);
+ Rectangle region = new Rectangle(x, y, width, height);
ViewportSwizzle swizzleX = transform.UnpackSwizzleX();
ViewportSwizzle swizzleY = transform.UnpackSwizzleY();
@@ -649,6 +661,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
viewports[index] = new Viewport(region, swizzleX, swizzleY, swizzleZ, swizzleW, depthNear, depthFar);
}
+ _context.Renderer.Pipeline.SetDepthMode(GetDepthMode());
_context.Renderer.Pipeline.SetViewports(0, viewports, disableTransform);
}
@@ -1231,7 +1244,29 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
_state.State.EarlyZForce,
_drawState.Topology,
_state.State.TessMode,
- _state.State.ViewportTransformEnable == 0);
+ _state.State.ViewportTransformEnable == 0,
+ GetDepthMode() == DepthMode.MinusOneToOne,
+ _state.State.VertexProgramPointSize,
+ _state.State.PointSize);
+ }
+
+ private DepthMode GetDepthMode()
+ {
+ ref var transform = ref _state.State.ViewportTransform[0];
+ ref var extents = ref _state.State.ViewportExtents[0];
+
+ // Try to guess the depth mode being used on the high level API
+ // based on current transform.
+ // It is setup like so by said APIs:
+ // If depth mode is ZeroToOne:
+ // TranslateZ = Near
+ // ScaleZ = Far - Near
+ // If depth mode is MinusOneToOne:
+ // TranslateZ = (Near + Far) / 2
+ // ScaleZ = (Far - Near) / 2
+ // DepthNear/Far are sorted such as that Near is always less than Far.
+ return extents.DepthNear != transform.TranslateZ &&
+ extents.DepthFar != transform.TranslateZ ? DepthMode.MinusOneToOne : DepthMode.ZeroToOne;
}
///
diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs
index 81a228315..172e8c370 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs
@@ -311,6 +311,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
{
return Attribute & 0x3fe00000;
}
+
+ ///
+ /// Unpacks the Maxwell attribute component type.
+ ///
+ /// Attribute component type
+ public uint UnpackType()
+ {
+ return (Attribute >> 27) & 7;
+ }
}
///
diff --git a/Ryujinx.Graphics.Gpu/GraphicsConfig.cs b/Ryujinx.Graphics.Gpu/GraphicsConfig.cs
index 493dbd7bd..9f9de50f6 100644
--- a/Ryujinx.Graphics.Gpu/GraphicsConfig.cs
+++ b/Ryujinx.Graphics.Gpu/GraphicsConfig.cs
@@ -56,5 +56,10 @@ namespace Ryujinx.Graphics.Gpu
/// Enables or disables the shader cache.
///
public static bool EnableShaderCache;
+
+ ///
+ /// Enables or disables shader SPIR-V compilation.
+ ///
+ public static bool EnableSpirvCompilation;
}
}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
index 91cadde3e..6c932442b 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
@@ -435,6 +435,25 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
+ ///
+ /// Counts the total number of texture bindings used by all shader stages.
+ ///
+ /// The total amount of textures used
+ private int GetTextureBindingsCount()
+ {
+ int count = 0;
+
+ for (int i = 0; i < _textureBindings.Length; i++)
+ {
+ if (_textureBindings[i] != null)
+ {
+ count += _textureBindings[i].Length;
+ }
+ }
+
+ return count;
+ }
+
///
/// Ensures that the texture bindings are visible to the host GPU.
/// Note: this actually performs the binding using the host graphics API.
@@ -511,7 +530,7 @@ namespace Ryujinx.Graphics.Gpu.Image
state.ScaleIndex = index;
state.UsageFlags = usageFlags;
- _context.Renderer.Pipeline.SetTexture(bindingInfo.Binding, hostTextureRebind);
+ _context.Renderer.Pipeline.SetTextureAndSampler(bindingInfo.Binding, hostTextureRebind, state.Sampler);
}
continue;
@@ -524,7 +543,10 @@ namespace Ryujinx.Graphics.Gpu.Image
specStateMatches &= specState.MatchesTexture(stage, index, descriptor);
+ Sampler sampler = _samplerPool?.Get(samplerId);
+
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
+ ISampler hostSampler = sampler?.GetHostSampler(texture);
if (hostTexture != null && texture.Target == Target.TextureBuffer)
{
@@ -535,7 +557,7 @@ namespace Ryujinx.Graphics.Gpu.Image
}
else
{
- if (state.Texture != hostTexture)
+ if (state.Texture != hostTexture || state.Sampler != hostSampler)
{
if (UpdateScale(texture, usageFlags, index, stage))
{
@@ -546,22 +568,13 @@ namespace Ryujinx.Graphics.Gpu.Image
state.ScaleIndex = index;
state.UsageFlags = usageFlags;
- _context.Renderer.Pipeline.SetTexture(bindingInfo.Binding, hostTexture);
- }
-
- Sampler sampler = samplerPool?.Get(samplerId);
- state.CachedSampler = sampler;
-
- ISampler hostSampler = sampler?.GetHostSampler(texture);
-
- if (state.Sampler != hostSampler)
- {
state.Sampler = hostSampler;
- _context.Renderer.Pipeline.SetSampler(bindingInfo.Binding, hostSampler);
+ _context.Renderer.Pipeline.SetTextureAndSampler(bindingInfo.Binding, hostTexture, hostSampler);
}
state.CachedTexture = texture;
+ state.CachedSampler = sampler;
state.InvalidatedSequence = texture?.InvalidatedSequence ?? 0;
}
}
diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
index 9f5f39a92..7615d60d6 100644
--- a/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
+++ b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
@@ -435,7 +435,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
else
{
- _context.Renderer.Pipeline.SetTexture(binding.BindingInfo.Binding, binding.Texture);
+ _context.Renderer.Pipeline.SetTextureAndSampler(binding.BindingInfo.Binding, binding.Texture, null);
}
}
diff --git a/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs b/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
index 0ac6160d9..9d246b5cb 100644
--- a/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
+++ b/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
@@ -252,6 +252,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
Write(va, MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1)));
}
+ public void WriteUntracked(ulong va, T value) where T : unmanaged
+ {
+ WriteUntracked(va, MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1)));
+ }
+
///
/// Writes data to GPU mapped memory.
///
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Migration.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Migration.cs
index 4de6eff91..73c35ae50 100644
--- a/Ryujinx.Graphics.Gpu/Shader/Cache/Migration.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/Cache/Migration.cs
@@ -167,7 +167,10 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
accessorHeader.StateFlags.HasFlag(GuestGpuStateFlags.EarlyZForce),
topology,
tessMode,
- false);
+ false,
+ false,
+ false,
+ 1f);
TransformFeedbackDescriptor[] tfdNew = null;
diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs
index bc63f714d..1ab9e8655 100644
--- a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs
@@ -34,7 +34,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
ShaderSpecializationState oldSpecState,
ShaderSpecializationState newSpecState,
ResourceCounts counts,
- int stageIndex) : base(context)
+ int stageIndex) : base(context, counts, stageIndex)
{
_data = data;
_cb1Data = cb1Data;
@@ -67,30 +67,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return MemoryMarshal.Cast(_data.Span.Slice((int)address));
}
- ///
- public int QueryBindingConstantBuffer(int index)
- {
- return _resourceCounts.UniformBuffersCount++;
- }
-
- ///
- public int QueryBindingStorageBuffer(int index)
- {
- return _resourceCounts.StorageBuffersCount++;
- }
-
- ///
- public int QueryBindingTexture(int index)
- {
- return _resourceCounts.TexturesCount++;
- }
-
- ///
- public int QueryBindingImage(int index)
- {
- return _resourceCounts.ImagesCount++;
- }
-
///
public int QueryComputeLocalSizeX() => _oldSpecState.ComputeState.LocalSizeX;
diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs
index af7579d5d..803b06766 100644
--- a/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs
@@ -567,6 +567,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1];
TranslatorContext nextStage = null;
+ TargetApi api = _context.Capabilities.Api;
+
for (int stageIndex = Constants.ShaderStages - 1; stageIndex >= 0; stageIndex--)
{
CachedShaderStage shader = shaders[stageIndex + 1];
@@ -577,7 +579,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
byte[] cb1Data = shader.Cb1Data;
DiskCacheGpuAccessor gpuAccessor = new DiskCacheGpuAccessor(_context, guestCode, cb1Data, specState, newSpecState, counts, stageIndex);
- TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, DefaultFlags, 0);
+ TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags, 0);
if (nextStage != null)
{
@@ -590,7 +592,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
byte[] cb1DataA = shaders[0].Cb1Data;
DiskCacheGpuAccessor gpuAccessorA = new DiskCacheGpuAccessor(_context, guestCodeA, cb1DataA, specState, newSpecState, counts, 0);
- translatorContexts[0] = DecodeGraphicsShader(gpuAccessorA, DefaultFlags | TranslationFlags.VertexA, 0);
+ translatorContexts[0] = DecodeGraphicsShader(gpuAccessorA, api, DefaultFlags | TranslationFlags.VertexA, 0);
}
translatorContexts[stageIndex + 1] = currentStage;
@@ -651,7 +653,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
ShaderSpecializationState newSpecState = new ShaderSpecializationState(specState.ComputeState);
DiskCacheGpuAccessor gpuAccessor = new DiskCacheGpuAccessor(_context, shader.Code, shader.Cb1Data, specState, newSpecState, counts, 0);
- TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, 0);
+ TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, 0);
ShaderProgram program = translatorContext.Translate();
diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
index 5cd966af7..4f8af41df 100644
--- a/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
@@ -13,6 +13,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
private readonly GpuChannel _channel;
private readonly GpuAccessorState _state;
+ private readonly AttributeType[] _attributeTypes;
private readonly int _stageIndex;
private readonly bool _compute;
@@ -22,11 +23,18 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// GPU context
/// GPU channel
/// Current GPU state
+ /// Type of the vertex attributes consumed by the shader
/// Graphics shader stage index (0 = Vertex, 4 = Fragment)
- public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state, int stageIndex) : base(context)
+ public GpuAccessor(
+ GpuContext context,
+ GpuChannel channel,
+ GpuAccessorState state,
+ AttributeType[] attributeTypes,
+ int stageIndex) : base(context, state.ResourceCounts, stageIndex)
{
_channel = channel;
_state = state;
+ _attributeTypes = attributeTypes;
_stageIndex = stageIndex;
}
@@ -36,7 +44,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// GPU context
/// GPU channel
/// Current GPU state
- public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state) : base(context)
+ public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state) : base(context, state.ResourceCounts, 0)
{
_channel = channel;
_state = state;
@@ -67,27 +75,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
///
- public int QueryBindingConstantBuffer(int index)
+ public AttributeType QueryAttributeType(int location)
{
- return _state.ResourceCounts.UniformBuffersCount++;
- }
+ if (_attributeTypes != null)
+ {
+ return _attributeTypes[location];
+ }
- ///
- public int QueryBindingStorageBuffer(int index)
- {
- return _state.ResourceCounts.StorageBuffersCount++;
- }
-
- ///
- public int QueryBindingTexture(int index)
- {
- return _state.ResourceCounts.TexturesCount++;
- }
-
- ///
- public int QueryBindingImage(int index)
- {
- return _state.ResourceCounts.ImagesCount++;
+ return AttributeType.Float;
}
///
@@ -123,6 +118,18 @@ namespace Ryujinx.Graphics.Gpu.Shader
return ConvertToInputTopology(_state.GraphicsState.Topology, _state.GraphicsState.TessellationMode);
}
+ ///
+ public bool QueryProgramPointSize()
+ {
+ return _state.GraphicsState.ProgramPointSizeEnable;
+ }
+
+ ///
+ public float QueryPointSize()
+ {
+ return _state.GraphicsState.PointSize;
+ }
+
///
public bool QueryTessCw()
{
@@ -192,6 +199,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
}
+ ///
+ public bool QueryTransformDepthMinusOneToOne()
+ {
+ return _state.GraphicsState.DepthMode;
+ }
+
///
public bool QueryTransformFeedbackEnabled()
{
diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs b/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs
index 5f9dd5880..3da678fe3 100644
--- a/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs
@@ -2,6 +2,7 @@ using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Threed;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Shader;
+using Ryujinx.Graphics.Shader.Translation;
namespace Ryujinx.Graphics.Gpu.Shader
{
@@ -11,14 +12,86 @@ namespace Ryujinx.Graphics.Gpu.Shader
class GpuAccessorBase
{
private readonly GpuContext _context;
+ private readonly ResourceCounts _resourceCounts;
+ private readonly int _stageIndex;
///
/// Creates a new GPU accessor.
///
/// GPU context
- public GpuAccessorBase(GpuContext context)
+ public GpuAccessorBase(GpuContext context, ResourceCounts resourceCounts, int stageIndex)
{
_context = context;
+ _resourceCounts = resourceCounts;
+ _stageIndex = stageIndex;
+ }
+
+ ///
+ public int QueryBindingConstantBuffer(int index)
+ {
+ if (_context.Capabilities.Api == TargetApi.Vulkan)
+ {
+ return 1 + GetStageIndex() * 18 + index;
+ }
+ else
+ {
+ return _resourceCounts.UniformBuffersCount++;
+ }
+ }
+
+ ///
+ public int QueryBindingStorageBuffer(int index)
+ {
+ if (_context.Capabilities.Api == TargetApi.Vulkan)
+ {
+ return GetStageIndex() * 16 + index;
+ }
+ else
+ {
+ return _resourceCounts.StorageBuffersCount++;
+ }
+ }
+
+ ///
+ public int QueryBindingTexture(int index)
+ {
+ if (_context.Capabilities.Api == TargetApi.Vulkan)
+ {
+ return GetStageIndex() * 32 + index;
+ }
+ else
+ {
+ return _resourceCounts.TexturesCount++;
+ }
+ }
+
+ ///
+ public int QueryBindingImage(int index)
+ {
+ if (_context.Capabilities.Api == TargetApi.Vulkan)
+ {
+ return GetStageIndex() * 8 + index;
+ }
+ else
+ {
+ return _resourceCounts.ImagesCount++;
+ }
+ }
+
+ private int GetStageIndex()
+ {
+ // This is just a simple remapping to ensure that most frequently used shader stages
+ // have the lowest binding numbers.
+ // This is useful because if we need to run on a system with a low limit on the bindings,
+ // then we can still get most games working as the most common shaders will have low binding numbers.
+ return _stageIndex switch
+ {
+ 4 => 1, // Fragment
+ 3 => 2, // Geometry
+ 1 => 3, // Tessellation control
+ 2 => 4, // Tessellation evaluation
+ _ => 0 // Vertex/Compute
+ };
}
///
diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs b/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs
index 92ec117f3..fba67851d 100644
--- a/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs
@@ -30,6 +30,21 @@ namespace Ryujinx.Graphics.Gpu.Shader
///
public readonly bool ViewportTransformDisable;
+ ///
+ /// Depth mode zero to one or minus one to one.
+ ///
+ public readonly bool DepthMode;
+
+ ///
+ /// Indicates if the point size is set on the shader or is fixed.
+ ///
+ public readonly bool ProgramPointSizeEnable;
+
+ ///
+ /// Point size if not set from shader.
+ ///
+ public readonly float PointSize;
+
///
/// Creates a new GPU graphics state.
///
@@ -37,12 +52,25 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// Primitive topology
/// Tessellation mode
/// Indicates whenever the viewport transform is disabled
- public GpuChannelGraphicsState(bool earlyZForce, PrimitiveTopology topology, TessMode tessellationMode, bool viewportTransformDisable)
+ /// Depth mode zero to one or minus one to one
+ /// Indicates if the point size is set on the shader or is fixed
+ /// Point size if not set from shader
+ public GpuChannelGraphicsState(
+ bool earlyZForce,
+ PrimitiveTopology topology,
+ TessMode tessellationMode,
+ bool viewportTransformDisable,
+ bool depthMode,
+ bool programPointSizeEnable,
+ float pointSize)
{
EarlyZForce = earlyZForce;
Topology = topology;
TessellationMode = tessellationMode;
ViewportTransformDisable = viewportTransformDisable;
+ DepthMode = depthMode;
+ ProgramPointSizeEnable = programPointSizeEnable;
+ PointSize = pointSize;
}
}
}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
index 0779bf2ce..9ffece6a2 100644
--- a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
@@ -8,6 +8,7 @@ using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Threading;
namespace Ryujinx.Graphics.Gpu.Shader
@@ -214,7 +215,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
GpuAccessorState gpuAccessorState = new GpuAccessorState(poolState, computeState, default, specState);
GpuAccessor gpuAccessor = new GpuAccessor(_context, channel, gpuAccessorState);
- TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, gpuVa);
+ TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, gpuVa);
TranslatedShader translatedShader = TranslateShader(_dumper, channel, translatorContext, cachedGuestCode);
@@ -260,6 +261,18 @@ namespace Ryujinx.Graphics.Gpu.Shader
return gpShaders;
}
+ AttributeType[] attributeTypes = new AttributeType[Constants.TotalVertexAttribs];
+
+ for (int location = 0; location < attributeTypes.Length; location++)
+ {
+ attributeTypes[location] = state.VertexAttribState[location].UnpackType() switch
+ {
+ 3 => AttributeType.Sint,
+ 4 => AttributeType.Uint,
+ _ => AttributeType.Float
+ };
+ }
+
TransformFeedbackDescriptor[] transformFeedbackDescriptors = GetTransformFeedbackDescriptors(ref state);
ShaderSpecializationState specState = new ShaderSpecializationState(graphicsState, transformFeedbackDescriptors);
@@ -270,14 +283,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1];
TranslatorContext nextStage = null;
+ TargetApi api = _context.Capabilities.Api;
+
for (int stageIndex = Constants.ShaderStages - 1; stageIndex >= 0; stageIndex--)
{
ulong gpuVa = addressesSpan[stageIndex + 1];
if (gpuVa != 0)
{
- GpuAccessor gpuAccessor = new GpuAccessor(_context, channel, gpuAccessorState, stageIndex);
- TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, DefaultFlags, gpuVa);
+ GpuAccessor gpuAccessor = new GpuAccessor(_context, channel, gpuAccessorState, attributeTypes, stageIndex);
+ TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags, gpuVa);
if (nextStage != null)
{
@@ -286,7 +301,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
if (stageIndex == 0 && addresses.VertexA != 0)
{
- translatorContexts[0] = DecodeGraphicsShader(gpuAccessor, DefaultFlags | TranslationFlags.VertexA, addresses.VertexA);
+ translatorContexts[0] = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags | TranslationFlags.VertexA, addresses.VertexA);
}
translatorContexts[stageIndex + 1] = currentStage;
@@ -355,7 +370,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// Shader source
public static ShaderSource CreateShaderSource(ShaderProgram program)
{
- return new ShaderSource(program.Code, program.BinaryCode, program.Info.Stage, program.Language);
+ return new ShaderSource(program.Code, program.BinaryCode, GetBindings(program.Info), program.Info.Stage, program.Language);
}
///
@@ -480,11 +495,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// Decode the binary Maxwell shader code to a translator context.
///
/// GPU state accessor
+ /// Graphics API that will be used with the shader
/// GPU virtual address of the binary shader code
/// The generated translator context
- public static TranslatorContext DecodeComputeShader(IGpuAccessor gpuAccessor, ulong gpuVa)
+ public static TranslatorContext DecodeComputeShader(IGpuAccessor gpuAccessor, TargetApi api, ulong gpuVa)
{
- var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, DefaultFlags | TranslationFlags.Compute);
+ var options = CreateTranslationOptions(api, DefaultFlags | TranslationFlags.Compute);
return Translator.CreateContext(gpuVa, gpuAccessor, options);
}
@@ -495,12 +511,13 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// This will combine the "Vertex A" and "Vertex B" shader stages, if specified, into one shader.
///
/// GPU state accessor
+ /// Graphics API that will be used with the shader
/// Flags that controls shader translation
/// GPU virtual address of the shader code
/// The generated translator context
- public static TranslatorContext DecodeGraphicsShader(IGpuAccessor gpuAccessor, TranslationFlags flags, ulong gpuVa)
+ public static TranslatorContext DecodeGraphicsShader(IGpuAccessor gpuAccessor, TargetApi api, TranslationFlags flags, ulong gpuVa)
{
- var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, flags);
+ var options = CreateTranslationOptions(api, flags);
return Translator.CreateContext(gpuVa, gpuAccessor, options);
}
@@ -595,6 +612,41 @@ namespace Ryujinx.Graphics.Gpu.Shader
};
}
+ private static ShaderBindings GetBindings(ShaderProgramInfo info)
+ {
+ static bool IsBuffer(TextureDescriptor descriptor)
+ {
+ return (descriptor.Type & SamplerType.Mask) == SamplerType.TextureBuffer;
+ }
+
+ static bool IsNotBuffer(TextureDescriptor descriptor)
+ {
+ return !IsBuffer(descriptor);
+ }
+
+ var uniformBufferBindings = info.CBuffers.Select(x => x.Binding).ToArray();
+ var storageBufferBindings = info.SBuffers.Select(x => x.Binding).ToArray();
+ var textureBindings = info.Textures.Where(IsNotBuffer).Select(x => x.Binding).ToArray();
+ var imageBindings = info.Images.Where(IsNotBuffer).Select(x => x.Binding).ToArray();
+ var bufferTextureBindings = info.Textures.Where(IsBuffer).Select(x => x.Binding).ToArray();
+ var bufferImageBindings = info.Images.Where(IsBuffer).Select(x => x.Binding).ToArray();
+
+ return new ShaderBindings(
+ uniformBufferBindings,
+ storageBufferBindings,
+ textureBindings,
+ imageBindings,
+ bufferTextureBindings,
+ bufferImageBindings);
+ }
+
+ private static TranslationOptions CreateTranslationOptions(TargetApi api, TranslationFlags flags)
+ {
+ TargetLanguage lang = GraphicsConfig.EnableSpirvCompilation ? TargetLanguage.Spirv : TargetLanguage.Glsl;
+
+ return new TranslationOptions(lang, api, flags);
+ }
+
///
/// Disposes the shader cache, deleting all the cached shaders.
/// It's an error to use the shader cache after disposal.
diff --git a/Ryujinx.Graphics.OpenGL/BackgroundContextWorker.cs b/Ryujinx.Graphics.OpenGL/BackgroundContextWorker.cs
index 3f1c055bf..764ea7159 100644
--- a/Ryujinx.Graphics.OpenGL/BackgroundContextWorker.cs
+++ b/Ryujinx.Graphics.OpenGL/BackgroundContextWorker.cs
@@ -1,4 +1,4 @@
-using Ryujinx.Common;
+using Ryujinx.Common;
using System;
using System.Collections.Generic;
using System.Threading;
diff --git a/Ryujinx.Graphics.OpenGL/FormatTable.cs b/Ryujinx.Graphics.OpenGL/FormatTable.cs
index 1a739b5ce..ea710b42c 100644
--- a/Ryujinx.Graphics.OpenGL/FormatTable.cs
+++ b/Ryujinx.Graphics.OpenGL/FormatTable.cs
@@ -88,16 +88,14 @@ namespace Ryujinx.Graphics.OpenGL
Add(Format.Bc3Srgb, new FormatInfo(4, false, false, All.CompressedSrgbAlphaS3tcDxt5Ext));
Add(Format.Bc4Unorm, new FormatInfo(1, true, false, All.CompressedRedRgtc1));
Add(Format.Bc4Snorm, new FormatInfo(1, true, false, All.CompressedSignedRedRgtc1));
- Add(Format.Bc5Unorm, new FormatInfo(2, true, false, All.CompressedRgRgtc2));
- Add(Format.Bc5Snorm, new FormatInfo(2, true, false, All.CompressedSignedRgRgtc2));
- Add(Format.Bc7Unorm, new FormatInfo(4, true, false, All.CompressedRgbaBptcUnorm));
- Add(Format.Bc7Srgb, new FormatInfo(4, false, false, All.CompressedSrgbAlphaBptcUnorm));
- Add(Format.Bc6HSfloat, new FormatInfo(4, false, false, All.CompressedRgbBptcSignedFloat));
- Add(Format.Bc6HUfloat, new FormatInfo(4, false, false, All.CompressedRgbBptcUnsignedFloat));
- Add(Format.Etc2RgbUnorm, new FormatInfo(3, false, false, All.CompressedRgb8Etc2));
- Add(Format.Etc2RgbaUnorm, new FormatInfo(4, false, false, All.CompressedRgba8Etc2Eac));
- Add(Format.Etc2RgbSrgb, new FormatInfo(3, false, false, All.CompressedSrgb8Etc2));
- Add(Format.Etc2RgbaSrgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Etc2Eac));
+ Add(Format.Bc5Unorm, new FormatInfo(1, true, false, All.CompressedRgRgtc2));
+ Add(Format.Bc5Snorm, new FormatInfo(1, true, false, All.CompressedSignedRgRgtc2));
+ Add(Format.Bc7Unorm, new FormatInfo(1, true, false, All.CompressedRgbaBptcUnorm));
+ Add(Format.Bc7Srgb, new FormatInfo(1, false, false, All.CompressedSrgbAlphaBptcUnorm));
+ Add(Format.Bc6HSfloat, new FormatInfo(1, false, false, All.CompressedRgbBptcSignedFloat));
+ Add(Format.Bc6HUfloat, new FormatInfo(1, false, false, All.CompressedRgbBptcUnsignedFloat));
+ Add(Format.Etc2RgbaUnorm, new FormatInfo(1, false, false, All.CompressedRgba8Etc2Eac));
+ Add(Format.Etc2RgbaSrgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Etc2Eac));
Add(Format.R8Uscaled, new FormatInfo(1, false, true, All.R8ui, PixelFormat.RedInteger, PixelType.UnsignedByte));
Add(Format.R8Sscaled, new FormatInfo(1, false, true, All.R8i, PixelFormat.RedInteger, PixelType.Byte));
Add(Format.R16Uscaled, new FormatInfo(1, false, true, All.R16ui, PixelFormat.RedInteger, PixelType.UnsignedShort));
@@ -138,34 +136,34 @@ namespace Ryujinx.Graphics.OpenGL
Add(Format.R32G32B32X32Float, new FormatInfo(4, false, false, All.Rgb32f, PixelFormat.Rgba, PixelType.Float));
Add(Format.R32G32B32X32Uint, new FormatInfo(4, false, false, All.Rgb32ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt));
Add(Format.R32G32B32X32Sint, new FormatInfo(4, false, false, All.Rgb32i, PixelFormat.RgbaInteger, PixelType.Int));
- Add(Format.Astc4x4Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc4X4Khr));
- Add(Format.Astc5x4Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc5X4Khr));
- Add(Format.Astc5x5Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc5X5Khr));
- Add(Format.Astc6x5Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc6X5Khr));
- Add(Format.Astc6x6Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc6X6Khr));
- Add(Format.Astc8x5Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc8X5Khr));
- Add(Format.Astc8x6Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc8X6Khr));
- Add(Format.Astc8x8Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc8X8Khr));
- Add(Format.Astc10x5Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc10X5Khr));
- Add(Format.Astc10x6Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc10X6Khr));
- Add(Format.Astc10x8Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc10X8Khr));
- Add(Format.Astc10x10Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc10X10Khr));
- Add(Format.Astc12x10Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc12X10Khr));
- Add(Format.Astc12x12Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc12X12Khr));
- Add(Format.Astc4x4Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc4X4Khr));
- Add(Format.Astc5x4Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc5X4Khr));
- Add(Format.Astc5x5Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc5X5Khr));
- Add(Format.Astc6x5Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc6X5Khr));
- Add(Format.Astc6x6Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc6X6Khr));
- Add(Format.Astc8x5Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc8X5Khr));
- Add(Format.Astc8x6Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc8X6Khr));
- Add(Format.Astc8x8Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc8X8Khr));
- Add(Format.Astc10x5Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc10X5Khr));
- Add(Format.Astc10x6Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc10X6Khr));
- Add(Format.Astc10x8Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc10X8Khr));
- Add(Format.Astc10x10Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc10X10Khr));
- Add(Format.Astc12x10Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc12X10Khr));
- Add(Format.Astc12x12Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc12X12Khr));
+ Add(Format.Astc4x4Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc4X4Khr));
+ Add(Format.Astc5x4Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc5X4Khr));
+ Add(Format.Astc5x5Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc5X5Khr));
+ Add(Format.Astc6x5Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc6X5Khr));
+ Add(Format.Astc6x6Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc6X6Khr));
+ Add(Format.Astc8x5Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc8X5Khr));
+ Add(Format.Astc8x6Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc8X6Khr));
+ Add(Format.Astc8x8Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc8X8Khr));
+ Add(Format.Astc10x5Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc10X5Khr));
+ Add(Format.Astc10x6Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc10X6Khr));
+ Add(Format.Astc10x8Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc10X8Khr));
+ Add(Format.Astc10x10Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc10X10Khr));
+ Add(Format.Astc12x10Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc12X10Khr));
+ Add(Format.Astc12x12Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc12X12Khr));
+ Add(Format.Astc4x4Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc4X4Khr));
+ Add(Format.Astc5x4Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc5X4Khr));
+ Add(Format.Astc5x5Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc5X5Khr));
+ Add(Format.Astc6x5Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc6X5Khr));
+ Add(Format.Astc6x6Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc6X6Khr));
+ Add(Format.Astc8x5Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc8X5Khr));
+ Add(Format.Astc8x6Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc8X6Khr));
+ Add(Format.Astc8x8Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc8X8Khr));
+ Add(Format.Astc10x5Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc10X5Khr));
+ Add(Format.Astc10x6Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc10X6Khr));
+ Add(Format.Astc10x8Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc10X8Khr));
+ Add(Format.Astc10x10Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc10X10Khr));
+ Add(Format.Astc12x10Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc12X10Khr));
+ Add(Format.Astc12x12Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc12X12Khr));
Add(Format.B5G6R5Unorm, new FormatInfo(3, true, false, All.Rgb565, PixelFormat.Rgb, PixelType.UnsignedShort565Reversed));
Add(Format.B5G5R5X1Unorm, new FormatInfo(4, true, false, All.Rgb5, PixelFormat.Rgba, PixelType.UnsignedShort1555Reversed));
Add(Format.B5G5R5A1Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Rgba, PixelType.UnsignedShort1555Reversed));
diff --git a/Ryujinx.Graphics.OpenGL/Pipeline.cs b/Ryujinx.Graphics.OpenGL/Pipeline.cs
index 62d4dee90..fb6713360 100644
--- a/Ryujinx.Graphics.OpenGL/Pipeline.cs
+++ b/Ryujinx.Graphics.OpenGL/Pipeline.cs
@@ -1106,45 +1106,25 @@ namespace Ryujinx.Graphics.OpenGL
_framebuffer.SetDrawBuffers(colors.Length);
}
- public void SetSampler(int binding, ISampler sampler)
+ public unsafe void SetScissors(ReadOnlySpan> regions)
{
- if (sampler == null)
+ int count = Math.Min(regions.Length, Constants.MaxViewports);
+
+ int* v = stackalloc int[count * 4];
+
+ for (int index = 0; index < count; index++)
{
- return;
- }
+ int vIndex = index * 4;
- Sampler samp = (Sampler)sampler;
+ v[vIndex] = regions[index].X;
+ v[vIndex + 1] = regions[index].Y;
+ v[vIndex + 2] = regions[index].Width;
+ v[vIndex + 3] = regions[index].Height;
- if (binding == 0)
- {
- _unit0Sampler = samp;
- }
-
- samp.Bind(binding);
- }
-
- public void SetScissor(int index, bool enable, int x, int y, int width, int height)
- {
- uint mask = 1u << index;
-
- if (!enable)
- {
- if ((_scissorEnables & mask) != 0)
- {
- _scissorEnables &= ~mask;
- GL.Disable(IndexedEnableCap.ScissorTest, index);
- }
-
- return;
- }
-
- if ((_scissorEnables & mask) == 0)
- {
- _scissorEnables |= mask;
GL.Enable(IndexedEnableCap.ScissorTest, index);
}
- GL.ScissorIndexed(index, x, y, width, height);
+ GL.ScissorArray(0, count, v);
}
public void SetStencilTest(StencilTestDescriptor stencilTest)
@@ -1195,23 +1175,25 @@ namespace Ryujinx.Graphics.OpenGL
SetBuffers(first, buffers, isStorage: true);
}
- public void SetTexture(int binding, ITexture texture)
+ public void SetTextureAndSampler(int binding, ITexture texture, ISampler sampler)
{
- if (texture == null)
+ if (texture != null && sampler != null)
{
- return;
- }
+ if (binding == 0)
+ {
+ _unit0Texture = (TextureBase)texture;
+ _unit0Sampler = (Sampler)sampler;
+ }
+ else
+ {
+ ((TextureBase)texture).Bind(binding);
+ }
- if (binding == 0)
- {
- _unit0Texture = (TextureBase)texture;
- }
- else
- {
- ((TextureBase)texture).Bind(binding);
+ ((Sampler)sampler).Bind(binding);
}
}
+
public void SetTransformFeedbackBuffers(ReadOnlySpan buffers)
{
if (_tfEnabled)
diff --git a/Ryujinx.Graphics.OpenGL/Queries/CounterQueueEvent.cs b/Ryujinx.Graphics.OpenGL/Queries/CounterQueueEvent.cs
index 8b0ae30ea..81451389c 100644
--- a/Ryujinx.Graphics.OpenGL/Queries/CounterQueueEvent.cs
+++ b/Ryujinx.Graphics.OpenGL/Queries/CounterQueueEvent.cs
@@ -1,5 +1,4 @@
using OpenTK.Graphics.OpenGL;
-using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using System;
using System.Threading;
diff --git a/Ryujinx.Graphics.OpenGL/Renderer.cs b/Ryujinx.Graphics.OpenGL/Renderer.cs
index 2a9ab4223..d7ed6d004 100644
--- a/Ryujinx.Graphics.OpenGL/Renderer.cs
+++ b/Ryujinx.Graphics.OpenGL/Renderer.cs
@@ -87,6 +87,11 @@ namespace Ryujinx.Graphics.OpenGL
Buffer.Delete(buffer);
}
+ public HardwareInfo GetHardwareInfo()
+ {
+ return new HardwareInfo(GpuVendor, GpuRenderer);
+ }
+
public ReadOnlySpan GetBufferData(BufferHandle buffer, int offset, int size)
{
return Buffer.GetData(this, buffer, offset, size);
diff --git a/Ryujinx.Graphics.OpenGL/Window.cs b/Ryujinx.Graphics.OpenGL/Window.cs
index ae74558e9..cd8efd129 100644
--- a/Ryujinx.Graphics.OpenGL/Window.cs
+++ b/Ryujinx.Graphics.OpenGL/Window.cs
@@ -106,8 +106,6 @@ namespace Ryujinx.Graphics.OpenGL
GL.Disable(EnableCap.RasterizerDiscard);
GL.Disable(IndexedEnableCap.ScissorTest, 0);
- GL.Clear(ClearBufferMask.ColorBufferBit);
-
int srcX0, srcX1, srcY0, srcY1;
float scale = view.ScaleFactor;
diff --git a/Ryujinx.Graphics.Shader/AttributeType.cs b/Ryujinx.Graphics.Shader/AttributeType.cs
new file mode 100644
index 000000000..11112f682
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/AttributeType.cs
@@ -0,0 +1,36 @@
+using System;
+
+namespace Ryujinx.Graphics.Shader
+{
+ public enum AttributeType
+ {
+ Float,
+ Sint,
+ Uint
+ }
+
+ static class AttributeTypeExtensions
+ {
+ public static string GetScalarType(this AttributeType type)
+ {
+ return type switch
+ {
+ AttributeType.Float => "float",
+ AttributeType.Sint => "int",
+ AttributeType.Uint => "uint",
+ _ => throw new ArgumentException($"Invalid attribute type \"{type}\".")
+ };
+ }
+
+ public static string GetVec4Type(this AttributeType type)
+ {
+ return type switch
+ {
+ AttributeType.Float => "vec4",
+ AttributeType.Sint => "ivec4",
+ AttributeType.Uint => "uvec4",
+ _ => throw new ArgumentException($"Invalid attribute type \"{type}\".")
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
index 59a7ccdca..b5fe0973a 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
@@ -11,7 +11,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{
public static void Declare(CodeGenContext context, StructuredProgramInfo info)
{
- context.AppendLine("#version 450 core");
+ context.AppendLine(context.Config.Options.TargetApi == TargetApi.Vulkan ? "#version 460 core" : "#version 450 core");
context.AppendLine("#extension GL_ARB_gpu_shader_int64 : enable");
if (context.Config.GpuAccessor.QueryHostSupportsShaderBallot())
@@ -326,11 +326,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
switch (type)
{
case VariableType.Bool: return "bool";
- case VariableType.F32: return "precise float";
- case VariableType.F64: return "double";
+ case VariableType.F32: return "precise float";
+ case VariableType.F64: return "double";
case VariableType.None: return "void";
- case VariableType.S32: return "int";
- case VariableType.U32: return "uint";
+ case VariableType.S32: return "int";
+ case VariableType.U32: return "uint";
}
throw new ArgumentException($"Invalid variable type \"{type}\".");
@@ -530,16 +530,38 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
if (context.Config.TransformFeedbackEnabled && context.Config.Stage != ShaderStage.Vertex)
{
+ string type;
+
+ if (context.Config.Stage == ShaderStage.Vertex)
+ {
+ type = context.Config.GpuAccessor.QueryAttributeType(attr).GetScalarType();
+ }
+ else
+ {
+ type = AttributeType.Float.GetScalarType();
+ }
+
for (int c = 0; c < 4; c++)
{
char swzMask = "xyzw"[c];
- context.AppendLine($"layout ({pass}location = {attr}, component = {c}) {iq}in float {name}_{swzMask}{suffix};");
+ context.AppendLine($"layout ({pass}location = {attr}, component = {c}) {iq}in {type} {name}_{swzMask}{suffix};");
}
}
else
{
- context.AppendLine($"layout ({pass}location = {attr}) {iq}in vec4 {name}{suffix};");
+ string type;
+
+ if (context.Config.Stage == ShaderStage.Vertex)
+ {
+ type = context.Config.GpuAccessor.QueryAttributeType(attr).GetVec4Type();
+ }
+ else
+ {
+ type = AttributeType.Float.GetVec4Type();
+ }
+
+ context.AppendLine($"layout ({pass}location = {attr}) {iq}in {type} {name}{suffix};");
}
}
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs
index 3af120f88..e9dbdd2d3 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs
@@ -127,7 +127,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
else if (node is AstAssignment assignment)
{
VariableType srcType = OperandManager.GetNodeDestType(context, assignment.Source);
- VariableType dstType = OperandManager.GetNodeDestType(context, assignment.Destination);
+ VariableType dstType = OperandManager.GetNodeDestType(context, assignment.Destination, isAsgDest: true);
string dest;
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs
index 69214a355..c40f96f11 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs
@@ -7,11 +7,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{
static class InstGenHelper
{
- private static InstInfo[] _infoTbl;
+ private static readonly InstInfo[] InfoTable;
static InstGenHelper()
{
- _infoTbl = new InstInfo[(int)Instruction.Count];
+ InfoTable = new InstInfo[(int)Instruction.Count];
Add(Instruction.AtomicAdd, InstType.AtomicBinary, "atomicAdd");
Add(Instruction.AtomicAnd, InstType.AtomicBinary, "atomicAnd");
@@ -139,12 +139,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
private static void Add(Instruction inst, InstType flags, string opName = null, int precedence = 0)
{
- _infoTbl[(int)inst] = new InstInfo(flags, opName, precedence);
+ InfoTable[(int)inst] = new InstInfo(flags, opName, precedence);
}
public static InstInfo GetInstructionInfo(Instruction inst)
{
- return _infoTbl[(int)(inst & Instruction.Mask)];
+ return InfoTable[(int)(inst & Instruction.Mask)];
}
public static string GetSoureExpr(CodeGenContext context, IAstNode node, VariableType dstType)
@@ -191,7 +191,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return false;
}
- InstInfo info = _infoTbl[(int)(operation.Inst & Instruction.Mask)];
+ InstInfo info = InfoTable[(int)(operation.Inst & Instruction.Mask)];
if ((info.Type & (InstType.Call | InstType.Special)) != 0)
{
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs
index 334c744d7..c5d60ee16 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs
@@ -11,7 +11,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{
class OperandManager
{
- private static string[] _stagePrefixes = new string[] { "cp", "vp", "tcp", "tep", "gp", "fp" };
+ private static readonly string[] StagePrefixes = new string[] { "cp", "vp", "tcp", "tep", "gp", "fp" };
private struct BuiltInAttribute
{
@@ -26,8 +26,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
}
}
- private static Dictionary _builtInAttributes =
- new Dictionary()
+ private static Dictionary _builtInAttributes = new Dictionary()
{
{ AttributeConsts.TessLevelOuter0, new BuiltInAttribute("gl_TessLevelOuter[0]", VariableType.F32) },
{ AttributeConsts.TessLevelOuter1, new BuiltInAttribute("gl_TessLevelOuter[1]", VariableType.F32) },
@@ -391,12 +390,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{
int index = (int)stage;
- if ((uint)index >= _stagePrefixes.Length)
+ if ((uint)index >= StagePrefixes.Length)
{
return "invalid";
}
- return _stagePrefixes[index];
+ return StagePrefixes[index];
}
private static char GetSwizzleMask(int value)
@@ -409,7 +408,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
return $"{DefaultNames.ArgumentNamePrefix}{argIndex}";
}
- public static VariableType GetNodeDestType(CodeGenContext context, IAstNode node)
+ public static VariableType GetNodeDestType(CodeGenContext context, IAstNode node, bool isAsgDest = false)
{
if (node is AstOperation operation)
{
@@ -455,7 +454,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
return context.CurrentFunction.GetArgumentType(argIndex);
}
- return GetOperandVarType(operand);
+ return GetOperandVarType(context, operand, isAsgDest);
}
else
{
@@ -463,7 +462,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
}
}
- private static VariableType GetOperandVarType(AstOperand operand)
+ private static VariableType GetOperandVarType(CodeGenContext context, AstOperand operand, bool isAsgDest = false)
{
if (operand.Type == OperandType.Attribute)
{
@@ -471,6 +470,21 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{
return builtInAttr.Type;
}
+ else if (context.Config.Stage == ShaderStage.Vertex && !isAsgDest &&
+ operand.Value >= AttributeConsts.UserAttributeBase &&
+ operand.Value < AttributeConsts.UserAttributeEnd)
+ {
+ int location = (operand.Value - AttributeConsts.UserAttributeBase) / 16;
+
+ AttributeType type = context.Config.GpuAccessor.QueryAttributeType(location);
+
+ return type switch
+ {
+ AttributeType.Sint => VariableType.S32,
+ AttributeType.Uint => VariableType.U32,
+ _ => VariableType.F32
+ };
+ }
}
return OperandInfo.GetVarType(operand);
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs b/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs
new file mode 100644
index 000000000..a30b33934
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs
@@ -0,0 +1,351 @@
+using Ryujinx.Graphics.Shader.StructuredIr;
+using Ryujinx.Graphics.Shader.Translation;
+using Spv.Generator;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using static Spv.Specification;
+
+namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
+{
+ using IrConsts = IntermediateRepresentation.IrConsts;
+ using IrOperandType = IntermediateRepresentation.OperandType;
+
+ partial class CodeGenContext : Module
+ {
+ public ShaderConfig Config { get; }
+
+ public Instruction ExtSet { get; }
+
+ public Dictionary UniformBuffers { get; } = new Dictionary();
+ public Instruction StorageBuffersArray { get; set; }
+ public Instruction LocalMemory { get; set; }
+ public Instruction SharedMemory { get; set; }
+ public Dictionary Samplers { get; } = new Dictionary();
+ public Dictionary Images { get; } = new Dictionary();
+ public Dictionary Inputs { get; } = new Dictionary();
+ public Dictionary Outputs { get; } = new Dictionary();
+
+ private readonly Dictionary _locals = new Dictionary();
+ private readonly Dictionary _localForArgs = new Dictionary();
+ private readonly Dictionary _funcArgs = new Dictionary();
+ private readonly Dictionary _functions = new Dictionary();
+
+ private class BlockState
+ {
+ private int _entryCount;
+ private readonly List _labels = new List();
+
+ public Instruction GetNextLabel(CodeGenContext context)
+ {
+ return GetLabel(context, _entryCount);
+ }
+
+ public Instruction GetNextLabelAutoIncrement(CodeGenContext context)
+ {
+ return GetLabel(context, _entryCount++);
+ }
+
+ public Instruction GetLabel(CodeGenContext context, int index)
+ {
+ while (index >= _labels.Count)
+ {
+ _labels.Add(context.Label());
+ }
+
+ return _labels[index];
+ }
+ }
+
+ private readonly Dictionary _labels = new Dictionary();
+
+ public AstBlock CurrentBlock { get; private set; }
+
+ public CodeGenContext(ShaderConfig config) : base(0x00010300)
+ {
+ Config = config;
+
+ AddCapability(Capability.Shader);
+ AddCapability(Capability.Float64);
+
+ ExtSet = AddExtInstImport("GLSL.std.450");
+
+ SetMemoryModel(AddressingModel.Logical, MemoryModel.GLSL450);
+ }
+
+ public void StartFunction()
+ {
+ _locals.Clear();
+ _localForArgs.Clear();
+ _funcArgs.Clear();
+ }
+
+ public void EnterBlock(AstBlock block)
+ {
+ CurrentBlock = block;
+ AddLabel(GetBlockStateLazy(block).GetNextLabelAutoIncrement(this));
+ }
+
+ public Instruction GetFirstLabel(AstBlock block)
+ {
+ return GetBlockStateLazy(block).GetLabel(this, 0);
+ }
+
+ public Instruction GetNextLabel(AstBlock block)
+ {
+ return GetBlockStateLazy(block).GetNextLabel(this);
+ }
+
+ private BlockState GetBlockStateLazy(AstBlock block)
+ {
+ if (!_labels.TryGetValue(block, out var blockState))
+ {
+ blockState = new BlockState();
+
+ _labels.Add(block, blockState);
+ }
+
+ return blockState;
+ }
+
+ public Instruction NewBlock()
+ {
+ var label = Label();
+ Branch(label);
+ AddLabel(label);
+ return label;
+ }
+
+ public Instruction[] GetMainInterface()
+ {
+ return Inputs.Values.Concat(Outputs.Values).ToArray();
+ }
+
+ public void DeclareLocal(AstOperand local, Instruction spvLocal)
+ {
+ _locals.Add(local, spvLocal);
+ }
+
+ public void DeclareLocalForArgs(int funcIndex, Instruction[] spvLocals)
+ {
+ _localForArgs.Add(funcIndex, spvLocals);
+ }
+
+ public void DeclareArgument(int argIndex, Instruction spvLocal)
+ {
+ _funcArgs.Add(argIndex, spvLocal);
+ }
+
+ public void DeclareFunction(int funcIndex, StructuredFunction function, Instruction spvFunc)
+ {
+ _functions.Add(funcIndex, (function, spvFunc));
+ }
+
+ public Instruction GetFP32(IAstNode node)
+ {
+ return Get(AggregateType.FP32, node);
+ }
+
+ public Instruction GetFP64(IAstNode node)
+ {
+ return Get(AggregateType.FP64, node);
+ }
+
+ public Instruction GetS32(IAstNode node)
+ {
+ return Get(AggregateType.S32, node);
+ }
+
+ public Instruction GetU32(IAstNode node)
+ {
+ return Get(AggregateType.U32, node);
+ }
+
+ public Instruction Get(AggregateType type, IAstNode node)
+ {
+ if (node is AstOperation operation)
+ {
+ var opResult = Instructions.Generate(this, operation);
+ return BitcastIfNeeded(type, opResult.Type, opResult.Value);
+ }
+ else if (node is AstOperand operand)
+ {
+ return operand.Type switch
+ {
+ IrOperandType.Argument => GetArgument(type, operand),
+ IrOperandType.Attribute => GetAttribute(type, operand, false),
+ IrOperandType.Constant => GetConstant(type, operand),
+ IrOperandType.ConstantBuffer => GetConstantBuffer(type, operand),
+ IrOperandType.LocalVariable => GetLocal(type, operand),
+ IrOperandType.Undefined => Undef(GetType(type)),
+ _ => throw new ArgumentException($"Invalid operand type \"{operand.Type}\".")
+ };
+ }
+
+ throw new NotImplementedException(node.GetType().Name);
+ }
+
+ public Instruction GetAttributeVectorPointer(AstOperand operand, bool isOutAttr)
+ {
+ var attrInfo = AttributeInfo.From(Config, operand.Value);
+
+ return isOutAttr ? Outputs[attrInfo.BaseValue] : Inputs[attrInfo.BaseValue];
+ }
+
+ public Instruction GetAttributeElemPointer(AstOperand operand, bool isOutAttr, out AggregateType elemType)
+ {
+ var attrInfo = AttributeInfo.From(Config, operand.Value);
+ if (attrInfo.BaseValue == AttributeConsts.PositionX && Config.Stage != ShaderStage.Fragment)
+ {
+ isOutAttr = true;
+ }
+
+ elemType = attrInfo.Type & AggregateType.ElementTypeMask;
+
+ var ioVariable = isOutAttr ? Outputs[attrInfo.BaseValue] : Inputs[attrInfo.BaseValue];
+
+ if ((attrInfo.Type & (AggregateType.Array | AggregateType.Vector)) == 0)
+ {
+ return ioVariable;
+ }
+
+ var storageClass = isOutAttr ? StorageClass.Output : StorageClass.Input;
+
+ var elemIndex = Constant(TypeU32(), attrInfo.GetInnermostIndex());
+ return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, elemIndex);
+ }
+
+ public Instruction GetAttribute(AggregateType type, AstOperand operand, bool isOutAttr)
+ {
+ var elemPointer = GetAttributeElemPointer(operand, isOutAttr, out var elemType);
+ return BitcastIfNeeded(type, elemType, Load(GetType(elemType), elemPointer));
+ }
+
+ public Instruction GetConstant(AggregateType type, AstOperand operand)
+ {
+ return type switch
+ {
+ AggregateType.Bool => operand.Value != 0 ? ConstantTrue(TypeBool()) : ConstantFalse(TypeBool()),
+ AggregateType.FP32 => Constant(TypeFP32(), BitConverter.Int32BitsToSingle(operand.Value)),
+ AggregateType.FP64 => Constant(TypeFP64(), (double)BitConverter.Int32BitsToSingle(operand.Value)),
+ AggregateType.S32 => Constant(TypeS32(), operand.Value),
+ AggregateType.U32 => Constant(TypeU32(), (uint)operand.Value),
+ _ => throw new ArgumentException($"Invalid type \"{type}\".")
+ };
+ }
+
+ public Instruction GetConstantBuffer(AggregateType type, AstOperand operand)
+ {
+ var ubVariable = UniformBuffers[operand.CbufSlot];
+ var i0 = Constant(TypeS32(), 0);
+ var i1 = Constant(TypeS32(), operand.CbufOffset >> 2);
+ var i2 = Constant(TypeU32(), operand.CbufOffset & 3);
+
+ var elemPointer = AccessChain(TypePointer(StorageClass.Uniform, TypeFP32()), ubVariable, i0, i1, i2);
+ return BitcastIfNeeded(type, AggregateType.FP32, Load(TypeFP32(), elemPointer));
+ }
+
+ public Instruction GetLocalPointer(AstOperand local)
+ {
+ return _locals[local];
+ }
+
+ public Instruction[] GetLocalForArgsPointers(int funcIndex)
+ {
+ return _localForArgs[funcIndex];
+ }
+
+ public Instruction GetArgumentPointer(AstOperand funcArg)
+ {
+ return _funcArgs[funcArg.Value];
+ }
+
+ public Instruction GetLocal(AggregateType dstType, AstOperand local)
+ {
+ var srcType = local.VarType.Convert();
+ return BitcastIfNeeded(dstType, srcType, Load(GetType(srcType), GetLocalPointer(local)));
+ }
+
+ public Instruction GetArgument(AggregateType dstType, AstOperand funcArg)
+ {
+ var srcType = funcArg.VarType.Convert();
+ return BitcastIfNeeded(dstType, srcType, Load(GetType(srcType), GetArgumentPointer(funcArg)));
+ }
+
+ public (StructuredFunction, Instruction) GetFunction(int funcIndex)
+ {
+ return _functions[funcIndex];
+ }
+
+ protected override void Construct()
+ {
+ }
+
+ public Instruction GetType(AggregateType type, int length = 1)
+ {
+ if (type.HasFlag(AggregateType.Array))
+ {
+ return TypeArray(GetType(type & ~AggregateType.Array), Constant(TypeU32(), length));
+ }
+ else if (type.HasFlag(AggregateType.Vector))
+ {
+ return TypeVector(GetType(type & ~AggregateType.Vector), length);
+ }
+
+ return type switch
+ {
+ AggregateType.Void => TypeVoid(),
+ AggregateType.Bool => TypeBool(),
+ AggregateType.FP32 => TypeFP32(),
+ AggregateType.FP64 => TypeFP64(),
+ AggregateType.S32 => TypeS32(),
+ AggregateType.U32 => TypeU32(),
+ _ => throw new ArgumentException($"Invalid attribute type \"{type}\".")
+ };
+ }
+
+ public Instruction BitcastIfNeeded(AggregateType dstType, AggregateType srcType, Instruction value)
+ {
+ if (dstType == srcType)
+ {
+ return value;
+ }
+
+ if (dstType == AggregateType.Bool)
+ {
+ return INotEqual(TypeBool(), BitcastIfNeeded(AggregateType.S32, srcType, value), Constant(TypeS32(), 0));
+ }
+ else if (srcType == AggregateType.Bool)
+ {
+ var intTrue = Constant(TypeS32(), IrConsts.True);
+ var intFalse = Constant(TypeS32(), IrConsts.False);
+
+ return BitcastIfNeeded(dstType, AggregateType.S32, Select(TypeS32(), value, intTrue, intFalse));
+ }
+ else
+ {
+ return Bitcast(GetType(dstType, 1), value);
+ }
+ }
+
+ public Instruction TypeS32()
+ {
+ return TypeInt(32, true);
+ }
+
+ public Instruction TypeU32()
+ {
+ return TypeInt(32, false);
+ }
+
+ public Instruction TypeFP32()
+ {
+ return TypeFloat(32);
+ }
+
+ public Instruction TypeFP64()
+ {
+ return TypeFloat(64);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs b/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs
new file mode 100644
index 000000000..ce8e2dd13
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs
@@ -0,0 +1,474 @@
+using Ryujinx.Common;
+using Ryujinx.Graphics.Shader.StructuredIr;
+using Ryujinx.Graphics.Shader.Translation;
+using Spv.Generator;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using static Spv.Specification;
+
+namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
+{
+ using SpvInstruction = Spv.Generator.Instruction;
+
+ static class Declarations
+ {
+ // At least 16 attributes are guaranteed by the spec.
+ public const int MaxAttributes = 16;
+
+ private static readonly string[] StagePrefixes = new string[] { "cp", "vp", "tcp", "tep", "gp", "fp" };
+
+ public static void DeclareParameters(CodeGenContext context, StructuredFunction function)
+ {
+ DeclareParameters(context, function.InArguments, 0);
+ DeclareParameters(context, function.OutArguments, function.InArguments.Length);
+ }
+
+ private static void DeclareParameters(CodeGenContext context, IEnumerable argTypes, int argIndex)
+ {
+ foreach (var argType in argTypes)
+ {
+ var argPointerType = context.TypePointer(StorageClass.Function, context.GetType(argType.Convert()));
+ var spvArg = context.FunctionParameter(argPointerType);
+
+ context.DeclareArgument(argIndex++, spvArg);
+ }
+ }
+
+ public static void DeclareLocals(CodeGenContext context, StructuredFunction function)
+ {
+ foreach (AstOperand local in function.Locals)
+ {
+ var localPointerType = context.TypePointer(StorageClass.Function, context.GetType(local.VarType.Convert()));
+ var spvLocal = context.Variable(localPointerType, StorageClass.Function);
+
+ context.AddLocalVariable(spvLocal);
+ context.DeclareLocal(local, spvLocal);
+ }
+ }
+
+ public static void DeclareLocalForArgs(CodeGenContext context, List functions)
+ {
+ for (int funcIndex = 0; funcIndex < functions.Count; funcIndex++)
+ {
+ StructuredFunction function = functions[funcIndex];
+ SpvInstruction[] locals = new SpvInstruction[function.InArguments.Length];
+
+ for (int i = 0; i < function.InArguments.Length; i++)
+ {
+ var type = function.GetArgumentType(i).Convert();
+ var localPointerType = context.TypePointer(StorageClass.Function, context.GetType(type));
+ var spvLocal = context.Variable(localPointerType, StorageClass.Function);
+
+ context.AddLocalVariable(spvLocal);
+
+ locals[i] = spvLocal;
+ }
+
+ context.DeclareLocalForArgs(funcIndex, locals);
+ }
+ }
+
+ public static void DeclareAll(CodeGenContext context, StructuredProgramInfo info)
+ {
+ if (context.Config.Stage == ShaderStage.Compute)
+ {
+ int localMemorySize = BitUtils.DivRoundUp(context.Config.GpuAccessor.QueryComputeLocalMemorySize(), 4);
+
+ if (localMemorySize != 0)
+ {
+ DeclareLocalMemory(context, localMemorySize);
+ }
+
+ int sharedMemorySize = BitUtils.DivRoundUp(context.Config.GpuAccessor.QueryComputeSharedMemorySize(), 4);
+
+ if (sharedMemorySize != 0)
+ {
+ DeclareSharedMemory(context, sharedMemorySize);
+ }
+ }
+ else if (context.Config.LocalMemorySize != 0)
+ {
+ int localMemorySize = BitUtils.DivRoundUp(context.Config.LocalMemorySize, 4);
+ DeclareLocalMemory(context, localMemorySize);
+ }
+
+ DeclareUniformBuffers(context, context.Config.GetConstantBufferDescriptors());
+ DeclareStorageBuffers(context, context.Config.GetStorageBufferDescriptors());
+ DeclareSamplers(context, context.Config.GetTextureDescriptors());
+ DeclareImages(context, context.Config.GetImageDescriptors());
+ DeclareInputAttributes(context, info);
+ DeclareOutputAttributes(context, info);
+ }
+
+ private static void DeclareLocalMemory(CodeGenContext context, int size)
+ {
+ context.LocalMemory = DeclareMemory(context, StorageClass.Private, size);
+ }
+
+ private static void DeclareSharedMemory(CodeGenContext context, int size)
+ {
+ context.SharedMemory = DeclareMemory(context, StorageClass.Workgroup, size);
+ }
+
+ private static SpvInstruction DeclareMemory(CodeGenContext context, StorageClass storage, int size)
+ {
+ var arrayType = context.TypeArray(context.TypeU32(), context.Constant(context.TypeU32(), size));
+ var pointerType = context.TypePointer(storage, arrayType);
+ var variable = context.Variable(pointerType, storage);
+
+ context.AddGlobalVariable(variable);
+
+ return variable;
+ }
+
+ private static void DeclareUniformBuffers(CodeGenContext context, BufferDescriptor[] descriptors)
+ {
+ uint ubSize = Constants.ConstantBufferSize / 16;
+
+ var ubArrayType = context.TypeArray(context.TypeVector(context.TypeFP32(), 4), context.Constant(context.TypeU32(), ubSize), true);
+ context.Decorate(ubArrayType, Decoration.ArrayStride, (LiteralInteger)16);
+ var ubStructType = context.TypeStruct(true, ubArrayType);
+ context.Decorate(ubStructType, Decoration.Block);
+ context.MemberDecorate(ubStructType, 0, Decoration.Offset, (LiteralInteger)0);
+ var ubPointerType = context.TypePointer(StorageClass.Uniform, ubStructType);
+
+ foreach (var descriptor in descriptors)
+ {
+ var ubVariable = context.Variable(ubPointerType, StorageClass.Uniform);
+
+ context.Name(ubVariable, $"{GetStagePrefix(context.Config.Stage)}_c{descriptor.Slot}");
+ context.Decorate(ubVariable, Decoration.DescriptorSet, (LiteralInteger)0);
+ context.Decorate(ubVariable, Decoration.Binding, (LiteralInteger)descriptor.Binding);
+ context.AddGlobalVariable(ubVariable);
+ context.UniformBuffers.Add(descriptor.Slot, ubVariable);
+ }
+ }
+
+ private static void DeclareStorageBuffers(CodeGenContext context, BufferDescriptor[] descriptors)
+ {
+ if (descriptors.Length == 0)
+ {
+ return;
+ }
+
+ int setIndex = context.Config.Options.TargetApi == TargetApi.Vulkan ? 1 : 0;
+ int count = descriptors.Max(x => x.Binding) + 1;
+
+ var sbArrayType = context.TypeRuntimeArray(context.TypeU32());
+ context.Decorate(sbArrayType, Decoration.ArrayStride, (LiteralInteger)4);
+ var sbStructType = context.TypeStruct(true, sbArrayType);
+ context.Decorate(sbStructType, Decoration.BufferBlock);
+ context.MemberDecorate(sbStructType, 0, Decoration.Offset, (LiteralInteger)0);
+ var sbStructArrayType = context.TypeArray(sbStructType, context.Constant(context.TypeU32(), count));
+ var sbPointerType = context.TypePointer(StorageClass.Uniform, sbStructArrayType);
+ var sbVariable = context.Variable(sbPointerType, StorageClass.Uniform);
+
+ context.Name(sbVariable, $"{GetStagePrefix(context.Config.Stage)}_s");
+ context.Decorate(sbVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex);
+ context.Decorate(sbVariable, Decoration.Binding, (LiteralInteger)descriptors[0].Binding);
+ context.AddGlobalVariable(sbVariable);
+
+ context.StorageBuffersArray = sbVariable;
+ }
+
+ private static void DeclareSamplers(CodeGenContext context, TextureDescriptor[] descriptors)
+ {
+ foreach (var descriptor in descriptors)
+ {
+ var meta = new TextureMeta(descriptor.CbufSlot, descriptor.HandleIndex, descriptor.Format, descriptor.Type);
+
+ if (context.Samplers.ContainsKey(meta))
+ {
+ continue;
+ }
+
+ bool isBuffer = (descriptor.Type & SamplerType.Mask) == SamplerType.TextureBuffer;
+ int setIndex = context.Config.Options.TargetApi == TargetApi.Vulkan ? (isBuffer ? 4 : 2) : 0;
+
+ var dim = (meta.Type & SamplerType.Mask) switch
+ {
+ SamplerType.Texture1D => Dim.Dim1D,
+ SamplerType.Texture2D => Dim.Dim2D,
+ SamplerType.Texture3D => Dim.Dim3D,
+ SamplerType.TextureCube => Dim.Cube,
+ SamplerType.TextureBuffer => Dim.Buffer,
+ _ => throw new InvalidOperationException($"Invalid sampler type \"{meta.Type & SamplerType.Mask}\".")
+ };
+
+ var imageType = context.TypeImage(
+ context.TypeFP32(),
+ dim,
+ meta.Type.HasFlag(SamplerType.Shadow),
+ meta.Type.HasFlag(SamplerType.Array),
+ meta.Type.HasFlag(SamplerType.Multisample),
+ 1,
+ ImageFormat.Unknown);
+
+ var nameSuffix = meta.CbufSlot < 0 ? $"_tcb_{meta.Handle:X}" : $"_cb{meta.CbufSlot}_{meta.Handle:X}";
+
+ var sampledImageType = context.TypeSampledImage(imageType);
+ var sampledImagePointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageType);
+ var sampledImageVariable = context.Variable(sampledImagePointerType, StorageClass.UniformConstant);
+
+ context.Samplers.Add(meta, (imageType, sampledImageType, sampledImageVariable));
+
+ context.Name(sampledImageVariable, $"{GetStagePrefix(context.Config.Stage)}_tex{nameSuffix}");
+ context.Decorate(sampledImageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex);
+ context.Decorate(sampledImageVariable, Decoration.Binding, (LiteralInteger)descriptor.Binding);
+ context.AddGlobalVariable(sampledImageVariable);
+ }
+ }
+
+ private static void DeclareImages(CodeGenContext context, TextureDescriptor[] descriptors)
+ {
+ foreach (var descriptor in descriptors)
+ {
+ var meta = new TextureMeta(descriptor.CbufSlot, descriptor.HandleIndex, descriptor.Format, descriptor.Type);
+
+ if (context.Images.ContainsKey(meta))
+ {
+ continue;
+ }
+
+ bool isBuffer = (descriptor.Type & SamplerType.Mask) == SamplerType.TextureBuffer;
+ int setIndex = context.Config.Options.TargetApi == TargetApi.Vulkan ? (isBuffer ? 5 : 3) : 0;
+
+ var dim = GetDim(meta.Type);
+
+ var imageType = context.TypeImage(
+ context.GetType(meta.Format.GetComponentType().Convert()),
+ dim,
+ meta.Type.HasFlag(SamplerType.Shadow),
+ meta.Type.HasFlag(SamplerType.Array),
+ meta.Type.HasFlag(SamplerType.Multisample),
+ 2,
+ GetImageFormat(meta.Format));
+
+ var nameSuffix = meta.CbufSlot < 0 ?
+ $"_tcb_{meta.Handle:X}_{meta.Format.ToGlslFormat()}" :
+ $"_cb{meta.CbufSlot}_{meta.Handle:X}_{meta.Format.ToGlslFormat()}";
+
+ var imagePointerType = context.TypePointer(StorageClass.UniformConstant, imageType);
+ var imageVariable = context.Variable(imagePointerType, StorageClass.UniformConstant);
+
+ context.Images.Add(meta, (imageType, imageVariable));
+
+ context.Name(imageVariable, $"{GetStagePrefix(context.Config.Stage)}_img{nameSuffix}");
+ context.Decorate(imageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex);
+ context.Decorate(imageVariable, Decoration.Binding, (LiteralInteger)descriptor.Binding);
+ context.AddGlobalVariable(imageVariable);
+ }
+ }
+
+ private static Dim GetDim(SamplerType type)
+ {
+ return (type & SamplerType.Mask) switch
+ {
+ SamplerType.Texture1D => Dim.Dim1D,
+ SamplerType.Texture2D => Dim.Dim2D,
+ SamplerType.Texture3D => Dim.Dim3D,
+ SamplerType.TextureCube => Dim.Cube,
+ SamplerType.TextureBuffer => Dim.Buffer,
+ _ => throw new ArgumentException($"Invalid sampler type \"{type & SamplerType.Mask}\".")
+ };
+ }
+
+ private static ImageFormat GetImageFormat(TextureFormat format)
+ {
+ return format switch
+ {
+ TextureFormat.Unknown => ImageFormat.Unknown,
+ TextureFormat.R8Unorm => ImageFormat.R8,
+ TextureFormat.R8Snorm => ImageFormat.R8Snorm,
+ TextureFormat.R8Uint => ImageFormat.R8ui,
+ TextureFormat.R8Sint => ImageFormat.R8i,
+ TextureFormat.R16Float => ImageFormat.R16f,
+ TextureFormat.R16Unorm => ImageFormat.R16,
+ TextureFormat.R16Snorm => ImageFormat.R16Snorm,
+ TextureFormat.R16Uint => ImageFormat.R16ui,
+ TextureFormat.R16Sint => ImageFormat.R16i,
+ TextureFormat.R32Float => ImageFormat.R32f,
+ TextureFormat.R32Uint => ImageFormat.R32ui,
+ TextureFormat.R32Sint => ImageFormat.R32i,
+ TextureFormat.R8G8Unorm => ImageFormat.Rg8,
+ TextureFormat.R8G8Snorm => ImageFormat.Rg8Snorm,
+ TextureFormat.R8G8Uint => ImageFormat.Rg8ui,
+ TextureFormat.R8G8Sint => ImageFormat.Rg8i,
+ TextureFormat.R16G16Float => ImageFormat.Rg16f,
+ TextureFormat.R16G16Unorm => ImageFormat.Rg16,
+ TextureFormat.R16G16Snorm => ImageFormat.Rg16Snorm,
+ TextureFormat.R16G16Uint => ImageFormat.Rg16ui,
+ TextureFormat.R16G16Sint => ImageFormat.Rg16i,
+ TextureFormat.R32G32Float => ImageFormat.Rg32f,
+ TextureFormat.R32G32Uint => ImageFormat.Rg32ui,
+ TextureFormat.R32G32Sint => ImageFormat.Rg32i,
+ TextureFormat.R8G8B8A8Unorm => ImageFormat.Rgba8,
+ TextureFormat.R8G8B8A8Snorm => ImageFormat.Rgba8Snorm,
+ TextureFormat.R8G8B8A8Uint => ImageFormat.Rgba8ui,
+ TextureFormat.R8G8B8A8Sint => ImageFormat.Rgba8i,
+ TextureFormat.R16G16B16A16Float => ImageFormat.Rgba16f,
+ TextureFormat.R16G16B16A16Unorm => ImageFormat.Rgba16,
+ TextureFormat.R16G16B16A16Snorm => ImageFormat.Rgba16Snorm,
+ TextureFormat.R16G16B16A16Uint => ImageFormat.Rgba16ui,
+ TextureFormat.R16G16B16A16Sint => ImageFormat.Rgba16i,
+ TextureFormat.R32G32B32A32Float => ImageFormat.Rgba32f,
+ TextureFormat.R32G32B32A32Uint => ImageFormat.Rgba32ui,
+ TextureFormat.R32G32B32A32Sint => ImageFormat.Rgba32i,
+ TextureFormat.R10G10B10A2Unorm => ImageFormat.Rgb10A2,
+ TextureFormat.R10G10B10A2Uint => ImageFormat.Rgb10a2ui,
+ TextureFormat.R11G11B10Float => ImageFormat.R11fG11fB10f,
+ _ => throw new ArgumentException($"Invalid texture format \"{format}\".")
+ };
+ }
+
+ private static void DeclareInputAttributes(CodeGenContext context, StructuredProgramInfo info)
+ {
+ foreach (int attr in info.Inputs)
+ {
+ PixelImap iq = PixelImap.Unused;
+
+ if (context.Config.Stage == ShaderStage.Fragment &&
+ attr >= AttributeConsts.UserAttributeBase &&
+ attr < AttributeConsts.UserAttributeEnd)
+ {
+ iq = context.Config.ImapTypes[(attr - AttributeConsts.UserAttributeBase) / 16].GetFirstUsedType();
+ }
+
+ if (context.Config.TransformFeedbackEnabled)
+ {
+ throw new NotImplementedException();
+ }
+ else
+ {
+ DeclareInputOrOutput(context, attr, false, iq);
+ }
+ }
+ }
+
+ private static void DeclareOutputAttributes(CodeGenContext context, StructuredProgramInfo info)
+ {
+ foreach (int attr in info.Outputs)
+ {
+ DeclareOutputAttribute(context, attr);
+ }
+
+ if (context.Config.Stage != ShaderStage.Compute &&
+ context.Config.Stage != ShaderStage.Fragment &&
+ !context.Config.GpPassthrough)
+ {
+ for (int attr = 0; attr < MaxAttributes; attr++)
+ {
+ DeclareOutputAttribute(context, AttributeConsts.UserAttributeBase + attr * 16);
+ }
+
+ if (context.Config.Stage == ShaderStage.Vertex)
+ {
+ DeclareOutputAttribute(context, AttributeConsts.PositionX);
+ }
+ }
+ }
+
+ private static void DeclareOutputAttribute(CodeGenContext context, int attr)
+ {
+ if (context.Config.TransformFeedbackEnabled)
+ {
+ throw new NotImplementedException();
+ }
+ else
+ {
+ DeclareInputOrOutput(context, attr, true);
+ }
+ }
+
+ public static void DeclareInvocationId(CodeGenContext context)
+ {
+ DeclareInputOrOutput(context, AttributeConsts.LaneId, false);
+ }
+
+ private static void DeclareInputOrOutput(CodeGenContext context, int attr, bool isOutAttr, PixelImap iq = PixelImap.Unused)
+ {
+ var dict = isOutAttr ? context.Outputs : context.Inputs;
+ var attrInfo = AttributeInfo.From(context.Config, attr);
+
+ if (attrInfo.BaseValue == AttributeConsts.PositionX && context.Config.Stage != ShaderStage.Fragment)
+ {
+ isOutAttr = true;
+ dict = context.Outputs;
+ }
+
+ if (dict.ContainsKey(attrInfo.BaseValue))
+ {
+ return;
+ }
+
+ var storageClass = isOutAttr ? StorageClass.Output : StorageClass.Input;
+ var spvType = context.TypePointer(storageClass, context.GetType(attrInfo.Type, attrInfo.Length));
+ var spvVar = context.Variable(spvType, storageClass);
+
+ if (attrInfo.IsBuiltin)
+ {
+ context.Decorate(spvVar, Decoration.BuiltIn, (LiteralInteger)GetBuiltIn(context, attrInfo.BaseValue));
+ }
+ else if (attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd)
+ {
+ int location = (attr - AttributeConsts.UserAttributeBase) / 16;
+ context.Decorate(spvVar, Decoration.Location, (LiteralInteger)location);
+
+ if (!isOutAttr)
+ {
+ switch (iq)
+ {
+ case PixelImap.Constant:
+ context.Decorate(spvVar, Decoration.Flat);
+ break;
+ case PixelImap.ScreenLinear:
+ context.Decorate(spvVar, Decoration.NoPerspective);
+ break;
+ }
+ }
+ }
+ else if (attr >= AttributeConsts.FragmentOutputColorBase && attr < AttributeConsts.FragmentOutputColorEnd)
+ {
+ int location = (attr - AttributeConsts.FragmentOutputColorBase) / 16;
+ context.Decorate(spvVar, Decoration.Location, (LiteralInteger)location);
+ }
+
+ context.AddGlobalVariable(spvVar);
+ dict.Add(attrInfo.BaseValue, spvVar);
+ }
+
+ private static BuiltIn GetBuiltIn(CodeGenContext context, int attr)
+ {
+ return attr switch
+ {
+ AttributeConsts.Layer => BuiltIn.Layer,
+ AttributeConsts.ViewportIndex => BuiltIn.ViewportIndex,
+ AttributeConsts.PointSize => BuiltIn.PointSize,
+ AttributeConsts.PositionX => context.Config.Stage == ShaderStage.Fragment ? BuiltIn.FragCoord : BuiltIn.Position,
+ AttributeConsts.ClipDistance0 => BuiltIn.ClipDistance,
+ AttributeConsts.PointCoordX => BuiltIn.PointCoord,
+ AttributeConsts.TessCoordX => BuiltIn.TessCoord,
+ AttributeConsts.InstanceId => BuiltIn.InstanceId,
+ AttributeConsts.VertexId => BuiltIn.VertexId,
+ AttributeConsts.FrontFacing => BuiltIn.FrontFacing,
+ AttributeConsts.FragmentOutputDepth => BuiltIn.FragDepth,
+ AttributeConsts.ThreadKill => BuiltIn.HelperInvocation,
+ AttributeConsts.ThreadIdX => BuiltIn.LocalInvocationId,
+ AttributeConsts.CtaIdX => BuiltIn.WorkgroupId,
+ AttributeConsts.LaneId => BuiltIn.SubgroupLocalInvocationId,
+ AttributeConsts.EqMask => BuiltIn.SubgroupEqMask,
+ AttributeConsts.GeMask => BuiltIn.SubgroupGeMask,
+ AttributeConsts.GtMask => BuiltIn.SubgroupGtMask,
+ AttributeConsts.LeMask => BuiltIn.SubgroupLeMask,
+ AttributeConsts.LtMask => BuiltIn.SubgroupLtMask,
+ _ => throw new ArgumentException($"Invalid attribute number 0x{attr:X}.")
+ };
+ }
+
+ private static string GetStagePrefix(ShaderStage stage)
+ {
+ return StagePrefixes[(int)stage];
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Spirv/EnumConversion.cs b/Ryujinx.Graphics.Shader/CodeGen/Spirv/EnumConversion.cs
new file mode 100644
index 000000000..0ddb42640
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/CodeGen/Spirv/EnumConversion.cs
@@ -0,0 +1,38 @@
+using Ryujinx.Graphics.Shader.StructuredIr;
+using Ryujinx.Graphics.Shader.Translation;
+using System;
+using static Spv.Specification;
+
+namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
+{
+ static class EnumConversion
+ {
+ public static AggregateType Convert(this VariableType type)
+ {
+ return type switch
+ {
+ VariableType.None => AggregateType.Void,
+ VariableType.Bool => AggregateType.Bool,
+ VariableType.F32 => AggregateType.FP32,
+ VariableType.F64 => AggregateType.FP64,
+ VariableType.S32 => AggregateType.S32,
+ VariableType.U32 => AggregateType.U32,
+ _ => throw new ArgumentException($"Invalid variable type \"{type}\".")
+ };
+ }
+
+ public static ExecutionModel Convert(this ShaderStage stage)
+ {
+ return stage switch
+ {
+ ShaderStage.Compute => ExecutionModel.GLCompute,
+ ShaderStage.Vertex => ExecutionModel.Vertex,
+ ShaderStage.TessellationControl => ExecutionModel.TessellationControl,
+ ShaderStage.TessellationEvaluation => ExecutionModel.TessellationEvaluation,
+ ShaderStage.Geometry => ExecutionModel.Geometry,
+ ShaderStage.Fragment => ExecutionModel.Fragment,
+ _ => throw new ArgumentException($"Invalid shader stage \"{stage}\".")
+ };
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs b/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs
new file mode 100644
index 000000000..e2baf5428
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs
@@ -0,0 +1,1867 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.StructuredIr;
+using Ryujinx.Graphics.Shader.Translation;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using static Spv.Specification;
+
+namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
+{
+ using SpvInstruction = Spv.Generator.Instruction;
+ using SpvLiteralInteger = Spv.Generator.LiteralInteger;
+
+ static class Instructions
+ {
+ private static readonly Func[] InstTable;
+
+ static Instructions()
+ {
+ InstTable = new Func[(int)Instruction.Count];
+
+ Add(Instruction.Absolute, GenerateAbsolute);
+ Add(Instruction.Add, GenerateAdd);
+ Add(Instruction.AtomicAdd, GenerateAtomicAdd);
+ Add(Instruction.AtomicAnd, GenerateAtomicAnd);
+ Add(Instruction.AtomicCompareAndSwap, GenerateAtomicCompareAndSwap);
+ Add(Instruction.AtomicMinS32, GenerateAtomicMinS32);
+ Add(Instruction.AtomicMinU32, GenerateAtomicMinU32);
+ Add(Instruction.AtomicMaxS32, GenerateAtomicMaxS32);
+ Add(Instruction.AtomicMaxU32, GenerateAtomicMaxU32);
+ Add(Instruction.AtomicOr, GenerateAtomicOr);
+ Add(Instruction.AtomicSwap, GenerateAtomicSwap);
+ Add(Instruction.AtomicXor, GenerateAtomicXor);
+ Add(Instruction.Ballot, GenerateBallot);
+ Add(Instruction.Barrier, GenerateBarrier);
+ Add(Instruction.BitCount, GenerateBitCount);
+ Add(Instruction.BitfieldExtractS32, GenerateBitfieldExtractS32);
+ Add(Instruction.BitfieldExtractU32, GenerateBitfieldExtractU32);
+ Add(Instruction.BitfieldInsert, GenerateBitfieldInsert);
+ Add(Instruction.BitfieldReverse, GenerateBitfieldReverse);
+ Add(Instruction.BitwiseAnd, GenerateBitwiseAnd);
+ Add(Instruction.BitwiseExclusiveOr, GenerateBitwiseExclusiveOr);
+ Add(Instruction.BitwiseNot, GenerateBitwiseNot);
+ Add(Instruction.BitwiseOr, GenerateBitwiseOr);
+ Add(Instruction.Call, GenerateCall);
+ Add(Instruction.Ceiling, GenerateCeiling);
+ Add(Instruction.Clamp, GenerateClamp);
+ Add(Instruction.ClampU32, GenerateClampU32);
+ Add(Instruction.Comment, GenerateComment);
+ Add(Instruction.CompareEqual, GenerateCompareEqual);
+ Add(Instruction.CompareGreater, GenerateCompareGreater);
+ Add(Instruction.CompareGreaterOrEqual, GenerateCompareGreaterOrEqual);
+ Add(Instruction.CompareGreaterOrEqualU32, GenerateCompareGreaterOrEqualU32);
+ Add(Instruction.CompareGreaterU32, GenerateCompareGreaterU32);
+ Add(Instruction.CompareLess, GenerateCompareLess);
+ Add(Instruction.CompareLessOrEqual, GenerateCompareLessOrEqual);
+ Add(Instruction.CompareLessOrEqualU32, GenerateCompareLessOrEqualU32);
+ Add(Instruction.CompareLessU32, GenerateCompareLessU32);
+ Add(Instruction.CompareNotEqual, GenerateCompareNotEqual);
+ Add(Instruction.ConditionalSelect, GenerateConditionalSelect);
+ Add(Instruction.ConvertFP32ToFP64, GenerateConvertFP32ToFP64);
+ Add(Instruction.ConvertFP64ToFP32, GenerateConvertFP64ToFP32);
+ Add(Instruction.ConvertFP32ToS32, GenerateConvertFPToS32);
+ Add(Instruction.ConvertFP32ToU32, GenerateConvertFPToU32);
+ Add(Instruction.ConvertS32ToFP32, GenerateConvertS32ToFP);
+ Add(Instruction.ConvertU32ToFP32, GenerateConvertU32ToFP);
+ Add(Instruction.Cosine, GenerateCosine);
+ Add(Instruction.Ddx, GenerateDdx);
+ Add(Instruction.Ddy, GenerateDdy);
+ Add(Instruction.Discard, GenerateDiscard);
+ Add(Instruction.Divide, GenerateDivide);
+ Add(Instruction.ExponentB2, GenerateExponentB2);
+ Add(Instruction.FindMSBS32, GenerateFindMSBS32);
+ Add(Instruction.FindMSBU32, GenerateFindMSBU32);
+ Add(Instruction.Floor, GenerateFloor);
+ Add(Instruction.FusedMultiplyAdd, GenerateFusedMultiplyAdd);
+ Add(Instruction.GroupMemoryBarrier, GenerateGroupMemoryBarrier);
+ Add(Instruction.ImageLoad, GenerateImageLoad);
+ Add(Instruction.ImageStore, GenerateImageStore);
+ Add(Instruction.IsNan, GenerateIsNan);
+ Add(Instruction.LoadAttribute, GenerateLoadAttribute);
+ Add(Instruction.LoadConstant, GenerateLoadConstant);
+ Add(Instruction.LoadLocal, GenerateLoadLocal);
+ Add(Instruction.LoadShared, GenerateLoadShared);
+ Add(Instruction.LoadStorage, GenerateLoadStorage);
+ Add(Instruction.Lod, GenerateLod);
+ Add(Instruction.LogarithmB2, GenerateLogarithmB2);
+ Add(Instruction.LogicalAnd, GenerateLogicalAnd);
+ Add(Instruction.LogicalNot, GenerateLogicalNot);
+ Add(Instruction.LogicalOr, GenerateLogicalOr);
+ Add(Instruction.LoopBreak, GenerateLoopBreak);
+ Add(Instruction.Maximum, GenerateMaximum);
+ Add(Instruction.MaximumU32, GenerateMaximumU32);
+ Add(Instruction.MemoryBarrier, GenerateMemoryBarrier);
+ Add(Instruction.Minimum, GenerateMinimum);
+ Add(Instruction.MinimumU32, GenerateMinimumU32);
+ Add(Instruction.Multiply, GenerateMultiply);
+ Add(Instruction.Negate, GenerateNegate);
+ Add(Instruction.PackHalf2x16, GeneratePackHalf2x16);
+ Add(Instruction.ReciprocalSquareRoot, GenerateReciprocalSquareRoot);
+ Add(Instruction.Return, GenerateReturn);
+ Add(Instruction.Round, GenerateRound);
+ Add(Instruction.ShiftLeft, GenerateShiftLeft);
+ Add(Instruction.ShiftRightS32, GenerateShiftRightS32);
+ Add(Instruction.ShiftRightU32, GenerateShiftRightU32);
+ Add(Instruction.Shuffle, GenerateShuffle);
+ Add(Instruction.ShuffleDown, GenerateShuffleDown);
+ Add(Instruction.ShuffleUp, GenerateShuffleUp);
+ Add(Instruction.ShuffleXor, GenerateShuffleXor);
+ Add(Instruction.Sine, GenerateSine);
+ Add(Instruction.SquareRoot, GenerateSquareRoot);
+ Add(Instruction.StoreLocal, GenerateStoreLocal);
+ Add(Instruction.StoreShared, GenerateStoreShared);
+ Add(Instruction.StoreStorage, GenerateStoreStorage);
+ Add(Instruction.Subtract, GenerateSubtract);
+ Add(Instruction.TextureSample, GenerateTextureSample);
+ Add(Instruction.TextureSize, GenerateTextureSize);
+ Add(Instruction.Truncate, GenerateTruncate);
+ Add(Instruction.UnpackHalf2x16, GenerateUnpackHalf2x16);
+ Add(Instruction.VoteAll, GenerateVoteAll);
+ Add(Instruction.VoteAllEqual, GenerateVoteAllEqual);
+ Add(Instruction.VoteAny, GenerateVoteAny);
+ }
+
+ private static void Add(Instruction inst, Func handler)
+ {
+ InstTable[(int)(inst & Instruction.Mask)] = handler;
+ }
+
+ public static OperationResult Generate(CodeGenContext context, AstOperation operation)
+ {
+ var handler = InstTable[(int)(operation.Inst & Instruction.Mask)];
+ if (handler != null)
+ {
+ return handler(context, operation);
+ }
+ else
+ {
+ throw new NotImplementedException(operation.Inst.ToString());
+ }
+ }
+
+ private static OperationResult GenerateAbsolute(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateUnaryExtInst(context, operation, 4, 5);
+ }
+
+ private static OperationResult GenerateAdd(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateBinary(context, operation, context.FAdd, context.IAdd);
+ }
+
+ private static OperationResult GenerateAtomicAdd(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateAtomicMemoryBinary(context, operation, context.AtomicIAdd);
+ }
+
+ private static OperationResult GenerateAtomicAnd(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateAtomicMemoryBinary(context, operation, context.AtomicAnd);
+ }
+
+ private static OperationResult GenerateAtomicCompareAndSwap(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateAtomicMemoryCas(context, operation);
+ }
+
+ private static OperationResult GenerateAtomicMinS32(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateAtomicMemoryBinary(context, operation, context.AtomicSMin);
+ }
+
+ private static OperationResult GenerateAtomicMinU32(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateAtomicMemoryBinary(context, operation, context.AtomicUMin);
+ }
+
+ private static OperationResult GenerateAtomicMaxS32(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateAtomicMemoryBinary(context, operation, context.AtomicSMax);
+ }
+
+ private static OperationResult GenerateAtomicMaxU32(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateAtomicMemoryBinary(context, operation, context.AtomicUMax);
+ }
+
+ private static OperationResult GenerateAtomicOr(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateAtomicMemoryBinary(context, operation, context.AtomicOr);
+ }
+
+ private static OperationResult GenerateAtomicSwap(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateAtomicMemoryBinary(context, operation, context.AtomicExchange);
+ }
+
+ private static OperationResult GenerateAtomicXor(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateAtomicMemoryBinary(context, operation, context.AtomicXor);
+ }
+
+ private static OperationResult GenerateBallot(CodeGenContext context, AstOperation operation)
+ {
+ var source = operation.GetSource(0);
+
+ var uvec4Type = context.TypeVector(context.TypeU32(), 4);
+ var execution = context.Constant(context.TypeU32(), 3); // Subgroup
+
+ var maskVector = context.GroupNonUniformBallot(uvec4Type, execution, context.Get(AggregateType.Bool, source));
+ var mask = context.CompositeExtract(context.TypeU32(), maskVector, (SpvLiteralInteger)0);
+
+ return new OperationResult(AggregateType.U32, mask);
+ }
+
+ private static OperationResult GenerateBarrier(CodeGenContext context, AstOperation operation)
+ {
+ context.ControlBarrier(
+ context.Constant(context.TypeU32(), 2),
+ context.Constant(context.TypeU32(), 2),
+ context.Constant(context.TypeU32(), 264));
+
+ return OperationResult.Invalid;
+ }
+
+ private static OperationResult GenerateBitCount(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateUnaryS32(context, operation, context.BitCount);
+ }
+
+ private static OperationResult GenerateBitfieldExtractS32(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateTernaryS32(context, operation, context.BitFieldSExtract);
+ }
+
+ private static OperationResult GenerateBitfieldExtractU32(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateTernaryS32(context, operation, context.BitFieldUExtract);
+ }
+
+ private static OperationResult GenerateBitfieldInsert(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateQuaternaryS32(context, operation, context.BitFieldInsert);
+ }
+
+ private static OperationResult GenerateBitfieldReverse(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateUnaryS32(context, operation, context.BitReverse);
+ }
+
+ private static OperationResult GenerateBitwiseAnd(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateBinaryS32(context, operation, context.BitwiseAnd);
+ }
+
+ private static OperationResult GenerateBitwiseExclusiveOr(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateBinaryS32(context, operation, context.BitwiseXor);
+ }
+
+ private static OperationResult GenerateBitwiseNot(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateUnaryS32(context, operation, context.Not);
+ }
+
+ private static OperationResult GenerateBitwiseOr(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateBinaryS32(context, operation, context.BitwiseOr);
+ }
+
+ private static OperationResult GenerateCall(CodeGenContext context, AstOperation operation)
+ {
+ AstOperand funcId = (AstOperand)operation.GetSource(0);
+
+ Debug.Assert(funcId.Type == OperandType.Constant);
+
+ (var function, var spvFunc) = context.GetFunction(funcId.Value);
+
+ var args = new SpvInstruction[operation.SourcesCount - 1];
+ var spvLocals = context.GetLocalForArgsPointers(funcId.Value);
+
+ for (int i = 0; i < args.Length; i++)
+ {
+ var operand = (AstOperand)operation.GetSource(i + 1);
+ if (i >= function.InArguments.Length)
+ {
+ args[i] = context.GetLocalPointer(operand);
+ }
+ else
+ {
+ var type = function.GetArgumentType(i).Convert();
+ var value = context.Get(type, operand);
+ var spvLocal = spvLocals[i];
+
+ context.Store(spvLocal, value);
+
+ args[i] = spvLocal;
+ }
+ }
+
+ var retType = function.ReturnType.Convert();
+ var result = context.FunctionCall(context.GetType(retType), spvFunc, args);
+ return new OperationResult(retType, result);
+ }
+
+ private static OperationResult GenerateCeiling(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateUnaryExtInst(context, operation, 9);
+ }
+
+ private static OperationResult GenerateClamp(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateTernaryExtInst(context, operation, 43, 45);
+ }
+
+ private static OperationResult GenerateClampU32(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateTernaryExtInstU32(context, operation, 44);
+ }
+
+ private static OperationResult GenerateComment(CodeGenContext context, AstOperation operation)
+ {
+ return OperationResult.Invalid;
+ }
+
+ private static OperationResult GenerateCompareEqual(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateCompare(context, operation, context.FOrdEqual, context.IEqual);
+ }
+
+ private static OperationResult GenerateCompareGreater(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateCompare(context, operation, context.FOrdGreaterThan, context.SGreaterThan);
+ }
+
+ private static OperationResult GenerateCompareGreaterOrEqual(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateCompare(context, operation, context.FOrdGreaterThanEqual, context.SGreaterThanEqual);
+ }
+
+ private static OperationResult GenerateCompareGreaterOrEqualU32(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateCompareU32(context, operation, context.UGreaterThanEqual);
+ }
+
+ private static OperationResult GenerateCompareGreaterU32(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateCompareU32(context, operation, context.UGreaterThan);
+ }
+
+ private static OperationResult GenerateCompareLess(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateCompare(context, operation, context.FOrdLessThan, context.SLessThan);
+ }
+
+ private static OperationResult GenerateCompareLessOrEqual(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateCompare(context, operation, context.FOrdLessThanEqual, context.SLessThanEqual);
+ }
+
+ private static OperationResult GenerateCompareLessOrEqualU32(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateCompareU32(context, operation, context.ULessThanEqual);
+ }
+
+ private static OperationResult GenerateCompareLessU32(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateCompareU32(context, operation, context.ULessThan);
+ }
+
+ private static OperationResult GenerateCompareNotEqual(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateCompare(context, operation, context.FOrdNotEqual, context.INotEqual);
+ }
+
+ private static OperationResult GenerateConditionalSelect(CodeGenContext context, AstOperation operation)
+ {
+ var src1 = operation.GetSource(0);
+ var src2 = operation.GetSource(1);
+ var src3 = operation.GetSource(2);
+
+ var cond = context.Get(AggregateType.Bool, src1);
+
+ if (operation.Inst.HasFlag(Instruction.FP64))
+ {
+ return new OperationResult(AggregateType.FP64, context.Select(context.TypeFP64(), cond, context.GetFP64(src2), context.GetFP64(src3)));
+ }
+ else if (operation.Inst.HasFlag(Instruction.FP32))
+ {
+ return new OperationResult(AggregateType.FP32, context.Select(context.TypeFP32(), cond, context.GetFP32(src2), context.GetFP32(src3)));
+ }
+ else
+ {
+ return new OperationResult(AggregateType.S32, context.Select(context.TypeS32(), cond, context.GetS32(src2), context.GetS32(src3)));
+ }
+ }
+
+ private static OperationResult GenerateConvertFP32ToFP64(CodeGenContext context, AstOperation operation)
+ {
+ var source = operation.GetSource(0);
+
+ return new OperationResult(AggregateType.FP64, context.FConvert(context.TypeFP64(), context.GetFP32(source)));
+ }
+
+ private static OperationResult GenerateConvertFP64ToFP32(CodeGenContext context, AstOperation operation)
+ {
+ var source = operation.GetSource(0);
+
+ return new OperationResult(AggregateType.FP32, context.FConvert(context.TypeFP32(), context.GetFP64(source)));
+ }
+
+ private static OperationResult GenerateConvertFPToS32(CodeGenContext context, AstOperation operation)
+ {
+ var source = operation.GetSource(0);
+
+ if (operation.Inst.HasFlag(Instruction.FP64))
+ {
+ return new OperationResult(AggregateType.S32, context.ConvertFToS(context.TypeS32(), context.GetFP64(source)));
+ }
+ else
+ {
+ return new OperationResult(AggregateType.S32, context.ConvertFToS(context.TypeS32(), context.GetFP32(source)));
+ }
+ }
+
+ private static OperationResult GenerateConvertFPToU32(CodeGenContext context, AstOperation operation)
+ {
+ var source = operation.GetSource(0);
+
+ if (operation.Inst.HasFlag(Instruction.FP64))
+ {
+ return new OperationResult(AggregateType.U32, context.ConvertFToU(context.TypeU32(), context.GetFP64(source)));
+ }
+ else
+ {
+ return new OperationResult(AggregateType.U32, context.ConvertFToU(context.TypeU32(), context.GetFP32(source)));
+ }
+ }
+
+ private static OperationResult GenerateConvertS32ToFP(CodeGenContext context, AstOperation operation)
+ {
+ var source = operation.GetSource(0);
+
+ if (operation.Inst.HasFlag(Instruction.FP64))
+ {
+ return new OperationResult(AggregateType.FP64, context.ConvertSToF(context.TypeFP64(), context.GetS32(source)));
+ }
+ else
+ {
+ return new OperationResult(AggregateType.FP32, context.ConvertSToF(context.TypeFP32(), context.GetS32(source)));
+ }
+ }
+
+ private static OperationResult GenerateConvertU32ToFP(CodeGenContext context, AstOperation operation)
+ {
+ var source = operation.GetSource(0);
+
+ if (operation.Inst.HasFlag(Instruction.FP64))
+ {
+ return new OperationResult(AggregateType.FP64, context.ConvertUToF(context.TypeFP64(), context.GetU32(source)));
+ }
+ else
+ {
+ return new OperationResult(AggregateType.FP32, context.ConvertUToF(context.TypeFP32(), context.GetU32(source)));
+ }
+ }
+
+ private static OperationResult GenerateCosine(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateUnaryExtInst(context, operation, 14);
+ }
+
+ private static OperationResult GenerateDdx(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateUnaryFP32(context, operation, context.DPdx);
+ }
+
+ private static OperationResult GenerateDdy(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateUnaryFP32(context, operation, context.DPdy);
+ }
+
+ private static OperationResult GenerateDiscard(CodeGenContext context, AstOperation operation)
+ {
+ context.Kill();
+ return OperationResult.Invalid;
+ }
+
+ private static OperationResult GenerateDivide(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateBinary(context, operation, context.FDiv, context.SDiv);
+ }
+
+ private static OperationResult GenerateExponentB2(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateUnaryExtInst(context, operation, 29);
+ }
+
+ private static OperationResult GenerateFindMSBS32(CodeGenContext context, AstOperation operation)
+ {
+ var source = context.GetS32(operation.GetSource(0));
+ return new OperationResult(AggregateType.U32, context.ExtInst(context.TypeU32(), context.ExtSet, 74, source));
+ }
+
+ private static OperationResult GenerateFindMSBU32(CodeGenContext context, AstOperation operation)
+ {
+ var source = context.GetU32(operation.GetSource(0));
+ return new OperationResult(AggregateType.U32, context.ExtInst(context.TypeU32(), context.ExtSet, 75, source));
+ }
+
+ private static OperationResult GenerateFloor(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateUnaryExtInst(context, operation, 8);
+ }
+
+ private static OperationResult GenerateFusedMultiplyAdd(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateTernaryExtInst(context, operation, 50);
+ }
+
+ private static OperationResult GenerateGroupMemoryBarrier(CodeGenContext context, AstOperation operation)
+ {
+ context.MemoryBarrier(context.Constant(context.TypeU32(), 2), context.Constant(context.TypeU32(), 3400));
+ return OperationResult.Invalid;
+ }
+
+ private static OperationResult GenerateImageLoad(CodeGenContext context, AstOperation operation)
+ {
+ AstTextureOperation texOp = (AstTextureOperation)operation;
+
+ bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
+
+ var componentType = texOp.Format.GetComponentType();
+
+ // TODO: Bindless texture support. For now we just return 0/do nothing.
+ if (isBindless)
+ {
+ var zero = componentType switch
+ {
+ VariableType.S32 => context.Constant(context.TypeS32(), 0),
+ VariableType.U32 => context.Constant(context.TypeU32(), 0u),
+ _ => context.Constant(context.TypeFP32(), 0f),
+ };
+
+ return new OperationResult(componentType.Convert(), zero);
+ }
+
+ bool isArray = (texOp.Type & SamplerType.Array) != 0;
+ bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
+
+ int srcIndex = isBindless ? 1 : 0;
+
+ SpvInstruction Src(AggregateType type)
+ {
+ return context.Get(type, texOp.GetSource(srcIndex++));
+ }
+
+ SpvInstruction index = null;
+
+ if (isIndexed)
+ {
+ index = Src(AggregateType.S32);
+ }
+
+ int coordsCount = texOp.Type.GetDimensions();
+
+ int pCount = coordsCount + (isArray ? 1 : 0);
+
+ SpvInstruction pCoords;
+
+ if (pCount > 1)
+ {
+ SpvInstruction[] elems = new SpvInstruction[pCount];
+
+ for (int i = 0; i < pCount; i++)
+ {
+ elems[i] = Src(AggregateType.S32);
+ }
+
+ var vectorType = context.TypeVector(context.TypeS32(), pCount);
+ pCoords = context.CompositeConstruct(vectorType, elems);
+ }
+ else
+ {
+ pCoords = Src(AggregateType.S32);
+ }
+
+ (var imageType, var imageVariable) = context.Images[new TextureMeta(texOp.CbufSlot, texOp.Handle, texOp.Format, texOp.Type)];
+
+ var image = context.Load(imageType, imageVariable);
+
+ var texel = context.ImageRead(context.TypeVector(context.GetType(componentType.Convert()), 4), image, pCoords);
+ var result = context.CompositeExtract(context.TypeFP32(), texel, (SpvLiteralInteger)texOp.Index);
+
+ return new OperationResult(componentType.Convert(), result);
+ }
+
+ private static OperationResult GenerateImageStore(CodeGenContext context, AstOperation 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 isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
+
+ int srcIndex = isBindless ? 1 : 0;
+
+ SpvInstruction Src(AggregateType type)
+ {
+ return context.Get(type, texOp.GetSource(srcIndex++));
+ }
+
+ SpvInstruction index = null;
+
+ if (isIndexed)
+ {
+ index = Src(AggregateType.S32);
+ }
+
+ int coordsCount = texOp.Type.GetDimensions();
+
+ int pCount = coordsCount + (isArray ? 1 : 0);
+
+ SpvInstruction pCoords;
+
+ if (pCount > 1)
+ {
+ SpvInstruction[] elems = new SpvInstruction[pCount];
+
+ for (int i = 0; i < pCount; i++)
+ {
+ elems[i] = Src(AggregateType.S32);
+ }
+
+ var vectorType = context.TypeVector(context.TypeS32(), pCount);
+ pCoords = context.CompositeConstruct(vectorType, elems);
+ }
+ else
+ {
+ pCoords = Src(AggregateType.S32);
+ }
+
+ var componentType = texOp.Format.GetComponentType();
+
+ const int ComponentsCount = 4;
+
+ SpvInstruction[] cElems = new SpvInstruction[ComponentsCount];
+
+ for (int i = 0; i < ComponentsCount; i++)
+ {
+ if (srcIndex < texOp.SourcesCount)
+ {
+ cElems[i] = Src(componentType.Convert());
+ }
+ else
+ {
+ cElems[i] = componentType switch
+ {
+ VariableType.S32 => context.Constant(context.TypeS32(), 0),
+ VariableType.U32 => context.Constant(context.TypeU32(), 0u),
+ _ => context.Constant(context.TypeFP32(), 0f),
+ };
+ }
+ }
+
+ var texel = context.CompositeConstruct(context.TypeVector(context.GetType(componentType.Convert()), ComponentsCount), cElems);
+
+ (var imageType, var imageVariable) = context.Images[new TextureMeta(texOp.CbufSlot, texOp.Handle, texOp.Format, texOp.Type)];
+
+ var image = context.Load(imageType, imageVariable);
+
+ context.ImageWrite(image, pCoords, texel);
+
+ return OperationResult.Invalid;
+ }
+
+ private static OperationResult GenerateIsNan(CodeGenContext context, AstOperation operation)
+ {
+ var source = operation.GetSource(0);
+
+ SpvInstruction result;
+
+ if (operation.Inst.HasFlag(Instruction.FP64))
+ {
+ result = context.IsNan(context.TypeBool(), context.GetFP64(source));
+ }
+ else
+ {
+ result = context.IsNan(context.TypeBool(), context.GetFP32(source));
+ }
+
+ return new OperationResult(AggregateType.Bool, result);
+ }
+
+ private static OperationResult GenerateLoadAttribute(CodeGenContext context, AstOperation operation)
+ {
+ var src1 = operation.GetSource(0);
+ var src2 = operation.GetSource(1);
+
+ if (src1 is not AstOperand oper || oper.Type != OperandType.Attribute)
+ {
+ throw new InvalidOperationException("First source of LoadAttribute must be a attribute.");
+ }
+
+ return new OperationResult(AggregateType.FP32, context.GetAttribute(AggregateType.FP32, oper, false));
+ }
+
+ private static OperationResult GenerateLoadConstant(CodeGenContext context, AstOperation operation)
+ {
+ var src1 = operation.GetSource(0);
+ var src2 = context.Get(AggregateType.S32, operation.GetSource(1));
+
+ var ubVariable = context.UniformBuffers[((AstOperand)src1).Value];
+ var i0 = context.Constant(context.TypeS32(), 0);
+ var i1 = context.ShiftRightArithmetic(context.TypeS32(), src2, context.Constant(context.TypeS32(), 2));
+ var i2 = context.BitwiseAnd(context.TypeS32(), src2, context.Constant(context.TypeS32(), 3));
+
+ var elemPointer = context.AccessChain(context.TypePointer(StorageClass.Uniform, context.TypeFP32()), ubVariable, i0, i1, i2);
+ var value = context.Load(context.TypeFP32(), elemPointer);
+
+ return new OperationResult(AggregateType.FP32, value);
+ }
+
+ private static OperationResult GenerateLoadLocal(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateLoadLocalOrShared(context, operation, StorageClass.Private, context.LocalMemory);
+ }
+
+ private static OperationResult GenerateLoadShared(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateLoadLocalOrShared(context, operation, StorageClass.Workgroup, context.SharedMemory);
+ }
+
+ private static OperationResult GenerateLoadLocalOrShared(
+ CodeGenContext context,
+ AstOperation operation,
+ StorageClass storageClass,
+ SpvInstruction memory)
+ {
+ var offset = context.Get(AggregateType.S32, operation.GetSource(0));
+
+ var elemPointer = context.AccessChain(context.TypePointer(storageClass, context.TypeU32()), memory, offset);
+ var value = context.Load(context.TypeU32(), elemPointer);
+
+ return new OperationResult(AggregateType.U32, value);
+ }
+
+ private static OperationResult GenerateLoadStorage(CodeGenContext context, AstOperation operation)
+ {
+ var elemPointer = GetStorageElemPointer(context, operation);
+ var value = context.Load(context.TypeU32(), elemPointer);
+
+ return new OperationResult(AggregateType.U32, value);
+ }
+
+ private static OperationResult GenerateLod(CodeGenContext context, AstOperation 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;
+
+ SpvInstruction Src(AggregateType type)
+ {
+ return context.Get(type, texOp.GetSource(srcIndex++));
+ }
+
+ SpvInstruction index = null;
+
+ if (isIndexed)
+ {
+ index = Src(AggregateType.S32);
+ }
+
+ int pCount = texOp.Type.GetDimensions();
+
+ SpvInstruction pCoords;
+
+ if (pCount > 1)
+ {
+ SpvInstruction[] elems = new SpvInstruction[pCount];
+
+ for (int i = 0; i < pCount; i++)
+ {
+ elems[i] = Src(AggregateType.FP32);
+ }
+
+ var vectorType = context.TypeVector(context.TypeFP32(), pCount);
+ pCoords = context.CompositeConstruct(vectorType, elems);
+ }
+ else
+ {
+ pCoords = Src(AggregateType.FP32);
+ }
+
+ var meta = new TextureMeta(texOp.CbufSlot, texOp.Handle, texOp.Format, texOp.Type);
+
+ (_, var sampledImageType, var sampledImageVariable) = context.Samplers[meta];
+
+ var image = context.Load(sampledImageType, sampledImageVariable);
+
+ var resultType = context.TypeVector(context.TypeFP32(), 2);
+ var packed = context.ImageQueryLod(resultType, image, pCoords);
+ var result = context.CompositeExtract(context.TypeFP32(), packed, (SpvLiteralInteger)texOp.Index);
+
+ return new OperationResult(AggregateType.FP32, result);
+ }
+
+ private static OperationResult GenerateLogarithmB2(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateUnaryExtInst(context, operation, 30);
+ }
+
+ private static OperationResult GenerateLogicalAnd(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateBinaryBool(context, operation, context.LogicalAnd);
+ }
+
+ private static OperationResult GenerateLogicalNot(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateUnaryBool(context, operation, context.LogicalNot);
+ }
+
+ private static OperationResult GenerateLogicalOr(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateBinaryBool(context, operation, context.LogicalOr);
+ }
+
+ private static OperationResult GenerateLoopBreak(CodeGenContext context, AstOperation operation)
+ {
+ AstBlock loopBlock = context.CurrentBlock;
+ while (loopBlock.Type != AstBlockType.DoWhile)
+ {
+ loopBlock = loopBlock.Parent;
+ }
+
+ context.Branch(context.GetNextLabel(loopBlock.Parent));
+
+ return OperationResult.Invalid;
+ }
+
+ private static OperationResult GenerateMaximum(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateBinaryExtInst(context, operation, 40, 42);
+ }
+
+ private static OperationResult GenerateMaximumU32(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateBinaryExtInstU32(context, operation, 41);
+ }
+
+ private static OperationResult GenerateMemoryBarrier(CodeGenContext context, AstOperation operation)
+ {
+ context.MemoryBarrier(context.Constant(context.TypeU32(), 1), context.Constant(context.TypeU32(), 3400));
+ return OperationResult.Invalid;
+ }
+
+ private static OperationResult GenerateMinimum(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateBinaryExtInst(context, operation, 37, 39);
+ }
+
+ private static OperationResult GenerateMinimumU32(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateBinaryExtInstU32(context, operation, 38);
+ }
+
+ private static OperationResult GenerateMultiply(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateBinary(context, operation, context.FMul, context.IMul);
+ }
+
+ private static OperationResult GenerateNegate(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateUnary(context, operation, context.FNegate, context.SNegate);
+ }
+
+ private static OperationResult GeneratePackHalf2x16(CodeGenContext context, AstOperation operation)
+ {
+ var value0 = context.GetFP32(operation.GetSource(0));
+ var value1 = context.GetFP32(operation.GetSource(1));
+ var vector = context.CompositeConstruct(context.TypeVector(context.TypeFP32(), 2), value0, value1);
+ var result = context.ExtInst(context.TypeU32(), context.ExtSet, 58, vector);
+
+ return new OperationResult(AggregateType.U32, result);
+ }
+
+ private static OperationResult GenerateReciprocalSquareRoot(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateUnaryExtInst(context, operation, 32);
+ }
+
+ private static OperationResult GenerateReturn(CodeGenContext context, AstOperation operation)
+ {
+ context.Return();
+ return OperationResult.Invalid;
+ }
+
+ private static OperationResult GenerateRound(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateUnaryExtInst(context, operation, 2);
+ }
+
+ private static OperationResult GenerateShiftLeft(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateBinaryS32(context, operation, context.ShiftLeftLogical);
+ }
+
+ private static OperationResult GenerateShiftRightS32(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateBinaryS32(context, operation, context.ShiftRightArithmetic);
+ }
+
+ private static OperationResult GenerateShiftRightU32(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateBinaryS32(context, operation, context.ShiftRightLogical);
+ }
+
+ private static OperationResult GenerateShuffle(CodeGenContext context, AstOperation operation)
+ {
+ var x = context.GetFP32(operation.GetSource(0));
+ var index = context.GetU32(operation.GetSource(1));
+ var mask = context.GetU32(operation.GetSource(2));
+
+ var const31 = context.Constant(context.TypeU32(), 31);
+ var const8 = context.Constant(context.TypeU32(), 8);
+
+ var clamp = context.BitwiseAnd(context.TypeU32(), mask, const31);
+ var segMask = context.BitwiseAnd(context.TypeU32(), context.ShiftRightLogical(context.TypeU32(), mask, const8), const31);
+ var notSegMask = context.Not(context.TypeU32(), segMask);
+ var clampNotSegMask = context.BitwiseAnd(context.TypeU32(), clamp, notSegMask);
+ var indexNotSegMask = context.BitwiseAnd(context.TypeU32(), index, notSegMask);
+
+ var threadId = context.GetAttribute(AggregateType.U32, new AstOperand(OperandType.Attribute, AttributeConsts.LaneId), false);
+
+ var minThreadId = context.BitwiseAnd(context.TypeU32(), threadId, segMask);
+ var maxThreadId = context.BitwiseOr(context.TypeU32(), minThreadId, clampNotSegMask);
+ var srcThreadId = context.BitwiseOr(context.TypeU32(), indexNotSegMask, minThreadId);
+ var valid = context.ULessThanEqual(context.TypeBool(), srcThreadId, maxThreadId);
+ var value = context.SubgroupReadInvocationKHR(context.TypeFP32(), x, srcThreadId);
+ var result = context.Select(context.TypeFP32(), valid, value, x);
+
+ var validLocal = (AstOperand)operation.GetSource(3);
+
+ context.Store(context.GetLocalPointer(validLocal), context.BitcastIfNeeded(validLocal.VarType.Convert(), AggregateType.Bool, valid));
+
+ return new OperationResult(AggregateType.FP32, result);
+ }
+
+ private static OperationResult GenerateShuffleDown(CodeGenContext context, AstOperation operation)
+ {
+ var x = context.GetFP32(operation.GetSource(0));
+ var index = context.GetU32(operation.GetSource(1));
+ var mask = context.GetU32(operation.GetSource(2));
+
+ var const31 = context.Constant(context.TypeU32(), 31);
+ var const8 = context.Constant(context.TypeU32(), 8);
+
+ var clamp = context.BitwiseAnd(context.TypeU32(), mask, const31);
+ var segMask = context.BitwiseAnd(context.TypeU32(), context.ShiftRightLogical(context.TypeU32(), mask, const8), const31);
+ var notSegMask = context.Not(context.TypeU32(), segMask);
+ var clampNotSegMask = context.BitwiseAnd(context.TypeU32(), clamp, notSegMask);
+ var indexNotSegMask = context.BitwiseAnd(context.TypeU32(), index, notSegMask);
+
+ var threadId = context.GetAttribute(AggregateType.U32, new AstOperand(OperandType.Attribute, AttributeConsts.LaneId), false);
+
+ var minThreadId = context.BitwiseAnd(context.TypeU32(), threadId, segMask);
+ var maxThreadId = context.BitwiseOr(context.TypeU32(), minThreadId, clampNotSegMask);
+ var srcThreadId = context.IAdd(context.TypeU32(), threadId, index);
+ var valid = context.ULessThanEqual(context.TypeBool(), srcThreadId, maxThreadId);
+ var value = context.SubgroupReadInvocationKHR(context.TypeFP32(), x, srcThreadId);
+ var result = context.Select(context.TypeFP32(), valid, value, x);
+
+ var validLocal = (AstOperand)operation.GetSource(3);
+
+ context.Store(context.GetLocalPointer(validLocal), context.BitcastIfNeeded(validLocal.VarType.Convert(), AggregateType.Bool, valid));
+
+ return new OperationResult(AggregateType.FP32, result);
+ }
+
+ private static OperationResult GenerateShuffleUp(CodeGenContext context, AstOperation operation)
+ {
+ var x = context.GetFP32(operation.GetSource(0));
+ var index = context.GetU32(operation.GetSource(1));
+ var mask = context.GetU32(operation.GetSource(2));
+
+ var const31 = context.Constant(context.TypeU32(), 31);
+ var const8 = context.Constant(context.TypeU32(), 8);
+
+ var clamp = context.BitwiseAnd(context.TypeU32(), mask, const31);
+ var segMask = context.BitwiseAnd(context.TypeU32(), context.ShiftRightLogical(context.TypeU32(), mask, const8), const31);
+ var notSegMask = context.Not(context.TypeU32(), segMask);
+ var clampNotSegMask = context.BitwiseAnd(context.TypeU32(), clamp, notSegMask);
+ var indexNotSegMask = context.BitwiseAnd(context.TypeU32(), index, notSegMask);
+
+ var threadId = context.GetAttribute(AggregateType.U32, new AstOperand(OperandType.Attribute, AttributeConsts.LaneId), false);
+
+ var minThreadId = context.BitwiseAnd(context.TypeU32(), threadId, segMask);
+ var srcThreadId = context.ISub(context.TypeU32(), threadId, index);
+ var valid = context.UGreaterThanEqual(context.TypeBool(), srcThreadId, minThreadId);
+ var value = context.SubgroupReadInvocationKHR(context.TypeFP32(), x, srcThreadId);
+ var result = context.Select(context.TypeFP32(), valid, value, x);
+
+ var validLocal = (AstOperand)operation.GetSource(3);
+
+ context.Store(context.GetLocalPointer(validLocal), context.BitcastIfNeeded(validLocal.VarType.Convert(), AggregateType.Bool, valid));
+
+ return new OperationResult(AggregateType.FP32, result);
+ }
+
+ private static OperationResult GenerateShuffleXor(CodeGenContext context, AstOperation operation)
+ {
+ var x = context.GetFP32(operation.GetSource(0));
+ var index = context.GetU32(operation.GetSource(1));
+ var mask = context.GetU32(operation.GetSource(2));
+
+ var const31 = context.Constant(context.TypeU32(), 31);
+ var const8 = context.Constant(context.TypeU32(), 8);
+
+ var clamp = context.BitwiseAnd(context.TypeU32(), mask, const31);
+ var segMask = context.BitwiseAnd(context.TypeU32(), context.ShiftRightLogical(context.TypeU32(), mask, const8), const31);
+ var notSegMask = context.Not(context.TypeU32(), segMask);
+ var clampNotSegMask = context.BitwiseAnd(context.TypeU32(), clamp, notSegMask);
+ var indexNotSegMask = context.BitwiseAnd(context.TypeU32(), index, notSegMask);
+
+ var threadId = context.GetAttribute(AggregateType.U32, new AstOperand(OperandType.Attribute, AttributeConsts.LaneId), false);
+
+ var minThreadId = context.BitwiseAnd(context.TypeU32(), threadId, segMask);
+ var maxThreadId = context.BitwiseOr(context.TypeU32(), minThreadId, clampNotSegMask);
+ var srcThreadId = context.BitwiseXor(context.TypeU32(), threadId, index);
+ var valid = context.ULessThanEqual(context.TypeBool(), srcThreadId, maxThreadId);
+ var value = context.SubgroupReadInvocationKHR(context.TypeFP32(), x, srcThreadId);
+ var result = context.Select(context.TypeFP32(), valid, value, x);
+
+ var validLocal = (AstOperand)operation.GetSource(3);
+
+ context.Store(context.GetLocalPointer(validLocal), context.BitcastIfNeeded(validLocal.VarType.Convert(), AggregateType.Bool, valid));
+
+ return new OperationResult(AggregateType.FP32, result);
+ }
+
+ private static OperationResult GenerateSine(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateUnaryExtInst(context, operation, 13);
+ }
+
+ private static OperationResult GenerateSquareRoot(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateUnaryExtInst(context, operation, 31);
+ }
+
+ private static OperationResult GenerateStoreLocal(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateStoreLocalOrShared(context, operation, StorageClass.Private, context.LocalMemory);
+ }
+
+ private static OperationResult GenerateStoreShared(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateStoreLocalOrShared(context, operation, StorageClass.Workgroup, context.SharedMemory);
+ }
+
+ private static OperationResult GenerateStoreLocalOrShared(
+ CodeGenContext context,
+ AstOperation operation,
+ StorageClass storageClass,
+ SpvInstruction memory)
+ {
+ var offset = context.Get(AggregateType.S32, operation.GetSource(0));
+ var value = context.Get(AggregateType.U32, operation.GetSource(1));
+
+ var elemPointer = context.AccessChain(context.TypePointer(storageClass, context.TypeU32()), memory, offset);
+ context.Store(elemPointer, value);
+
+ return OperationResult.Invalid;
+ }
+
+ private static OperationResult GenerateStoreStorage(CodeGenContext context, AstOperation operation)
+ {
+ var elemPointer = GetStorageElemPointer(context, operation);
+ context.Store(elemPointer, context.Get(AggregateType.U32, operation.GetSource(2)));
+
+ return OperationResult.Invalid;
+ }
+
+ private static OperationResult GenerateSubtract(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateBinary(context, operation, context.FSub, context.ISub);
+ }
+
+ private static OperationResult GenerateTextureSample(CodeGenContext context, AstOperation operation)
+ {
+ AstTextureOperation texOp = (AstTextureOperation)operation;
+
+ bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
+ bool isGather = (texOp.Flags & TextureFlags.Gather) != 0;
+ bool hasDerivatives = (texOp.Flags & TextureFlags.Derivatives) != 0;
+ bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
+ bool hasLodBias = (texOp.Flags & TextureFlags.LodBias) != 0;
+ bool hasLodLevel = (texOp.Flags & TextureFlags.LodLevel) != 0;
+ bool hasOffset = (texOp.Flags & TextureFlags.Offset) != 0;
+ bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0;
+
+ bool isArray = (texOp.Type & SamplerType.Array) != 0;
+ bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
+ bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0;
+ bool isShadow = (texOp.Type & SamplerType.Shadow) != 0;
+
+ // TODO: Bindless texture support. For now we just return 0.
+ if (isBindless)
+ {
+ return new OperationResult(AggregateType.FP32, context.Constant(context.TypeFP32(), 0f));
+ }
+
+ // This combination is valid, but not available on GLSL.
+ // For now, ignore the LOD level and do a normal sample.
+ // TODO: How to implement it properly?
+ if (hasLodLevel && isArray && isShadow)
+ {
+ hasLodLevel = false;
+ }
+
+ int srcIndex = isBindless ? 1 : 0;
+
+ SpvInstruction Src(AggregateType type)
+ {
+ return context.Get(type, texOp.GetSource(srcIndex++));
+ }
+
+ SpvInstruction index = null;
+
+ if (isIndexed)
+ {
+ index = Src(AggregateType.S32);
+ }
+
+ int coordsCount = texOp.Type.GetDimensions();
+
+ int pCount = coordsCount;
+
+ int arrayIndexElem = -1;
+
+ if (isArray)
+ {
+ arrayIndexElem = pCount++;
+ }
+
+ AggregateType coordType = intCoords ? AggregateType.S32 : AggregateType.FP32;
+
+ SpvInstruction AssemblePVector(int count)
+ {
+ if (count > 1)
+ {
+ SpvInstruction[] elems = new SpvInstruction[count];
+
+ for (int index = 0; index < count; index++)
+ {
+ if (arrayIndexElem == index)
+ {
+ elems[index] = Src(AggregateType.S32);
+
+ if (!intCoords)
+ {
+ elems[index] = context.ConvertSToF(context.TypeFP32(), elems[index]);
+ }
+ }
+ else
+ {
+ elems[index] = Src(coordType);
+ }
+ }
+
+ var vectorType = context.TypeVector(intCoords ? context.TypeS32() : context.TypeFP32(), count);
+ return context.CompositeConstruct(vectorType, elems);
+ }
+ else
+ {
+ return Src(coordType);
+ }
+ }
+
+ SpvInstruction pCoords = AssemblePVector(pCount);
+
+ SpvInstruction AssembleDerivativesVector(int count)
+ {
+ if (count > 1)
+ {
+ SpvInstruction[] elems = new SpvInstruction[count];
+
+ for (int index = 0; index < count; index++)
+ {
+ elems[index] = Src(AggregateType.FP32);
+ }
+
+ var vectorType = context.TypeVector(context.TypeFP32(), count);
+ return context.CompositeConstruct(vectorType, elems);
+ }
+ else
+ {
+ return Src(AggregateType.FP32);
+ }
+ }
+
+ SpvInstruction dRef = null;
+
+ if (isShadow)
+ {
+ dRef = Src(AggregateType.FP32);
+ }
+
+ SpvInstruction[] derivatives = null;
+
+ if (hasDerivatives)
+ {
+ derivatives = new[]
+ {
+ AssembleDerivativesVector(coordsCount), // dPdx
+ AssembleDerivativesVector(coordsCount) // dPdy
+ };
+ }
+
+ SpvInstruction sample = null;
+ SpvInstruction lod = null;
+
+ if (isMultisample)
+ {
+ sample = Src(AggregateType.S32);
+ }
+ else if (hasLodLevel)
+ {
+ lod = Src(coordType);
+ }
+
+ SpvInstruction AssembleOffsetVector(int count)
+ {
+ if (count > 1)
+ {
+ SpvInstruction[] elems = new SpvInstruction[count];
+
+ for (int index = 0; index < count; index++)
+ {
+ elems[index] = Src(AggregateType.S32);
+ }
+
+ var vectorType = context.TypeVector(context.TypeS32(), count);
+ return context.CompositeConstruct(vectorType, elems);
+ }
+ else
+ {
+ return Src(AggregateType.S32);
+ }
+ }
+
+ SpvInstruction[] offsets = null;
+
+ if (hasOffset)
+ {
+ offsets = new[] { AssembleOffsetVector(coordsCount) };
+ }
+ else if (hasOffsets)
+ {
+ offsets = new[]
+ {
+ AssembleOffsetVector(coordsCount),
+ AssembleOffsetVector(coordsCount),
+ AssembleOffsetVector(coordsCount),
+ AssembleOffsetVector(coordsCount)
+ };
+ }
+
+ SpvInstruction lodBias = null;
+
+ if (hasLodBias)
+ {
+ lodBias = Src(AggregateType.FP32);
+ }
+
+ SpvInstruction compIdx = null;
+
+ // textureGather* optional extra component index,
+ // not needed for shadow samplers.
+ if (isGather && !isShadow)
+ {
+ compIdx = Src(AggregateType.S32);
+ }
+
+ var operandsList = new List();
+ var operandsMask = ImageOperandsMask.MaskNone;
+
+ if (hasLodBias)
+ {
+ operandsMask |= ImageOperandsMask.Bias;
+ operandsList.Add(lodBias);
+ }
+
+ if (hasLodLevel)
+ {
+ operandsMask |= ImageOperandsMask.Lod;
+ operandsList.Add(lod);
+ }
+
+ if (hasDerivatives)
+ {
+ operandsMask |= ImageOperandsMask.Grad;
+ operandsList.Add(derivatives[0]);
+ operandsList.Add(derivatives[1]);
+ }
+
+ if (hasOffset)
+ {
+ operandsMask |= ImageOperandsMask.ConstOffset;
+ operandsList.Add(offsets[0]);
+ }
+ else if (hasOffsets)
+ {
+ operandsMask |= ImageOperandsMask.ConstOffsets;
+ operandsList.Add(offsets[0]);
+ operandsList.Add(offsets[1]);
+ operandsList.Add(offsets[2]);
+ operandsList.Add(offsets[3]);
+ }
+
+ if (isMultisample)
+ {
+ operandsMask |= ImageOperandsMask.Sample;
+ operandsList.Add(sample);
+ }
+
+ bool colorIsVector = isGather || !isShadow;
+ var resultType = colorIsVector ? context.TypeVector(context.TypeFP32(), 4) : context.TypeFP32();
+
+ var meta = new TextureMeta(texOp.CbufSlot, texOp.Handle, texOp.Format, texOp.Type);
+
+ (var imageType, var sampledImageType, var sampledImageVariable) = context.Samplers[meta];
+
+ var image = context.Load(sampledImageType, sampledImageVariable);
+
+ if (intCoords)
+ {
+ image = context.Image(imageType, image);
+ }
+
+ var operands = operandsList.ToArray();
+
+ SpvInstruction result;
+
+ if (intCoords)
+ {
+ result = context.ImageFetch(resultType, image, pCoords, operandsMask, operands);
+ }
+ else if (isGather)
+ {
+ if (isShadow)
+ {
+ result = context.ImageDrefGather(resultType, image, pCoords, dRef, operandsMask, operands);
+ }
+ else
+ {
+ result = context.ImageGather(resultType, image, pCoords, compIdx, operandsMask, operands);
+ }
+ }
+ else if (isShadow)
+ {
+ if (hasLodLevel)
+ {
+ result = context.ImageSampleDrefExplicitLod(resultType, image, pCoords, dRef, operandsMask, operands);
+ }
+ else
+ {
+ result = context.ImageSampleDrefImplicitLod(resultType, image, pCoords, dRef, operandsMask, operands);
+ }
+ }
+ else if (hasDerivatives || hasLodLevel)
+ {
+ result = context.ImageSampleExplicitLod(resultType, image, pCoords, operandsMask, operands);
+ }
+ else
+ {
+ result = context.ImageSampleImplicitLod(resultType, image, pCoords, operandsMask, operands);
+ }
+
+ if (colorIsVector)
+ {
+ result = context.CompositeExtract(context.TypeFP32(), result, (SpvLiteralInteger)texOp.Index);
+ }
+
+ return new OperationResult(AggregateType.FP32, result);
+ }
+
+ private static OperationResult GenerateTextureSize(CodeGenContext context, AstOperation operation)
+ {
+ AstTextureOperation texOp = (AstTextureOperation)operation;
+
+ bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
+
+ // TODO: Bindless texture support. For now we just return 0.
+ if (isBindless)
+ {
+ return new OperationResult(AggregateType.S32, context.Constant(context.TypeS32(), 0));
+ }
+
+ bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
+
+ SpvInstruction index = null;
+
+ if (isIndexed)
+ {
+ index = context.GetS32(texOp.GetSource(0));
+ }
+
+ int lodSrcIndex = isBindless || isIndexed ? 1 : 0;
+
+ var lod = context.GetS32(operation.GetSource(lodSrcIndex));
+
+ var meta = new TextureMeta(texOp.CbufSlot, texOp.Handle, texOp.Format, texOp.Type);
+
+ (_, var sampledImageType, var sampledImageVariable) = context.Samplers[meta];
+
+ var image = context.Load(sampledImageType, sampledImageVariable);
+
+ if (texOp.Index == 3)
+ {
+ return new OperationResult(AggregateType.S32, context.ImageQueryLevels(context.TypeS32(), image));
+ }
+ else
+ {
+ int components = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : texOp.Type.GetDimensions();
+ var resultType = components == 1 ? context.TypeS32() : context.TypeVector(context.TypeS32(), components);
+ var result = context.ImageQuerySizeLod(resultType, image, lod);
+
+ if (components != 1)
+ {
+ result = context.CompositeExtract(context.TypeS32(), result, (SpvLiteralInteger)texOp.Index);
+ }
+
+ return new OperationResult(AggregateType.S32, result);
+ }
+ }
+
+ private static OperationResult GenerateTruncate(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateUnaryExtInst(context, operation, 3);
+ }
+
+ private static OperationResult GenerateUnpackHalf2x16(CodeGenContext context, AstOperation operation)
+ {
+ var value = context.GetU32(operation.GetSource(0));
+ var vector = context.ExtInst(context.TypeVector(context.TypeFP32(), 2), context.ExtSet, 62, value);
+ var result = context.CompositeExtract(context.TypeFP32(), vector, operation.Index);
+
+ return new OperationResult(AggregateType.FP32, result);
+ }
+
+ private static OperationResult GenerateVoteAll(CodeGenContext context, AstOperation operation)
+ {
+ var result = context.SubgroupAllKHR(context.TypeBool(), context.Get(AggregateType.Bool, operation.GetSource(0)));
+ return new OperationResult(AggregateType.Bool, result);
+ }
+
+ private static OperationResult GenerateVoteAllEqual(CodeGenContext context, AstOperation operation)
+ {
+ var result = context.SubgroupAllEqualKHR(context.TypeBool(), context.Get(AggregateType.Bool, operation.GetSource(0)));
+ return new OperationResult(AggregateType.Bool, result);
+ }
+
+ private static OperationResult GenerateVoteAny(CodeGenContext context, AstOperation operation)
+ {
+ var result = context.SubgroupAnyKHR(context.TypeBool(), context.Get(AggregateType.Bool, operation.GetSource(0)));
+ return new OperationResult(AggregateType.Bool, result);
+ }
+
+ private static OperationResult GenerateCompare(
+ CodeGenContext context,
+ AstOperation operation,
+ Func emitF,
+ Func emitI)
+ {
+ var src1 = operation.GetSource(0);
+ var src2 = operation.GetSource(1);
+
+ SpvInstruction result;
+
+ if (operation.Inst.HasFlag(Instruction.FP64))
+ {
+ result = emitF(context.TypeBool(), context.GetFP64(src1), context.GetFP64(src2));
+ }
+ else if (operation.Inst.HasFlag(Instruction.FP32))
+ {
+ result = emitF(context.TypeBool(), context.GetFP32(src1), context.GetFP32(src2));
+ }
+ else
+ {
+ result = emitI(context.TypeBool(), context.GetS32(src1), context.GetS32(src2));
+ }
+
+ return new OperationResult(AggregateType.Bool, result);
+ }
+
+ private static OperationResult GenerateCompareU32(
+ CodeGenContext context,
+ AstOperation operation,
+ Func emitU)
+ {
+ var src1 = operation.GetSource(0);
+ var src2 = operation.GetSource(1);
+
+ var result = emitU(context.TypeBool(), context.GetU32(src1), context.GetU32(src2));
+
+ return new OperationResult(AggregateType.Bool, result);
+ }
+
+ private static OperationResult GenerateAtomicMemoryBinary(
+ CodeGenContext context,
+ AstOperation operation,
+ Func emitU)
+ {
+ var value = context.GetU32(operation.GetSource(2));
+
+ SpvInstruction elemPointer;
+ Instruction mr = operation.Inst & Instruction.MrMask;
+
+ if (mr == Instruction.MrStorage)
+ {
+ elemPointer = GetStorageElemPointer(context, operation);
+ }
+ else if (mr == Instruction.MrShared)
+ {
+ var offset = context.GetU32(operation.GetSource(0));
+ elemPointer = context.AccessChain(context.TypePointer(StorageClass.Workgroup, context.TypeU32()), context.SharedMemory, offset);
+ }
+ else
+ {
+ throw new InvalidOperationException($"Invalid storage class \"{mr}\".");
+ }
+
+ var one = context.Constant(context.TypeU32(), 1);
+ var zero = context.Constant(context.TypeU32(), 0);
+
+ return new OperationResult(AggregateType.U32, emitU(context.TypeU32(), elemPointer, one, zero, value));
+ }
+
+ private static OperationResult GenerateAtomicMemoryCas(CodeGenContext context, AstOperation operation)
+ {
+ var value0 = context.GetU32(operation.GetSource(2));
+ var value1 = context.GetU32(operation.GetSource(3));
+
+ SpvInstruction elemPointer;
+ Instruction mr = operation.Inst & Instruction.MrMask;
+
+ if (mr == Instruction.MrStorage)
+ {
+ elemPointer = GetStorageElemPointer(context, operation);
+ }
+ else if (mr == Instruction.MrShared)
+ {
+ var offset = context.GetU32(operation.GetSource(0));
+ elemPointer = context.AccessChain(context.TypePointer(StorageClass.Workgroup, context.TypeU32()), context.SharedMemory, offset);
+ }
+ else
+ {
+ throw new InvalidOperationException($"Invalid storage class \"{mr}\".");
+ }
+
+ var one = context.Constant(context.TypeU32(), 1);
+ var zero = context.Constant(context.TypeU32(), 0);
+
+ return new OperationResult(AggregateType.U32, context.AtomicCompareExchange(context.TypeU32(), elemPointer, one, zero, zero, value1, value0));
+ }
+
+ private static SpvInstruction GetStorageElemPointer(CodeGenContext context, AstOperation operation)
+ {
+ var sbVariable = context.StorageBuffersArray;
+ var i0 = context.Get(AggregateType.S32, operation.GetSource(0));
+ var i1 = context.Constant(context.TypeS32(), 0);
+ var i2 = context.Get(AggregateType.S32, operation.GetSource(1));
+
+ return context.AccessChain(context.TypePointer(StorageClass.Uniform, context.TypeU32()), sbVariable, i0, i1, i2);
+ }
+
+ private static OperationResult GenerateUnary(
+ CodeGenContext context,
+ AstOperation operation,
+ Func emitF,
+ Func emitI)
+ {
+ var source = operation.GetSource(0);
+
+ if (operation.Inst.HasFlag(Instruction.FP64))
+ {
+ return new OperationResult(AggregateType.FP64, emitF(context.TypeFP64(), context.GetFP64(source)));
+ }
+ else if (operation.Inst.HasFlag(Instruction.FP32))
+ {
+ return new OperationResult(AggregateType.FP32, emitF(context.TypeFP32(), context.GetFP32(source)));
+ }
+ else
+ {
+ return new OperationResult(AggregateType.S32, emitI(context.TypeS32(), context.GetS32(source)));
+ }
+ }
+
+ private static OperationResult GenerateUnaryBool(
+ CodeGenContext context,
+ AstOperation operation,
+ Func emitB)
+ {
+ var source = operation.GetSource(0);
+ return new OperationResult(AggregateType.Bool, emitB(context.TypeBool(), context.Get(AggregateType.Bool, source)));
+ }
+
+ private static OperationResult GenerateUnaryFP32(
+ CodeGenContext context,
+ AstOperation operation,
+ Func emit)
+ {
+ var source = operation.GetSource(0);
+ return new OperationResult(AggregateType.FP32, emit(context.TypeFP32(), context.GetFP32(source)));
+ }
+
+ private static OperationResult GenerateUnaryS32(
+ CodeGenContext context,
+ AstOperation operation,
+ Func emitS)
+ {
+ var source = operation.GetSource(0);
+ return new OperationResult(AggregateType.S32, emitS(context.TypeS32(), context.GetS32(source)));
+ }
+
+ private static OperationResult GenerateUnaryExtInst(CodeGenContext context, AstOperation operation, int instruction)
+ {
+ var source = operation.GetSource(0);
+
+ if (operation.Inst.HasFlag(Instruction.FP64))
+ {
+ return new OperationResult(AggregateType.FP64, context.ExtInst(context.TypeFP64(), context.ExtSet, instruction, context.GetFP64(source)));
+ }
+ else
+ {
+ return new OperationResult(AggregateType.FP32, context.ExtInst(context.TypeFP32(), context.ExtSet, instruction, context.GetFP32(source)));
+ }
+ }
+
+ private static OperationResult GenerateUnaryExtInst(CodeGenContext context, AstOperation operation, int instF, int instS)
+ {
+ var source = operation.GetSource(0);
+
+ if (operation.Inst.HasFlag(Instruction.FP64))
+ {
+ return new OperationResult(AggregateType.FP64, context.ExtInst(context.TypeFP64(), context.ExtSet, instF, context.GetFP64(source)));
+ }
+ else if (operation.Inst.HasFlag(Instruction.FP32))
+ {
+ return new OperationResult(AggregateType.FP32, context.ExtInst(context.TypeFP32(), context.ExtSet, instF, context.GetFP32(source)));
+ }
+ else
+ {
+ return new OperationResult(AggregateType.S32, context.ExtInst(context.TypeS32(), context.ExtSet, instS, context.GetS32(source)));
+ }
+ }
+
+ private static OperationResult GenerateBinary(
+ CodeGenContext context,
+ AstOperation operation,
+ Func emitF,
+ Func emitI)
+ {
+ var src1 = operation.GetSource(0);
+ var src2 = operation.GetSource(1);
+
+ if (operation.Inst.HasFlag(Instruction.FP64))
+ {
+ var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2));
+ context.Decorate(result, Decoration.NoContraction);
+ return new OperationResult(AggregateType.FP64, result);
+ }
+ else if (operation.Inst.HasFlag(Instruction.FP32))
+ {
+ var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2));
+ context.Decorate(result, Decoration.NoContraction);
+ return new OperationResult(AggregateType.FP32, result);
+ }
+ else
+ {
+ return new OperationResult(AggregateType.S32, emitI(context.TypeS32(), context.GetS32(src1), context.GetS32(src2)));
+ }
+ }
+
+ private static OperationResult GenerateBinaryBool(
+ CodeGenContext context,
+ AstOperation operation,
+ Func emitB)
+ {
+ var src1 = operation.GetSource(0);
+ var src2 = operation.GetSource(1);
+
+ return new OperationResult(AggregateType.Bool, emitB(context.TypeBool(), context.Get(AggregateType.Bool, src1), context.Get(AggregateType.Bool, src2)));
+ }
+
+ private static OperationResult GenerateBinaryS32(
+ CodeGenContext context,
+ AstOperation operation,
+ Func emitS)
+ {
+ var src1 = operation.GetSource(0);
+ var src2 = operation.GetSource(1);
+
+ return new OperationResult(AggregateType.S32, emitS(context.TypeS32(), context.GetS32(src1), context.GetS32(src2)));
+ }
+
+ private static OperationResult GenerateBinaryExtInst(CodeGenContext context, AstOperation operation, int instF, int instS)
+ {
+ var src1 = operation.GetSource(0);
+ var src2 = operation.GetSource(1);
+
+ if (operation.Inst.HasFlag(Instruction.FP64))
+ {
+ return new OperationResult(AggregateType.FP64, context.ExtInst(context.TypeFP64(), context.ExtSet, instF, context.GetFP64(src1), context.GetFP64(src2)));
+ }
+ else if (operation.Inst.HasFlag(Instruction.FP32))
+ {
+ return new OperationResult(AggregateType.FP32, context.ExtInst(context.TypeFP32(), context.ExtSet, instF, context.GetFP32(src1), context.GetFP32(src2)));
+ }
+ else
+ {
+ return new OperationResult(AggregateType.S32, context.ExtInst(context.TypeS32(), context.ExtSet, instS, context.GetS32(src1), context.GetS32(src2)));
+ }
+ }
+
+ private static OperationResult GenerateBinaryExtInstU32(CodeGenContext context, AstOperation operation, int instU)
+ {
+ var src1 = operation.GetSource(0);
+ var src2 = operation.GetSource(1);
+
+ return new OperationResult(AggregateType.U32, context.ExtInst(context.TypeU32(), context.ExtSet, instU, context.GetU32(src1), context.GetU32(src2)));
+ }
+
+ private static OperationResult GenerateTernaryS32(
+ CodeGenContext context,
+ AstOperation operation,
+ Func emitS)
+ {
+ var src1 = operation.GetSource(0);
+ var src2 = operation.GetSource(1);
+ var src3 = operation.GetSource(2);
+
+ return new OperationResult(AggregateType.S32, emitS(
+ context.TypeS32(),
+ context.GetS32(src1),
+ context.GetS32(src2),
+ context.GetS32(src3)));
+ }
+
+ private static OperationResult GenerateTernaryExtInst(CodeGenContext context, AstOperation operation, int instF)
+ {
+ var src1 = operation.GetSource(0);
+ var src2 = operation.GetSource(1);
+ var src3 = operation.GetSource(2);
+
+ if (operation.Inst.HasFlag(Instruction.FP64))
+ {
+ return new OperationResult(AggregateType.FP64, context.ExtInst(
+ context.TypeFP64(),
+ context.ExtSet,
+ instF,
+ context.GetFP64(src1),
+ context.GetFP64(src2),
+ context.GetFP64(src3)));
+ }
+ else
+ {
+ return new OperationResult(AggregateType.FP32, context.ExtInst(
+ context.TypeFP32(),
+ context.ExtSet,
+ instF,
+ context.GetFP32(src1),
+ context.GetFP32(src2),
+ context.GetFP32(src3)));
+ }
+ }
+
+ private static OperationResult GenerateTernaryExtInst(CodeGenContext context, AstOperation operation, int instF, int instS)
+ {
+ var src1 = operation.GetSource(0);
+ var src2 = operation.GetSource(1);
+ var src3 = operation.GetSource(2);
+
+ if (operation.Inst.HasFlag(Instruction.FP64))
+ {
+ return new OperationResult(AggregateType.FP64, context.ExtInst(
+ context.TypeFP64(),
+ context.ExtSet,
+ instF,
+ context.GetFP64(src1),
+ context.GetFP64(src2),
+ context.GetFP64(src3)));
+ }
+ else if (operation.Inst.HasFlag(Instruction.FP32))
+ {
+ return new OperationResult(AggregateType.FP32, context.ExtInst(
+ context.TypeFP32(),
+ context.ExtSet,
+ instF,
+ context.GetFP32(src1),
+ context.GetFP32(src2),
+ context.GetFP32(src3)));
+ }
+ else
+ {
+ return new OperationResult(AggregateType.S32, context.ExtInst(
+ context.TypeS32(),
+ context.ExtSet,
+ instS,
+ context.GetS32(src1),
+ context.GetS32(src2),
+ context.GetS32(src3)));
+ }
+ }
+
+ private static OperationResult GenerateTernaryExtInstU32(CodeGenContext context, AstOperation operation, int instU)
+ {
+ var src1 = operation.GetSource(0);
+ var src2 = operation.GetSource(1);
+ var src3 = operation.GetSource(2);
+
+ return new OperationResult(AggregateType.U32, context.ExtInst(
+ context.TypeU32(),
+ context.ExtSet,
+ instU,
+ context.GetU32(src1),
+ context.GetU32(src2),
+ context.GetU32(src3)));
+ }
+
+ private static OperationResult GenerateQuaternaryS32(
+ CodeGenContext context,
+ AstOperation operation,
+ Func emitS)
+ {
+ var src1 = operation.GetSource(0);
+ var src2 = operation.GetSource(1);
+ var src3 = operation.GetSource(2);
+ var src4 = operation.GetSource(3);
+
+ return new OperationResult(AggregateType.S32, emitS(
+ context.TypeS32(),
+ context.GetS32(src1),
+ context.GetS32(src2),
+ context.GetS32(src3),
+ context.GetS32(src4)));
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Spirv/OperationResult.cs b/Ryujinx.Graphics.Shader/CodeGen/Spirv/OperationResult.cs
new file mode 100644
index 000000000..f432f1c41
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/CodeGen/Spirv/OperationResult.cs
@@ -0,0 +1,19 @@
+using Ryujinx.Graphics.Shader.Translation;
+using Spv.Generator;
+
+namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
+{
+ struct OperationResult
+ {
+ public static OperationResult Invalid => new OperationResult(AggregateType.Invalid, null);
+
+ public AggregateType Type { get; }
+ public Instruction Value { get; }
+
+ public OperationResult(AggregateType type, Instruction value)
+ {
+ Type = type;
+ Value = value;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs b/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs
new file mode 100644
index 000000000..786db8b1e
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs
@@ -0,0 +1,275 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using Ryujinx.Graphics.Shader.StructuredIr;
+using Ryujinx.Graphics.Shader.Translation;
+using System;
+using System.Collections.Generic;
+using static Spv.Specification;
+
+namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
+{
+ using SpvInstruction = Spv.Generator.Instruction;
+ using SpvLiteralInteger = Spv.Generator.LiteralInteger;
+
+ static class SpirvGenerator
+ {
+ private const HelperFunctionsMask NeedsInvocationIdMask =
+ HelperFunctionsMask.Shuffle |
+ HelperFunctionsMask.ShuffleDown |
+ HelperFunctionsMask.ShuffleUp |
+ HelperFunctionsMask.ShuffleXor |
+ HelperFunctionsMask.SwizzleAdd;
+
+ public static byte[] Generate(StructuredProgramInfo info, ShaderConfig config)
+ {
+ CodeGenContext context = new CodeGenContext(config);
+
+ context.AddCapability(Capability.GroupNonUniformBallot);
+ context.AddCapability(Capability.ImageBuffer);
+ context.AddCapability(Capability.SampledBuffer);
+ context.AddCapability(Capability.SubgroupBallotKHR);
+ context.AddCapability(Capability.SubgroupVoteKHR);
+ context.AddExtension("SPV_KHR_shader_ballot");
+ context.AddExtension("SPV_KHR_subgroup_vote");
+
+ Declarations.DeclareAll(context, info);
+
+ if ((info.HelperFunctionsMask & NeedsInvocationIdMask) != 0)
+ {
+ Declarations.DeclareInvocationId(context);
+ }
+
+ for (int funcIndex = 0; funcIndex < info.Functions.Count; funcIndex++)
+ {
+ var function = info.Functions[funcIndex];
+ var retType = context.GetType(function.ReturnType.Convert());
+
+ var funcArgs = new SpvInstruction[function.InArguments.Length + function.OutArguments.Length];
+
+ for (int argIndex = 0; argIndex < funcArgs.Length; argIndex++)
+ {
+ var argType = context.GetType(function.GetArgumentType(argIndex).Convert());
+ var argPointerType = context.TypePointer(StorageClass.Function, argType);
+ funcArgs[argIndex] = argPointerType;
+ }
+
+ var funcType = context.TypeFunction(retType, false, funcArgs);
+ var spvFunc = context.Function(retType, FunctionControlMask.MaskNone, funcType);
+
+ context.DeclareFunction(funcIndex, function, spvFunc);
+ }
+
+ for (int funcIndex = 0; funcIndex < info.Functions.Count; funcIndex++)
+ {
+ Generate(context, info, funcIndex);
+ }
+
+ return context.Generate();
+ }
+
+ private static void Generate(CodeGenContext context, StructuredProgramInfo info, int funcIndex)
+ {
+ var function = info.Functions[funcIndex];
+
+ (_, var spvFunc) = context.GetFunction(funcIndex);
+
+ context.AddFunction(spvFunc);
+ context.StartFunction();
+
+ Declarations.DeclareParameters(context, function);
+
+ context.EnterBlock(function.MainBlock);
+
+ Declarations.DeclareLocals(context, function);
+ Declarations.DeclareLocalForArgs(context, info.Functions);
+
+ if (funcIndex == 0)
+ {
+ var v4Type = context.TypeVector(context.TypeFP32(), 4);
+ var zero = context.Constant(context.TypeFP32(), 0f);
+ var one = context.Constant(context.TypeFP32(), 1f);
+
+ // Some games will leave some elements of gl_Position uninitialized,
+ // in those cases, the elements will contain undefined values according
+ // to the spec, but on NVIDIA they seems to be always initialized to (0, 0, 0, 1),
+ // so we do explicit initialization to avoid UB on non-NVIDIA gpus.
+ if (context.Config.Stage == ShaderStage.Vertex)
+ {
+ var elemPointer = context.GetAttributeVectorPointer(new AstOperand(OperandType.Attribute, AttributeConsts.PositionX), true);
+ context.Store(elemPointer, context.CompositeConstruct(v4Type, zero, zero, zero, one));
+ }
+
+ // Ensure that unused attributes are set, otherwise the downstream
+ // compiler may eliminate them.
+ // (Not needed for fragment shader as it is the last stage).
+ if (context.Config.Stage != ShaderStage.Compute &&
+ context.Config.Stage != ShaderStage.Fragment &&
+ !context.Config.GpPassthrough)
+ {
+ for (int attr = 0; attr < Declarations.MaxAttributes; attr++)
+ {
+ if (context.Config.TransformFeedbackEnabled)
+ {
+ throw new NotImplementedException();
+ }
+ else
+ {
+ int currAttr = AttributeConsts.UserAttributeBase + attr * 16;
+ var elemPointer = context.GetAttributeVectorPointer(new AstOperand(OperandType.Attribute, currAttr), true);
+ context.Store(elemPointer, context.CompositeConstruct(v4Type, zero, zero, zero, one));
+ }
+ }
+ }
+ }
+
+ Generate(context, function.MainBlock);
+
+ context.FunctionEnd();
+
+ if (funcIndex == 0)
+ {
+ context.AddEntryPoint(context.Config.Stage.Convert(), spvFunc, "main", context.GetMainInterface());
+
+ if (context.Config.Stage == ShaderStage.Fragment)
+ {
+ context.AddExecutionMode(spvFunc, context.Config.Options.TargetApi == TargetApi.Vulkan
+ ? ExecutionMode.OriginUpperLeft
+ : ExecutionMode.OriginLowerLeft);
+ }
+ else if (context.Config.Stage == ShaderStage.Compute)
+ {
+ var localSizeX = (SpvLiteralInteger)context.Config.GpuAccessor.QueryComputeLocalSizeX();
+ var localSizeY = (SpvLiteralInteger)context.Config.GpuAccessor.QueryComputeLocalSizeY();
+ var localSizeZ = (SpvLiteralInteger)context.Config.GpuAccessor.QueryComputeLocalSizeZ();
+
+ context.AddExecutionMode(
+ spvFunc,
+ ExecutionMode.LocalSize,
+ localSizeX,
+ localSizeY,
+ localSizeZ);
+ }
+ }
+ }
+
+ private static void Generate(CodeGenContext context, AstBlock block)
+ {
+ AstBlockVisitor visitor = new AstBlockVisitor(block);
+
+ var loopTargets = new Dictionary();
+
+ visitor.BlockEntered += (sender, e) =>
+ {
+ AstBlock mergeBlock = e.Block.Parent;
+
+ if (e.Block.Type == AstBlockType.If)
+ {
+ AstBlock ifTrueBlock = e.Block;
+ AstBlock ifFalseBlock;
+
+ if (AstHelper.Next(e.Block) is AstBlock nextBlock && nextBlock.Type == AstBlockType.Else)
+ {
+ ifFalseBlock = nextBlock;
+ }
+ else
+ {
+ ifFalseBlock = mergeBlock;
+ }
+
+ var condition = context.Get(AggregateType.Bool, e.Block.Condition);
+
+ context.SelectionMerge(context.GetNextLabel(mergeBlock), SelectionControlMask.MaskNone);
+ context.BranchConditional(condition, context.GetNextLabel(ifTrueBlock), context.GetNextLabel(ifFalseBlock));
+ }
+ else if (e.Block.Type == AstBlockType.DoWhile)
+ {
+ var continueTarget = context.Label();
+
+ loopTargets.Add(e.Block, (context.NewBlock(), continueTarget));
+
+ context.LoopMerge(context.GetNextLabel(mergeBlock), continueTarget, LoopControlMask.MaskNone);
+ context.Branch(context.GetFirstLabel(e.Block));
+ }
+
+ context.EnterBlock(e.Block);
+ };
+
+ visitor.BlockLeft += (sender, e) =>
+ {
+ if (e.Block.Parent != null)
+ {
+ if (e.Block.Type == AstBlockType.DoWhile)
+ {
+ // This is a loop, we need to jump back to the loop header
+ // if the condition is true.
+ AstBlock mergeBlock = e.Block.Parent;
+
+ (var loopTarget, var continueTarget) = loopTargets[e.Block];
+
+ context.Branch(continueTarget);
+ context.AddLabel(continueTarget);
+
+ var condition = context.Get(AggregateType.Bool, e.Block.Condition);
+
+ context.BranchConditional(condition, loopTarget, context.GetNextLabel(mergeBlock));
+ }
+ else
+ {
+ // We only need a branch if the last instruction didn't
+ // already cause the program to exit or jump elsewhere.
+ bool lastIsCf = e.Block.Last is AstOperation lastOp &&
+ (lastOp.Inst == Instruction.Discard ||
+ lastOp.Inst == Instruction.LoopBreak ||
+ lastOp.Inst == Instruction.Return);
+
+ if (!lastIsCf)
+ {
+ context.Branch(context.GetNextLabel(e.Block.Parent));
+ }
+ }
+
+ bool hasElse = AstHelper.Next(e.Block) is AstBlock nextBlock &&
+ (nextBlock.Type == AstBlockType.Else ||
+ nextBlock.Type == AstBlockType.ElseIf);
+
+ // Re-enter the parent block.
+ if (e.Block.Parent != null && !hasElse)
+ {
+ context.EnterBlock(e.Block.Parent);
+ }
+ }
+ };
+
+ foreach (IAstNode node in visitor.Visit())
+ {
+ if (node is AstAssignment assignment)
+ {
+ var dest = (AstOperand)assignment.Destination;
+
+ if (dest.Type == OperandType.LocalVariable)
+ {
+ var source = context.Get(dest.VarType.Convert(), assignment.Source);
+ context.Store(context.GetLocalPointer(dest), source);
+ }
+ else if (dest.Type == OperandType.Attribute)
+ {
+ var elemPointer = context.GetAttributeElemPointer(dest, true, out var elemType);
+ context.Store(elemPointer, context.Get(elemType, assignment.Source));
+ }
+ else if (dest.Type == OperandType.Argument)
+ {
+ var source = context.Get(dest.VarType.Convert(), assignment.Source);
+ context.Store(context.GetArgumentPointer(dest), source);
+ }
+ else
+ {
+ throw new NotImplementedException(dest.Type.ToString());
+ }
+ }
+ else if (node is AstOperation operation)
+ {
+ Instructions.Generate(context, operation);
+ }
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Spirv/TextureMeta.cs b/Ryujinx.Graphics.Shader/CodeGen/Spirv/TextureMeta.cs
new file mode 100644
index 000000000..7d4aaa436
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/CodeGen/Spirv/TextureMeta.cs
@@ -0,0 +1,35 @@
+using System;
+
+namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
+{
+ struct TextureMeta : IEquatable
+ {
+ public int CbufSlot { get; }
+ public int Handle { get; }
+ public TextureFormat Format { get; }
+ public SamplerType Type { get; }
+
+ public TextureMeta(int cbufSlot, int handle, TextureFormat format, SamplerType type)
+ {
+ CbufSlot = cbufSlot;
+ Handle = handle;
+ Format = format;
+ Type = type;
+ }
+
+ public override bool Equals(object obj)
+ {
+ return obj is TextureMeta other && Equals(other);
+ }
+
+ public bool Equals(TextureMeta other)
+ {
+ return Handle == other.Handle && Type == other.Type;
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(Handle, Type);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/Ryujinx.Graphics.Shader/IGpuAccessor.cs
index 180fc1874..29795cdff 100644
--- a/Ryujinx.Graphics.Shader/IGpuAccessor.cs
+++ b/Ryujinx.Graphics.Shader/IGpuAccessor.cs
@@ -34,6 +34,11 @@ namespace Ryujinx.Graphics.Shader
/// Span of the memory location
ReadOnlySpan GetCode(ulong address, int minimumSize);
+ AttributeType QueryAttributeType(int location)
+ {
+ return AttributeType.Float;
+ }
+
///
/// Queries the binding number of a constant buffer.
///
@@ -218,6 +223,16 @@ namespace Ryujinx.Graphics.Shader
return true;
}
+ float QueryPointSize()
+ {
+ return 1f;
+ }
+
+ bool QueryProgramPointSize()
+ {
+ return true;
+ }
+
///
/// Queries sampler type information.
///
@@ -291,6 +306,11 @@ namespace Ryujinx.Graphics.Shader
return TextureFormat.R8G8B8A8Unorm;
}
+ bool QueryTransformDepthMinusOneToOne()
+ {
+ return false;
+ }
+
///
/// Queries transform feedback enable state.
///
diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/BasicBlock.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/BasicBlock.cs
index 1f7d2b25e..2aca118b7 100644
--- a/Ryujinx.Graphics.Shader/IntermediateRepresentation/BasicBlock.cs
+++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/BasicBlock.cs
@@ -58,5 +58,34 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
{
return Operations.Last?.Value;
}
+
+ public void Append(INode node)
+ {
+ INode lastOp = GetLastOp();
+
+ if (lastOp is Operation operation && IsControlFlowInst(operation.Inst))
+ {
+ Operations.AddBefore(Operations.Last, node);
+ }
+ else
+ {
+ Operations.AddLast(node);
+ }
+ }
+
+ private static bool IsControlFlowInst(Instruction inst)
+ {
+ switch (inst)
+ {
+ case Instruction.Branch:
+ case Instruction.BranchIfFalse:
+ case Instruction.BranchIfTrue:
+ case Instruction.Discard:
+ case Instruction.Return:
+ return true;
+ }
+
+ return false;
+ }
}
}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj b/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj
index 81cc0caf3..7f71f6ae7 100644
--- a/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj
+++ b/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj
@@ -10,6 +10,7 @@
+
diff --git a/Ryujinx.Graphics.Shader/SamplerType.cs b/Ryujinx.Graphics.Shader/SamplerType.cs
index 286ae9d5d..d04b16b38 100644
--- a/Ryujinx.Graphics.Shader/SamplerType.cs
+++ b/Ryujinx.Graphics.Shader/SamplerType.cs
@@ -25,32 +25,28 @@ namespace Ryujinx.Graphics.Shader
{
public static int GetDimensions(this SamplerType type)
{
- switch (type & SamplerType.Mask)
+ return (type & SamplerType.Mask) switch
{
- case SamplerType.Texture1D: return 1;
- case SamplerType.TextureBuffer: return 1;
- case SamplerType.Texture2D: return 2;
- case SamplerType.Texture3D: return 3;
- case SamplerType.TextureCube: return 3;
- }
-
- throw new ArgumentException($"Invalid sampler type \"{type}\".");
+ SamplerType.Texture1D => 1,
+ SamplerType.TextureBuffer => 1,
+ SamplerType.Texture2D => 2,
+ SamplerType.Texture3D => 3,
+ SamplerType.TextureCube => 3,
+ _ => throw new ArgumentException($"Invalid sampler type \"{type}\".")
+ };
}
public static string ToGlslSamplerType(this SamplerType type)
{
- string typeName;
-
- switch (type & SamplerType.Mask)
+ string typeName = (type & SamplerType.Mask) switch
{
- case SamplerType.Texture1D: typeName = "sampler1D"; break;
- case SamplerType.TextureBuffer: typeName = "samplerBuffer"; break;
- case SamplerType.Texture2D: typeName = "sampler2D"; break;
- case SamplerType.Texture3D: typeName = "sampler3D"; break;
- case SamplerType.TextureCube: typeName = "samplerCube"; break;
-
- default: throw new ArgumentException($"Invalid sampler type \"{type}\".");
- }
+ SamplerType.Texture1D => "sampler1D",
+ SamplerType.TextureBuffer => "samplerBuffer",
+ SamplerType.Texture2D => "sampler2D",
+ SamplerType.Texture3D => "sampler3D",
+ SamplerType.TextureCube => "samplerCube",
+ _ => throw new ArgumentException($"Invalid sampler type \"{type}\".")
+ };
if ((type & SamplerType.Multisample) != 0)
{
@@ -72,18 +68,15 @@ namespace Ryujinx.Graphics.Shader
public static string ToGlslImageType(this SamplerType type, VariableType componentType)
{
- string typeName;
-
- switch (type & SamplerType.Mask)
+ string typeName = (type & SamplerType.Mask) switch
{
- case SamplerType.Texture1D: typeName = "image1D"; break;
- case SamplerType.TextureBuffer: typeName = "imageBuffer"; break;
- case SamplerType.Texture2D: typeName = "image2D"; break;
- case SamplerType.Texture3D: typeName = "image3D"; break;
- case SamplerType.TextureCube: typeName = "imageCube"; break;
-
- default: throw new ArgumentException($"Invalid sampler type \"{type}\".");
- }
+ SamplerType.Texture1D => "image1D",
+ SamplerType.TextureBuffer => "imageBuffer",
+ SamplerType.Texture2D => "image2D",
+ SamplerType.Texture3D => "image3D",
+ SamplerType.TextureCube => "imageCube",
+ _ => throw new ArgumentException($"Invalid sampler type \"{type}\".")
+ };
if ((type & SamplerType.Multisample) != 0)
{
diff --git a/Ryujinx.Graphics.Shader/ShaderProgram.cs b/Ryujinx.Graphics.Shader/ShaderProgram.cs
index 29fff21e6..d790c831c 100644
--- a/Ryujinx.Graphics.Shader/ShaderProgram.cs
+++ b/Ryujinx.Graphics.Shader/ShaderProgram.cs
@@ -29,6 +29,7 @@ namespace Ryujinx.Graphics.Shader
public void Prepend(string line)
{
+ System.Console.WriteLine("prepend " + line);
Code = line + Environment.NewLine + Code;
}
}
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstBlock.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstBlock.cs
index fdef87de5..2f34bee83 100644
--- a/Ryujinx.Graphics.Shader/StructuredIr/AstBlock.cs
+++ b/Ryujinx.Graphics.Shader/StructuredIr/AstBlock.cs
@@ -32,6 +32,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
private LinkedList _nodes;
public IAstNode First => _nodes.First?.Value;
+ public IAstNode Last => _nodes.Last?.Value;
public int Count => _nodes.Count;
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs
index c4d8370c2..b71ae2c41 100644
--- a/Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs
+++ b/Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs
@@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
// When debug mode is enabled, we disable expression propagation
// (this makes comparison with the disassembly easier).
- if ((context.Config.Options.Flags & TranslationFlags.DebugMode) == 0)
+ if (!context.Config.Options.Flags.HasFlag(TranslationFlags.DebugMode))
{
AstBlockVisitor visitor = new AstBlockVisitor(mainBlock);
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs b/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs
index 53391b626..541ca298e 100644
--- a/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs
+++ b/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs
@@ -17,7 +17,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{
LinkedListNode nextNode = node.Next;
- if (!(node.Value is PhiNode phi))
+ if (node.Value is not PhiNode phi)
{
node = nextNode;
@@ -32,7 +32,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
Operation copyOp = new Operation(Instruction.Copy, phi.Dest, src);
- AddBeforeBranch(srcBlock, copyOp);
+ srcBlock.Append(copyOp);
}
block.Operations.Remove(node);
@@ -41,34 +41,5 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
}
}
}
-
- private static void AddBeforeBranch(BasicBlock block, INode node)
- {
- INode lastOp = block.GetLastOp();
-
- if (lastOp is Operation operation && IsControlFlowInst(operation.Inst))
- {
- block.Operations.AddBefore(block.Operations.Last, node);
- }
- else
- {
- block.Operations.AddLast(node);
- }
- }
-
- private static bool IsControlFlowInst(Instruction inst)
- {
- switch (inst)
- {
- case Instruction.Branch:
- case Instruction.BranchIfFalse:
- case Instruction.BranchIfTrue:
- case Instruction.Discard:
- case Instruction.Return:
- return true;
- }
-
- return false;
- }
}
}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs
index 2a39d0210..4e3f3e28a 100644
--- a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs
+++ b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs
@@ -277,6 +277,11 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
public AstOperand GetOperandDef(Operand operand)
{
+ if (operand.Type == OperandType.Attribute)
+ {
+ Info.Outputs.Add(operand.Value);
+ }
+
return GetOperand(operand);
}
@@ -288,6 +293,11 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
return GetOperandDef(operand);
}
+ if (operand.Type == OperandType.Attribute)
+ {
+ Info.Inputs.Add(operand.Value);
+ }
+
return GetOperand(operand);
}
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs
index 2dc239643..650349b6f 100644
--- a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs
+++ b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs
@@ -22,6 +22,9 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{
public List Functions { get; }
+ public HashSet Inputs { get; }
+ public HashSet Outputs { get; }
+
public HelperFunctionsMask HelperFunctionsMask { get; set; }
public TransformFeedbackOutput[] TransformFeedbackOutputs { get; }
@@ -30,6 +33,9 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{
Functions = new List();
+ Inputs = new HashSet();
+ Outputs = new HashSet();
+
TransformFeedbackOutputs = new TransformFeedbackOutput[0xc0];
}
}
diff --git a/Ryujinx.Graphics.Shader/Translation/AggregateType.cs b/Ryujinx.Graphics.Shader/Translation/AggregateType.cs
new file mode 100644
index 000000000..dcd1e0bd4
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/Translation/AggregateType.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.Shader.Translation
+{
+ enum AggregateType
+ {
+ Invalid,
+ Void,
+ Bool,
+ FP32,
+ FP64,
+ S32,
+ U32,
+
+ ElementTypeMask = 0xff,
+
+ Vector = 1 << 8,
+ Array = 1 << 9
+ }
+}
diff --git a/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs b/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs
index ada60ab97..0c3ab08e2 100644
--- a/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs
+++ b/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs
@@ -9,6 +9,7 @@ namespace Ryujinx.Graphics.Shader.Translation
public const int TessLevelInner0 = 0x010;
public const int TessLevelInner1 = 0x014;
public const int Layer = 0x064;
+ public const int ViewportIndex = 0x068;
public const int PointSize = 0x06c;
public const int PositionX = 0x070;
public const int PositionY = 0x074;
diff --git a/Ryujinx.Graphics.Shader/Translation/AttributeInfo.cs b/Ryujinx.Graphics.Shader/Translation/AttributeInfo.cs
new file mode 100644
index 000000000..676e49593
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/Translation/AttributeInfo.cs
@@ -0,0 +1,99 @@
+using Ryujinx.Common;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Shader.Translation
+{
+ struct AttributeInfo
+ {
+ private static readonly Dictionary BuiltInAttributes = new Dictionary()
+ {
+ { AttributeConsts.Layer, new AttributeInfo(AttributeConsts.Layer, 1, AggregateType.S32) },
+ { AttributeConsts.ViewportIndex, new AttributeInfo(AttributeConsts.ViewportIndex, 1, AggregateType.S32) },
+ { AttributeConsts.PointSize, new AttributeInfo(AttributeConsts.PointSize, 1, AggregateType.FP32) },
+ { AttributeConsts.PositionX, new AttributeInfo(AttributeConsts.PositionX, 4, AggregateType.Vector | AggregateType.FP32) },
+ { AttributeConsts.PositionY, new AttributeInfo(AttributeConsts.PositionY, 4, AggregateType.Vector | AggregateType.FP32) },
+ { AttributeConsts.PositionZ, new AttributeInfo(AttributeConsts.PositionZ, 4, AggregateType.Vector | AggregateType.FP32) },
+ { AttributeConsts.PositionW, new AttributeInfo(AttributeConsts.PositionW, 4, AggregateType.Vector | AggregateType.FP32) },
+ { AttributeConsts.ClipDistance0, new AttributeInfo(AttributeConsts.ClipDistance0, 8, AggregateType.Array | AggregateType.FP32) },
+ { AttributeConsts.ClipDistance1, new AttributeInfo(AttributeConsts.ClipDistance1, 8, AggregateType.Array | AggregateType.FP32) },
+ { AttributeConsts.ClipDistance2, new AttributeInfo(AttributeConsts.ClipDistance2, 8, AggregateType.Array | AggregateType.FP32) },
+ { AttributeConsts.ClipDistance3, new AttributeInfo(AttributeConsts.ClipDistance3, 8, AggregateType.Array | AggregateType.FP32) },
+ { AttributeConsts.ClipDistance4, new AttributeInfo(AttributeConsts.ClipDistance4, 8, AggregateType.Array | AggregateType.FP32) },
+ { AttributeConsts.ClipDistance5, new AttributeInfo(AttributeConsts.ClipDistance5, 8, AggregateType.Array | AggregateType.FP32) },
+ { AttributeConsts.ClipDistance6, new AttributeInfo(AttributeConsts.ClipDistance6, 8, AggregateType.Array | AggregateType.FP32) },
+ { AttributeConsts.ClipDistance7, new AttributeInfo(AttributeConsts.ClipDistance7, 8, AggregateType.Array | AggregateType.FP32) },
+ { AttributeConsts.PointCoordX, new AttributeInfo(AttributeConsts.PointCoordX, 2, AggregateType.Vector | AggregateType.FP32) },
+ { AttributeConsts.PointCoordY, new AttributeInfo(AttributeConsts.PointCoordY, 2, AggregateType.Vector | AggregateType.FP32) },
+ { AttributeConsts.TessCoordX, new AttributeInfo(AttributeConsts.TessCoordX, 2, AggregateType.Vector | AggregateType.FP32) },
+ { AttributeConsts.TessCoordY, new AttributeInfo(AttributeConsts.TessCoordY, 2, AggregateType.Vector | AggregateType.FP32) },
+ { AttributeConsts.InstanceId, new AttributeInfo(AttributeConsts.InstanceId, 1, AggregateType.S32) },
+ { AttributeConsts.VertexId, new AttributeInfo(AttributeConsts.VertexId, 1, AggregateType.S32) },
+ { AttributeConsts.FrontFacing, new AttributeInfo(AttributeConsts.FrontFacing, 1, AggregateType.Bool) },
+
+ // Special.
+ { AttributeConsts.FragmentOutputDepth, new AttributeInfo(AttributeConsts.FragmentOutputDepth, 1, AggregateType.FP32) },
+ { AttributeConsts.ThreadKill, new AttributeInfo(AttributeConsts.ThreadKill, 1, AggregateType.Bool) },
+ { AttributeConsts.ThreadIdX, new AttributeInfo(AttributeConsts.ThreadIdX, 3, AggregateType.Vector | AggregateType.U32) },
+ { AttributeConsts.ThreadIdY, new AttributeInfo(AttributeConsts.ThreadIdY, 3, AggregateType.Vector | AggregateType.U32) },
+ { AttributeConsts.ThreadIdZ, new AttributeInfo(AttributeConsts.ThreadIdZ, 3, AggregateType.Vector | AggregateType.U32) },
+ { AttributeConsts.CtaIdX, new AttributeInfo(AttributeConsts.CtaIdX, 3, AggregateType.Vector | AggregateType.U32) },
+ { AttributeConsts.CtaIdY, new AttributeInfo(AttributeConsts.CtaIdY, 3, AggregateType.Vector | AggregateType.U32) },
+ { AttributeConsts.CtaIdZ, new AttributeInfo(AttributeConsts.CtaIdZ, 3, AggregateType.Vector | AggregateType.U32) },
+ { AttributeConsts.LaneId, new AttributeInfo(AttributeConsts.LaneId, 1, AggregateType.U32) },
+ { AttributeConsts.EqMask, new AttributeInfo(AttributeConsts.EqMask, 1, AggregateType.U32) },
+ { AttributeConsts.GeMask, new AttributeInfo(AttributeConsts.GeMask, 1, AggregateType.U32) },
+ { AttributeConsts.GtMask, new AttributeInfo(AttributeConsts.GtMask, 1, AggregateType.U32) },
+ { AttributeConsts.LeMask, new AttributeInfo(AttributeConsts.LeMask, 1, AggregateType.U32) },
+ { AttributeConsts.LtMask, new AttributeInfo(AttributeConsts.LtMask, 1, AggregateType.U32) },
+ };
+
+ public int BaseValue { get; }
+ public int Value { get; }
+ public int Length { get; }
+ public AggregateType Type { get; }
+ public bool IsBuiltin { get; }
+ public bool IsValid => Type != AggregateType.Invalid;
+
+ public AttributeInfo(int value, int length, AggregateType type, bool isBuiltin = true)
+ {
+ BaseValue = value & ~(BitUtils.Pow2RoundUp(length) * 4 - 1);
+ Value = value;
+ Length = length;
+ Type = type;
+ IsBuiltin = isBuiltin;
+ }
+
+ public int GetInnermostIndex()
+ {
+ return (Value - BaseValue) / 4;
+ }
+
+ public static AttributeInfo From(ShaderConfig config, int value)
+ {
+ value &= ~3;
+
+ if (value >= AttributeConsts.UserAttributeBase && value < AttributeConsts.UserAttributeEnd)
+ {
+ int location = (value - AttributeConsts.UserAttributeBase) / 16;
+ var elemType = config.GpuAccessor.QueryAttributeType(location) switch
+ {
+ AttributeType.Sint => AggregateType.S32,
+ AttributeType.Uint => AggregateType.U32,
+ _ => AggregateType.FP32
+ };
+
+ return new AttributeInfo(value, 4, AggregateType.Vector | elemType, false);
+ }
+ else if (value >= AttributeConsts.FragmentOutputColorBase && value < AttributeConsts.FragmentOutputColorEnd)
+ {
+ return new AttributeInfo(value, 4, AggregateType.Vector | AggregateType.FP32, false);
+ }
+ else if (BuiltInAttributes.TryGetValue(value, out AttributeInfo info))
+ {
+ return info;
+ }
+
+ return new AttributeInfo(value, 0, AggregateType.Invalid);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs b/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs
index ba3b551d9..d99389727 100644
--- a/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs
+++ b/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs
@@ -30,6 +30,19 @@ namespace Ryujinx.Graphics.Shader.Translation
IsNonMain = isNonMain;
_operations = new List();
_labels = new Dictionary();
+
+ EmitStart();
+ }
+
+ private void EmitStart()
+ {
+ if (Config.Stage == ShaderStage.Vertex &&
+ Config.Options.TargetApi == TargetApi.Vulkan &&
+ (Config.Options.Flags & TranslationFlags.VertexA) == 0)
+ {
+ // Vulkan requires the point size to be always written on the shader if the primitive topology is points.
+ this.Copy(Attribute(AttributeConsts.PointSize), ConstF(Config.GpuAccessor.QueryPointSize()));
+ }
}
public T GetOp() where T : unmanaged
@@ -43,7 +56,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{
Operation operation = new Operation(inst, dest, sources);
- Add(operation);
+ _operations.Add(operation);
return dest;
}
@@ -167,6 +180,15 @@ namespace Ryujinx.Graphics.Shader.Translation
this.Copy(Attribute(AttributeConsts.PositionX), this.FPFusedMultiplyAdd(x, xScale, negativeOne));
this.Copy(Attribute(AttributeConsts.PositionY), this.FPFusedMultiplyAdd(y, yScale, negativeOne));
}
+
+ if (Config.GpuAccessor.QueryTransformDepthMinusOneToOne())
+ {
+ Operand z = Attribute(AttributeConsts.PositionZ);
+ Operand w = Attribute(AttributeConsts.PositionW);
+ Operand halfW = this.FPMultiply(w, ConstF(0.5f));
+
+ this.Copy(Attribute(AttributeConsts.PositionZ), this.FPFusedMultiplyAdd(z, ConstF(0.5f), halfW));
+ }
}
public void PrepareForVertexReturn(out Operand oldXLocal, out Operand oldYLocal, out Operand oldZLocal)
@@ -184,8 +206,15 @@ namespace Ryujinx.Graphics.Shader.Translation
oldYLocal = null;
}
- // Will be used by Vulkan backend for depth mode emulation.
- oldZLocal = null;
+ if (Config.GpuAccessor.QueryTransformDepthMinusOneToOne())
+ {
+ oldZLocal = Local();
+ this.Copy(oldYLocal, Attribute(AttributeConsts.PositionZ | AttributeConsts.LoadOutputMask));
+ }
+ else
+ {
+ oldZLocal = null;
+ }
PrepareForVertexReturn();
}
diff --git a/Ryujinx.Graphics.Shader/Translation/Translator.cs b/Ryujinx.Graphics.Shader/Translation/Translator.cs
index e1614e660..7bddf4590 100644
--- a/Ryujinx.Graphics.Shader/Translation/Translator.cs
+++ b/Ryujinx.Graphics.Shader/Translation/Translator.cs
@@ -1,4 +1,5 @@
using Ryujinx.Graphics.Shader.CodeGen.Glsl;
+using Ryujinx.Graphics.Shader.CodeGen.Spirv;
using Ryujinx.Graphics.Shader.Decoders;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
@@ -72,16 +73,15 @@ namespace Ryujinx.Graphics.Shader.Translation
Ssa.Rename(cfg.Blocks);
Optimizer.RunPass(cfg.Blocks, config);
-
Rewriter.RunPass(cfg.Blocks, config);
}
funcs[i] = new Function(cfg.Blocks, $"fun{i}", false, inArgumentsCount, outArgumentsCount);
}
- StructuredProgramInfo sInfo = StructuredProgram.MakeStructuredProgram(funcs, config);
+ var sInfo = StructuredProgram.MakeStructuredProgram(funcs, config);
- ShaderProgramInfo info = new ShaderProgramInfo(
+ var info = new ShaderProgramInfo(
config.GetConstantBufferDescriptors(),
config.GetStorageBufferDescriptors(),
config.GetTextureDescriptors(),
@@ -95,6 +95,7 @@ namespace Ryujinx.Graphics.Shader.Translation
return config.Options.TargetLanguage switch
{
TargetLanguage.Glsl => new ShaderProgram(info, TargetLanguage.Glsl, GlslGenerator.Generate(sInfo, config)),
+ TargetLanguage.Spirv => new ShaderProgram(info, TargetLanguage.Spirv, SpirvGenerator.Generate(sInfo, config)),
_ => throw new NotImplementedException(config.Options.TargetLanguage.ToString())
};
}
@@ -105,7 +106,7 @@ namespace Ryujinx.Graphics.Shader.Translation
DecodedProgram program;
ulong maxEndAddress = 0;
- if ((options.Flags & TranslationFlags.Compute) != 0)
+ if (options.Flags.HasFlag(TranslationFlags.Compute))
{
config = new ShaderConfig(gpuAccessor, options);
diff --git a/Ryujinx.Graphics.Vulkan/Auto.cs b/Ryujinx.Graphics.Vulkan/Auto.cs
new file mode 100644
index 000000000..d6b1aaab4
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/Auto.cs
@@ -0,0 +1,141 @@
+using System;
+using System.Diagnostics;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ interface IAuto
+ {
+ void IncrementReferenceCount();
+ void DecrementReferenceCount(int cbIndex);
+ void DecrementReferenceCount();
+ }
+
+ interface IAutoPrivate : IAuto
+ {
+ void AddCommandBufferDependencies(CommandBufferScoped cbs);
+ }
+
+ class Auto : IAutoPrivate, IDisposable where T : IDisposable
+ {
+ private int _referenceCount;
+ private T _value;
+
+ private readonly BitMap _cbOwnership;
+ private readonly MultiFenceHolder _waitable;
+ private readonly IAutoPrivate[] _referencedObjs;
+
+ private bool _disposed;
+ private bool _destroyed;
+
+ public Auto(T value)
+ {
+ _referenceCount = 1;
+ _value = value;
+ _cbOwnership = new BitMap(CommandBufferPool.MaxCommandBuffers);
+ }
+
+ public Auto(T value, MultiFenceHolder waitable, params IAutoPrivate[] referencedObjs) : this(value)
+ {
+ _waitable = waitable;
+ _referencedObjs = referencedObjs;
+
+ for (int i = 0; i < referencedObjs.Length; i++)
+ {
+ referencedObjs[i].IncrementReferenceCount();
+ }
+ }
+
+ public T Get(CommandBufferScoped cbs, int offset, int size)
+ {
+ _waitable?.AddBufferUse(cbs.CommandBufferIndex, offset, size);
+ return Get(cbs);
+ }
+
+ public T GetUnsafe()
+ {
+ return _value;
+ }
+
+ public T Get(CommandBufferScoped cbs)
+ {
+ if (!_destroyed)
+ {
+ AddCommandBufferDependencies(cbs);
+ }
+
+ return _value;
+ }
+
+ public void AddCommandBufferDependencies(CommandBufferScoped cbs)
+ {
+ // We don't want to add a reference to this object to the command buffer
+ // more than once, so if we detect that the command buffer already has ownership
+ // of this object, then we can just return without doing anything else.
+ if (_cbOwnership.Set(cbs.CommandBufferIndex))
+ {
+ if (_waitable != null)
+ {
+ cbs.AddWaitable(_waitable);
+ }
+
+ cbs.AddDependant(this);
+
+ // We need to add a dependency on the command buffer to all objects this object
+ // references aswell.
+ if (_referencedObjs != null)
+ {
+ for (int i = 0; i < _referencedObjs.Length; i++)
+ {
+ _referencedObjs[i].AddCommandBufferDependencies(cbs);
+ }
+ }
+ }
+ }
+
+ public void IncrementReferenceCount()
+ {
+ if (_referenceCount == 0)
+ {
+ throw new Exception("Attempted to inc ref of dead object.");
+ }
+ _referenceCount++;
+ }
+
+ public void DecrementReferenceCount(int cbIndex)
+ {
+ _cbOwnership.Clear(cbIndex);
+ DecrementReferenceCount();
+ }
+
+ public void DecrementReferenceCount()
+ {
+ if (--_referenceCount == 0)
+ {
+ _value.Dispose();
+ _value = default;
+ _destroyed = true;
+
+ // Value is no longer in use by the GPU, dispose all other
+ // resources that it references.
+ if (_referencedObjs != null)
+ {
+ for (int i = 0; i < _referencedObjs.Length; i++)
+ {
+ _referencedObjs[i].DecrementReferenceCount();
+ }
+ }
+ }
+
+ Debug.Assert(_referenceCount >= 0);
+ }
+
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ DecrementReferenceCount();
+ _disposed = true;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/BitMap.cs b/Ryujinx.Graphics.Vulkan/BitMap.cs
new file mode 100644
index 000000000..181d6636e
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/BitMap.cs
@@ -0,0 +1,42 @@
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct BitMap
+ {
+ private const int IntSize = 64;
+ private const int IntMask = IntSize - 1;
+
+ private readonly long[] _masks;
+
+ public BitMap(int count)
+ {
+ _masks = new long[(count + IntMask) / IntSize];
+ }
+
+ public bool Set(int bit)
+ {
+ int wordIndex = bit / IntSize;
+ int wordBit = bit & IntMask;
+
+ long wordMask = 1L << wordBit;
+
+ if ((_masks[wordIndex] & wordMask) != 0)
+ {
+ return false;
+ }
+
+ _masks[wordIndex] |= wordMask;
+
+ return true;
+ }
+
+ public void Clear(int bit)
+ {
+ int wordIndex = bit / IntSize;
+ int wordBit = bit & IntMask;
+
+ long wordMask = 1L << wordBit;
+
+ _masks[wordIndex] &= ~wordMask;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Vulkan/BufferHolder.cs b/Ryujinx.Graphics.Vulkan/BufferHolder.cs
new file mode 100644
index 000000000..77ec623e2
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/BufferHolder.cs
@@ -0,0 +1,365 @@
+using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
+using System;
+using System.Runtime.CompilerServices;
+using VkBuffer = Silk.NET.Vulkan.Buffer;
+using VkFormat = Silk.NET.Vulkan.Format;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class BufferHolder : IDisposable
+ {
+ private const int MaxUpdateBufferSize = 0x10000;
+
+ public const AccessFlags DefaultAccessFlags =
+ AccessFlags.AccessShaderReadBit |
+ AccessFlags.AccessShaderWriteBit |
+ AccessFlags.AccessTransferReadBit |
+ AccessFlags.AccessTransferWriteBit |
+ AccessFlags.AccessUniformReadBit |
+ AccessFlags.AccessShaderReadBit |
+ AccessFlags.AccessShaderWriteBit;
+
+ private readonly VulkanGraphicsDevice _gd;
+ private readonly Device _device;
+ private readonly MemoryAllocation _allocation;
+ private readonly Auto _buffer;
+ private readonly Auto _allocationAuto;
+ private readonly ulong _bufferHandle;
+
+ private CacheByRange _cachedConvertedIndexBuffers;
+
+ public int Size { get; }
+
+ private IntPtr _map;
+
+ private readonly MultiFenceHolder _waitable;
+
+ private bool _lastAccessIsWrite;
+
+ public BufferHolder(VulkanGraphicsDevice gd, Device device, VkBuffer buffer, MemoryAllocation allocation, int size)
+ {
+ _gd = gd;
+ _device = device;
+ _allocation = allocation;
+ _allocationAuto = new Auto(allocation);
+ _waitable = new MultiFenceHolder();
+ _buffer = new Auto(new DisposableBuffer(gd.Api, device, buffer), _waitable, _allocationAuto);
+ _bufferHandle = buffer.Handle;
+ Size = size;
+ _map = allocation.HostPointer;
+ }
+
+ public unsafe Auto CreateView(VkFormat format, int offset, int size)
+ {
+ var bufferViewCreateInfo = new BufferViewCreateInfo()
+ {
+ SType = StructureType.BufferViewCreateInfo,
+ Buffer = new VkBuffer(_bufferHandle),
+ Format = format,
+ Offset = (uint)offset,
+ Range = (uint)size
+ };
+
+ _gd.Api.CreateBufferView(_device, bufferViewCreateInfo, null, out var bufferView).ThrowOnError();
+
+ return new Auto(new DisposableBufferView(_gd.Api, _device, bufferView), _waitable, _buffer);
+ }
+
+ public unsafe void InsertBarrier(CommandBuffer commandBuffer, bool isWrite)
+ {
+ // If the last access is write, we always need a barrier to be sure we will read or modify
+ // the correct data.
+ // If the last access is read, and current one is a write, we need to wait until the
+ // read finishes to avoid overwriting data still in use.
+ // Otherwise, if the last access is a read and the current one too, we don't need barriers.
+ bool needsBarrier = isWrite || _lastAccessIsWrite;
+
+ _lastAccessIsWrite = isWrite;
+
+ if (needsBarrier)
+ {
+ MemoryBarrier memoryBarrier = new MemoryBarrier()
+ {
+ SType = StructureType.MemoryBarrier,
+ SrcAccessMask = DefaultAccessFlags,
+ DstAccessMask = DefaultAccessFlags
+ };
+
+ _gd.Api.CmdPipelineBarrier(
+ commandBuffer,
+ PipelineStageFlags.PipelineStageAllCommandsBit,
+ PipelineStageFlags.PipelineStageAllCommandsBit,
+ DependencyFlags.DependencyDeviceGroupBit,
+ 1,
+ memoryBarrier,
+ 0,
+ null,
+ 0,
+ null);
+ }
+ }
+
+ public Auto GetBuffer()
+ {
+ return _buffer;
+ }
+
+ public Auto GetBuffer(CommandBuffer commandBuffer, bool isWrite = false)
+ {
+ if (isWrite)
+ {
+ _cachedConvertedIndexBuffers.Clear();
+ }
+
+ // InsertBarrier(commandBuffer, isWrite);
+ return _buffer;
+ }
+
+ public BufferHandle GetHandle()
+ {
+ var handle = _bufferHandle;
+ return Unsafe.As(ref handle);
+ }
+
+ public unsafe IntPtr Map(int offset, int mappingSize)
+ {
+ return _map;
+ }
+
+ public unsafe ReadOnlySpan GetData(int offset, int size)
+ {
+ return GetDataStorage(offset, size);
+ }
+
+ public unsafe Span GetDataStorage(int offset, int size)
+ {
+ int mappingSize = Math.Min(size, Size - offset);
+
+ if (_map != IntPtr.Zero)
+ {
+ return new Span((void*)(_map + offset), mappingSize);
+ }
+
+ throw new InvalidOperationException("The buffer is not host mapped.");
+ }
+
+ public unsafe void SetData(int offset, ReadOnlySpan data, CommandBufferScoped? cbs = null, Action endRenderPass = null)
+ {
+ int dataSize = Math.Min(data.Length, Size - offset);
+ if (dataSize == 0)
+ {
+ return;
+ }
+
+ bool needsFlush = _gd.CommandBufferPool.HasWaitableOnRentedCommandBuffer(_waitable, offset, dataSize);
+ bool needsWait = needsFlush || MayWait(offset, dataSize);
+
+ if (cbs == null ||
+ !needsWait ||
+ !VulkanConfiguration.UseFastBufferUpdates ||
+ !TryPushData(cbs.Value, endRenderPass, offset, data))
+ {
+ // Some pending command might access the buffer,
+ // so we need to ensure they are all submited to the GPU before waiting.
+ // The flush below forces the submission of all pending commands.
+ if (needsFlush)
+ {
+ _gd.FlushAllCommands();
+ }
+
+ WaitForFences(offset, dataSize);
+
+ if (_map != IntPtr.Zero)
+ {
+ data.Slice(0, dataSize).CopyTo(new Span((void*)(_map + offset), dataSize));
+ }
+ }
+ }
+
+ public unsafe void SetDataUnchecked(int offset, ReadOnlySpan data)
+ {
+ int dataSize = Math.Min(data.Length, Size - offset);
+ if (dataSize == 0)
+ {
+ return;
+ }
+
+ if (_map != IntPtr.Zero)
+ {
+ data.Slice(0, dataSize).CopyTo(new Span((void*)(_map + offset), dataSize));
+ }
+ }
+
+ public void SetDataInline(CommandBufferScoped cbs, Action endRenderPass, int dstOffset, ReadOnlySpan data)
+ {
+ if (!TryPushData(cbs, endRenderPass, dstOffset, data))
+ {
+ throw new ArgumentException($"Invalid offset 0x{dstOffset:X} or data size 0x{data.Length:X}.");
+ }
+ }
+
+ private unsafe bool TryPushData(CommandBufferScoped cbs, Action endRenderPass, int dstOffset, ReadOnlySpan data)
+ {
+ if ((dstOffset & 3) != 0 || (data.Length & 3) != 0)
+ {
+ return false;
+ }
+
+ endRenderPass();
+
+ var dstBuffer = GetBuffer(cbs.CommandBuffer, true).Get(cbs, dstOffset, data.Length).Value;
+
+ InsertBufferBarrier(
+ _gd,
+ cbs.CommandBuffer,
+ dstBuffer,
+ BufferHolder.DefaultAccessFlags,
+ AccessFlags.AccessTransferWriteBit,
+ PipelineStageFlags.PipelineStageAllCommandsBit,
+ PipelineStageFlags.PipelineStageTransferBit,
+ dstOffset,
+ data.Length);
+
+ fixed (byte* pData = data)
+ {
+ for (ulong offset = 0; offset < (ulong)data.Length;)
+ {
+ ulong size = Math.Min(MaxUpdateBufferSize, (ulong)data.Length - offset);
+ _gd.Api.CmdUpdateBuffer(cbs.CommandBuffer, dstBuffer, (ulong)dstOffset + offset, size, pData + offset);
+ offset += size;
+ }
+ }
+
+ InsertBufferBarrier(
+ _gd,
+ cbs.CommandBuffer,
+ dstBuffer,
+ AccessFlags.AccessTransferWriteBit,
+ BufferHolder.DefaultAccessFlags,
+ PipelineStageFlags.PipelineStageTransferBit,
+ PipelineStageFlags.PipelineStageAllCommandsBit,
+ dstOffset,
+ data.Length);
+
+ // Not flushing commands here causes glitches on Intel (driver bug?)
+ if (_gd.Vendor == Vendor.Intel)
+ {
+ _gd.FlushAllCommands();
+ }
+
+ return true;
+ }
+
+ public static unsafe void Copy(
+ VulkanGraphicsDevice gd,
+ CommandBufferScoped cbs,
+ Auto src,
+ Auto dst,
+ int srcOffset,
+ int dstOffset,
+ int size)
+ {
+ var srcBuffer = src.Get(cbs, srcOffset, size).Value;
+ var dstBuffer = dst.Get(cbs, dstOffset, size).Value;
+
+ InsertBufferBarrier(
+ gd,
+ cbs.CommandBuffer,
+ dstBuffer,
+ BufferHolder.DefaultAccessFlags,
+ AccessFlags.AccessTransferWriteBit,
+ PipelineStageFlags.PipelineStageAllCommandsBit,
+ PipelineStageFlags.PipelineStageTransferBit,
+ dstOffset,
+ size);
+
+ var region = new BufferCopy((ulong)srcOffset, (ulong)dstOffset, (ulong)size);
+
+ gd.Api.CmdCopyBuffer(cbs.CommandBuffer, srcBuffer, dstBuffer, 1, ®ion);
+
+ InsertBufferBarrier(
+ gd,
+ cbs.CommandBuffer,
+ dstBuffer,
+ AccessFlags.AccessTransferWriteBit,
+ BufferHolder.DefaultAccessFlags,
+ PipelineStageFlags.PipelineStageTransferBit,
+ PipelineStageFlags.PipelineStageAllCommandsBit,
+ dstOffset,
+ size);
+ }
+
+ public static unsafe void InsertBufferBarrier(
+ VulkanGraphicsDevice gd,
+ CommandBuffer commandBuffer,
+ VkBuffer buffer,
+ AccessFlags srcAccessMask,
+ AccessFlags dstAccessMask,
+ PipelineStageFlags srcStageMask,
+ PipelineStageFlags dstStageMask,
+ int offset,
+ int size)
+ {
+ BufferMemoryBarrier memoryBarrier = new BufferMemoryBarrier()
+ {
+ SType = StructureType.BufferMemoryBarrier,
+ SrcAccessMask = srcAccessMask,
+ DstAccessMask = dstAccessMask,
+ SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
+ DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
+ Buffer = buffer,
+ Offset = (ulong)offset,
+ Size = (ulong)size
+ };
+
+ gd.Api.CmdPipelineBarrier(
+ commandBuffer,
+ srcStageMask,
+ dstStageMask,
+ 0,
+ 0,
+ null,
+ 1,
+ memoryBarrier,
+ 0,
+ null);
+ }
+
+ public void WaitForFences()
+ {
+ _waitable.WaitForFences(_gd.Api, _device);
+ }
+
+ public void WaitForFences(int offset, int size)
+ {
+ _waitable.WaitForFences(_gd.Api, _device, offset, size);
+ }
+
+ public bool MayWait(int offset, int size)
+ {
+ return _waitable.MayWait(_gd.Api, _device, offset, size);
+ }
+
+ public Auto GetBufferI8ToI16(CommandBufferScoped cbs, int offset, int size)
+ {
+ if (!_cachedConvertedIndexBuffers.TryGetValue(offset, size, out var holder))
+ {
+ holder = _gd.BufferManager.Create(_gd, (size * 2 + 3) & ~3);
+
+ _gd.HelperShader.ConvertI8ToI16(_gd, cbs, this, holder, offset, size);
+
+ _cachedConvertedIndexBuffers.Add(offset, size, holder);
+ }
+
+ return holder.GetBuffer();
+ }
+
+ public void Dispose()
+ {
+ _buffer.Dispose();
+ _allocationAuto.Dispose();
+ _cachedConvertedIndexBuffers.Dispose();
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/BufferManager.cs b/Ryujinx.Graphics.Vulkan/BufferManager.cs
new file mode 100644
index 000000000..cf7a90456
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/BufferManager.cs
@@ -0,0 +1,202 @@
+using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using VkFormat = Silk.NET.Vulkan.Format;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class BufferManager : IDisposable
+ {
+ private const MemoryPropertyFlags DefaultBufferMemoryFlags =
+ MemoryPropertyFlags.MemoryPropertyHostVisibleBit |
+ MemoryPropertyFlags.MemoryPropertyHostCoherentBit |
+ MemoryPropertyFlags.MemoryPropertyHostCachedBit;
+
+ private const BufferUsageFlags DefaultBufferUsageFlags =
+ BufferUsageFlags.BufferUsageTransferSrcBit |
+ BufferUsageFlags.BufferUsageTransferDstBit |
+ BufferUsageFlags.BufferUsageUniformTexelBufferBit |
+ BufferUsageFlags.BufferUsageStorageTexelBufferBit |
+ BufferUsageFlags.BufferUsageUniformBufferBit |
+ BufferUsageFlags.BufferUsageStorageBufferBit |
+ BufferUsageFlags.BufferUsageIndexBufferBit |
+ BufferUsageFlags.BufferUsageVertexBufferBit |
+ BufferUsageFlags.BufferUsageTransformFeedbackBufferBitExt;
+
+ private readonly PhysicalDevice _physicalDevice;
+ private readonly Device _device;
+
+ private readonly List _buffers;
+
+ public StagingBuffer StagingBuffer { get; }
+
+ public BufferManager(VulkanGraphicsDevice gd, PhysicalDevice physicalDevice, Device device)
+ {
+ _physicalDevice = physicalDevice;
+ _device = device;
+ _buffers = new List();
+ StagingBuffer = new StagingBuffer(gd, this);
+ }
+
+ public BufferHandle CreateWithHandle(VulkanGraphicsDevice gd, int size)
+ {
+ var holder = Create(gd, size);
+ if (holder == null)
+ {
+ return BufferHandle.Null;
+ }
+
+ ulong handle64 = (ulong)_buffers.Count + 1;
+
+ var handle = Unsafe.As(ref handle64);
+
+ _buffers.Add(holder);
+
+ return handle;
+ }
+
+ public unsafe BufferHolder Create(VulkanGraphicsDevice gd, int size, bool forConditionalRendering = false)
+ {
+ var usage = DefaultBufferUsageFlags;
+
+ if (forConditionalRendering && gd.Capabilities.SupportsConditionalRendering)
+ {
+ usage |= BufferUsageFlags.BufferUsageConditionalRenderingBitExt;
+ }
+ else if (gd.SupportsIndirectParameters)
+ {
+ usage |= BufferUsageFlags.BufferUsageIndirectBufferBit;
+ }
+
+ var bufferCreateInfo = new BufferCreateInfo()
+ {
+ SType = StructureType.BufferCreateInfo,
+ Size = (ulong)size,
+ Usage = usage,
+ SharingMode = SharingMode.Exclusive
+ };
+
+ gd.Api.CreateBuffer(_device, in bufferCreateInfo, null, out var buffer).ThrowOnError();
+ gd.Api.GetBufferMemoryRequirements(_device, buffer, out var requirements);
+ var allocation = gd.MemoryAllocator.AllocateDeviceMemory(_physicalDevice, requirements, DefaultBufferMemoryFlags);
+
+ if (allocation.Memory.Handle == 0UL)
+ {
+ gd.Api.DestroyBuffer(_device, buffer, null);
+ return null;
+ }
+
+ gd.Api.BindBufferMemory(_device, buffer, allocation.Memory, allocation.Offset);
+
+ return new BufferHolder(gd, _device, buffer, allocation, size);
+ }
+
+ public Auto CreateView(BufferHandle handle, VkFormat format, int offset, int size)
+ {
+ if (TryGetBuffer(handle, out var holder))
+ {
+ return holder.CreateView(format, offset, size);
+ }
+
+ return null;
+ }
+
+ public Auto GetBuffer(CommandBuffer commandBuffer, BufferHandle handle, bool isWrite)
+ {
+ if (TryGetBuffer(handle, out var holder))
+ {
+ return holder.GetBuffer(commandBuffer, isWrite);
+ }
+
+ return null;
+ }
+
+ public Auto GetBufferI8ToI16(CommandBufferScoped cbs, BufferHandle handle, int offset, int size)
+ {
+ if (TryGetBuffer(handle, out var holder))
+ {
+ return holder.GetBufferI8ToI16(cbs, offset, size);
+ }
+
+ return null;
+ }
+
+ public Auto GetBuffer(CommandBuffer commandBuffer, BufferHandle handle, bool isWrite, out int size)
+ {
+ if (TryGetBuffer(handle, out var holder))
+ {
+ size = holder.Size;
+ return holder.GetBuffer(commandBuffer, isWrite);
+ }
+
+ size = 0;
+ return null;
+ }
+
+ public ReadOnlySpan GetData(BufferHandle handle, int offset, int size)
+ {
+ if (TryGetBuffer(handle, out var holder))
+ {
+ return holder.GetData(offset, size);
+ }
+
+ return ReadOnlySpan.Empty;
+ }
+
+ public void SetData(BufferHandle handle, int offset, ReadOnlySpan data) where T : unmanaged
+ {
+ SetData(handle, offset, MemoryMarshal.Cast(data), null, null);
+ }
+
+ public void SetData(BufferHandle handle, int offset, ReadOnlySpan data, CommandBufferScoped? cbs, Action endRenderPass)
+ {
+ if (TryGetBuffer(handle, out var holder))
+ {
+ holder.SetData(offset, data, cbs, endRenderPass);
+ }
+ }
+
+ public void Delete(BufferHandle handle)
+ {
+ if (TryGetBuffer(handle, out var holder))
+ {
+ holder.Dispose();
+ // _buffers.Remove(handle);
+ }
+ }
+
+ private bool TryGetBuffer(BufferHandle handle, out BufferHolder holder)
+ {
+ int index = (int)Unsafe.As(ref handle) - 1;
+ if ((uint)index < _buffers.Count)
+ {
+ holder = _buffers[index];
+ return true;
+ }
+
+ holder = default;
+ return false;
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ for (int i = 0; i < _buffers.Count; i++)
+ {
+ _buffers[i].Dispose();
+ }
+
+ StagingBuffer.Dispose();
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/BufferRangeList.cs b/Ryujinx.Graphics.Vulkan/BufferRangeList.cs
new file mode 100644
index 000000000..9cf249fea
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/BufferRangeList.cs
@@ -0,0 +1,147 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct BufferRangeList
+ {
+ private struct Range
+ {
+ public int Offset { get; }
+ public int Size { get; }
+
+ public Range(int offset, int size)
+ {
+ Offset = offset;
+ Size = size;
+ }
+
+ public bool OverlapsWith(int offset, int size)
+ {
+ return Offset < offset + size && offset < Offset + Size;
+ }
+ }
+
+ private List[] _ranges;
+
+ public void Initialize()
+ {
+ _ranges = new List[CommandBufferPool.MaxCommandBuffers];
+ }
+
+ public void Add(int cbIndex, int offset, int size)
+ {
+ var list = _ranges[cbIndex];
+ if (list != null)
+ {
+ int overlapIndex = BinarySearch(list, offset, size);
+ if (overlapIndex >= 0)
+ {
+ while (overlapIndex > 0 && list[overlapIndex - 1].OverlapsWith(offset, size))
+ {
+ overlapIndex--;
+ }
+
+ int endOffset = offset + size;
+ int startIndex = overlapIndex;
+
+ while (overlapIndex < list.Count && list[overlapIndex].OverlapsWith(offset, size))
+ {
+ var currentOverlap = list[overlapIndex];
+ var currentOverlapEndOffset = currentOverlap.Offset + currentOverlap.Size;
+
+ if (offset > currentOverlap.Offset)
+ {
+ offset = currentOverlap.Offset;
+ }
+
+ if (endOffset < currentOverlapEndOffset)
+ {
+ endOffset = currentOverlapEndOffset;
+ }
+
+ overlapIndex++;
+ }
+
+ int count = overlapIndex - startIndex;
+
+ list.RemoveRange(startIndex, count);
+
+ size = endOffset - offset;
+ overlapIndex = startIndex;
+ }
+ else
+ {
+ overlapIndex = ~overlapIndex;
+ }
+
+ list.Insert(overlapIndex, new Range(offset, size));
+
+ int last = 0;
+ foreach (var rg in list)
+ {
+ if (rg.Offset < last)
+ {
+ throw new System.Exception("list not properly sorted");
+ }
+ last = rg.Offset;
+ }
+ }
+ else
+ {
+ list = new List
+ {
+ new Range(offset, size)
+ };
+
+ _ranges[cbIndex] = list;
+ }
+ }
+
+ public bool OverlapsWith(int cbIndex, int offset, int size)
+ {
+ var list = _ranges[cbIndex];
+ if (list == null)
+ {
+ return false;
+ }
+
+ return BinarySearch(list, offset, size) >= 0;
+ }
+
+ private static int BinarySearch(List list, int offset, int size)
+ {
+ int left = 0;
+ int right = list.Count - 1;
+
+ while (left <= right)
+ {
+ int range = right - left;
+
+ int middle = left + (range >> 1);
+
+ var item = list[middle];
+
+ if (item.OverlapsWith(offset, size))
+ {
+ return middle;
+ }
+
+ if (offset < item.Offset)
+ {
+ right = middle - 1;
+ }
+ else
+ {
+ left = middle + 1;
+ }
+ }
+
+ return ~left;
+ }
+
+ public void Clear(int cbIndex)
+ {
+ _ranges[cbIndex] = null;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/BufferState.cs b/Ryujinx.Graphics.Vulkan/BufferState.cs
new file mode 100644
index 000000000..80fc49036
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/BufferState.cs
@@ -0,0 +1,69 @@
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct BufferState : IDisposable
+ {
+ public static BufferState Null => new BufferState(null, 0, 0);
+
+ private readonly Auto _buffer;
+ private readonly int _offset;
+ private readonly int _size;
+ private readonly ulong _stride;
+ private readonly IndexType _type;
+
+ public BufferState(Auto buffer, int offset, int size, IndexType type)
+ {
+ _buffer = buffer;
+ _offset = offset;
+ _size = size;
+ _stride = 0;
+ _type = type;
+ buffer?.IncrementReferenceCount();
+ }
+
+ public BufferState(Auto buffer, int offset, int size, ulong stride = 0UL)
+ {
+ _buffer = buffer;
+ _offset = offset;
+ _size = size;
+ _stride = stride;
+ _type = IndexType.Uint16;
+ buffer?.IncrementReferenceCount();
+ }
+
+ public void BindIndexBuffer(Vk api, CommandBufferScoped cbs)
+ {
+ if (_buffer != null)
+ {
+ api.CmdBindIndexBuffer(cbs.CommandBuffer, _buffer.Get(cbs, _offset, _size).Value, (ulong)_offset, _type);
+ }
+ }
+
+ public void BindTransformFeedbackBuffer(VulkanGraphicsDevice gd, CommandBufferScoped cbs, uint binding)
+ {
+ if (_buffer != null)
+ {
+ var buffer = _buffer.Get(cbs, _offset, _size).Value;
+
+ gd.TransformFeedbackApi.CmdBindTransformFeedbackBuffers(cbs.CommandBuffer, binding, 1, buffer, (ulong)_offset, (ulong)_size);
+ }
+ }
+
+ public void BindVertexBuffer(VulkanGraphicsDevice gd, CommandBufferScoped cbs, uint binding)
+ {
+ if (_buffer != null)
+ {
+ var buffer = _buffer.Get(cbs, _offset, _size).Value;
+
+ gd.Api.CmdBindVertexBuffers(cbs.CommandBuffer, binding, 1, buffer, (ulong)_offset);
+ }
+ }
+
+ public void Dispose()
+ {
+ _buffer?.DecrementReferenceCount();
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/CacheByRange.cs b/Ryujinx.Graphics.Vulkan/CacheByRange.cs
new file mode 100644
index 000000000..f3f503da4
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/CacheByRange.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct CacheByRange where T : IDisposable
+ {
+ private Dictionary _ranges;
+
+ public void Add(int offset, int size, T value)
+ {
+ EnsureInitialized();
+ _ranges.Add(PackRange(offset, size), value);
+ }
+
+ public bool TryGetValue(int offset, int size, out T value)
+ {
+ EnsureInitialized();
+ return _ranges.TryGetValue(PackRange(offset, size), out value);
+ }
+
+ public void Clear()
+ {
+ if (_ranges != null)
+ {
+ foreach (T value in _ranges.Values)
+ {
+ value.Dispose();
+ }
+
+ _ranges.Clear();
+ _ranges = null;
+ }
+ }
+
+ private void EnsureInitialized()
+ {
+ if (_ranges == null)
+ {
+ _ranges = new Dictionary();
+ }
+ }
+
+ private static ulong PackRange(int offset, int size)
+ {
+ return (uint)offset | ((ulong)size << 32);
+ }
+
+ public void Dispose()
+ {
+ Clear();
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs b/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs
new file mode 100644
index 000000000..fdc27e9a0
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs
@@ -0,0 +1,291 @@
+using Silk.NET.Vulkan;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using Thread = System.Threading.Thread;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class CommandBufferPool : IDisposable
+ {
+ public const int MaxCommandBuffers = 8;
+
+ private int _totalCommandBuffers;
+ private int _totalCommandBuffersMask;
+
+ private readonly Vk _api;
+ private readonly Device _device;
+ private readonly Queue _queue;
+ private readonly CommandPool _pool;
+ private readonly Thread _owner;
+
+ public bool OwnedByCurrentThread => _owner == Thread.CurrentThread;
+
+ private struct ReservedCommandBuffer
+ {
+ public bool InUse;
+ public bool InConsumption;
+ public CommandBuffer CommandBuffer;
+ public FenceHolder Fence;
+ public SemaphoreHolder Semaphore;
+
+ public List Dependants;
+ public HashSet Waitables;
+ public HashSet Dependencies;
+
+ public void Initialize(Vk api, Device device, CommandPool pool)
+ {
+ var allocateInfo = new CommandBufferAllocateInfo()
+ {
+ SType = StructureType.CommandBufferAllocateInfo,
+ CommandBufferCount = 1,
+ CommandPool = pool,
+ Level = CommandBufferLevel.Primary
+ };
+
+ api.AllocateCommandBuffers(device, allocateInfo, out CommandBuffer);
+
+ Dependants = new List();
+ Waitables = new HashSet();
+ Dependencies = new HashSet();
+ }
+ }
+
+ private readonly ReservedCommandBuffer[] _commandBuffers;
+
+ private int _cursor;
+
+ public unsafe CommandBufferPool(Vk api, Device device, Queue queue, uint queueFamilyIndex, bool isLight = false)
+ {
+ _api = api;
+ _device = device;
+ _queue = queue;
+ _owner = Thread.CurrentThread;
+
+ var commandPoolCreateInfo = new CommandPoolCreateInfo()
+ {
+ SType = StructureType.CommandPoolCreateInfo,
+ QueueFamilyIndex = queueFamilyIndex,
+ Flags = CommandPoolCreateFlags.CommandPoolCreateTransientBit |
+ CommandPoolCreateFlags.CommandPoolCreateResetCommandBufferBit
+ };
+
+ api.CreateCommandPool(device, commandPoolCreateInfo, null, out _pool).ThrowOnError();
+
+ _totalCommandBuffers = isLight ? 1 : MaxCommandBuffers;
+ _totalCommandBuffersMask = _totalCommandBuffers - 1;
+
+ _commandBuffers = new ReservedCommandBuffer[_totalCommandBuffers];
+
+ for (int i = 0; i < _totalCommandBuffers; i++)
+ {
+ _commandBuffers[i].Initialize(api, device, _pool);
+ }
+ }
+
+ public void AddDependant(int cbIndex, IAuto dependant)
+ {
+ dependant.IncrementReferenceCount();
+ _commandBuffers[cbIndex].Dependants.Add(dependant);
+ }
+
+ public void AddWaitable(MultiFenceHolder waitable)
+ {
+ lock (_commandBuffers)
+ {
+ for (int i = 0; i < _totalCommandBuffers; i++)
+ {
+ ref var entry = ref _commandBuffers[i];
+
+ if (entry.InConsumption)
+ {
+ AddWaitable(i, waitable);
+ }
+ }
+ }
+ }
+
+ public void AddDependency(int cbIndex, CommandBufferScoped dependencyCbs)
+ {
+ Debug.Assert(_commandBuffers[cbIndex].InUse);
+ var semaphoreHolder = _commandBuffers[dependencyCbs.CommandBufferIndex].Semaphore;
+ semaphoreHolder.Get();
+ _commandBuffers[cbIndex].Dependencies.Add(semaphoreHolder);
+ }
+
+ public void AddWaitable(int cbIndex, MultiFenceHolder waitable)
+ {
+ ref var entry = ref _commandBuffers[cbIndex];
+ waitable.AddFence(cbIndex, entry.Fence);
+ entry.Waitables.Add(waitable);
+ }
+
+ public bool HasWaitableOnRentedCommandBuffer(MultiFenceHolder waitable, int offset, int size)
+ {
+ lock (_commandBuffers)
+ {
+ for (int i = 0; i < _totalCommandBuffers; i++)
+ {
+ ref var entry = ref _commandBuffers[i];
+
+ if (entry.InUse &&
+ entry.Waitables.Contains(waitable) &&
+ waitable.IsBufferRangeInUse(i, offset, size))
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public FenceHolder GetFence(int cbIndex)
+ {
+ return _commandBuffers[cbIndex].Fence;
+ }
+
+ public CommandBufferScoped ReturnAndRent(CommandBufferScoped cbs)
+ {
+ Return(cbs);
+ return Rent();
+ }
+
+ public CommandBufferScoped Rent()
+ {
+ lock (_commandBuffers)
+ {
+ for (int i = 0; i < _totalCommandBuffers; i++)
+ {
+ int currentIndex = _cursor;
+
+ ref var entry = ref _commandBuffers[currentIndex];
+
+ if (!entry.InUse)
+ {
+ WaitAndDecrementRef(currentIndex);
+ entry.InUse = true;
+
+ var commandBufferBeginInfo = new CommandBufferBeginInfo()
+ {
+ SType = StructureType.CommandBufferBeginInfo
+ };
+
+ _api.BeginCommandBuffer(entry.CommandBuffer, commandBufferBeginInfo);
+
+ return new CommandBufferScoped(this, entry.CommandBuffer, currentIndex);
+ }
+
+ _cursor = (currentIndex + 1) & _totalCommandBuffersMask;
+ }
+
+ return default;
+ }
+ }
+
+ public void Return(CommandBufferScoped cbs)
+ {
+ Return(cbs, null, null, null);
+ }
+
+ public unsafe void Return(
+ CommandBufferScoped cbs,
+ Semaphore[] waitSemaphores,
+ PipelineStageFlags[] waitDstStageMask,
+ Semaphore[] signalSemaphores)
+ {
+ lock (_commandBuffers)
+ {
+ int cbIndex = cbs.CommandBufferIndex;
+
+ ref var entry = ref _commandBuffers[cbIndex];
+
+ if (_cursor == cbIndex)
+ {
+ _cursor = (cbIndex + 1) & _totalCommandBuffersMask;
+ }
+
+ Debug.Assert(entry.InUse);
+ Debug.Assert(entry.CommandBuffer.Handle == cbs.CommandBuffer.Handle);
+ entry.InUse = false;
+ entry.InConsumption = true;
+
+ var commandBuffer = entry.CommandBuffer;
+
+ _api.EndCommandBuffer(commandBuffer);
+
+ fixed (Semaphore* pWaitSemaphores = waitSemaphores, pSignalSemaphores = signalSemaphores)
+ {
+ fixed (PipelineStageFlags* pWaitDstStageMask = waitDstStageMask)
+ {
+ SubmitInfo sInfo = new SubmitInfo()
+ {
+ SType = StructureType.SubmitInfo,
+ WaitSemaphoreCount = waitSemaphores != null ? (uint)waitSemaphores.Length : 0,
+ PWaitSemaphores = pWaitSemaphores,
+ PWaitDstStageMask = pWaitDstStageMask,
+ CommandBufferCount = 1,
+ PCommandBuffers = &commandBuffer,
+ SignalSemaphoreCount = signalSemaphores != null ? (uint)signalSemaphores.Length : 0,
+ PSignalSemaphores = pSignalSemaphores
+ };
+
+ _api.QueueSubmit(_queue, 1, sInfo, entry.Fence.GetUnsafe());
+ }
+ }
+ // _api.QueueWaitIdle(_queue);
+ }
+ }
+
+ private void WaitAndDecrementRef(int cbIndex, bool refreshFence = true)
+ {
+ ref var entry = ref _commandBuffers[cbIndex];
+
+ if (entry.InConsumption)
+ {
+ entry.Fence.Wait();
+ entry.InConsumption = false;
+ }
+
+ foreach (var dependant in entry.Dependants)
+ {
+ dependant.DecrementReferenceCount(cbIndex);
+ }
+
+ foreach (var waitable in entry.Waitables)
+ {
+ waitable.RemoveFence(cbIndex, entry.Fence);
+ waitable.RemoveBufferUses(cbIndex);
+ }
+
+ foreach (var dependency in entry.Dependencies)
+ {
+ dependency.Put();
+ }
+
+ entry.Dependants.Clear();
+ entry.Waitables.Clear();
+ entry.Dependencies.Clear();
+ entry.Fence?.Dispose();
+
+ if (refreshFence)
+ {
+ entry.Fence = new FenceHolder(_api, _device);
+ }
+ else
+ {
+ entry.Fence = null;
+ }
+ }
+
+ public unsafe void Dispose()
+ {
+ for (int i = 0; i < _totalCommandBuffers; i++)
+ {
+ WaitAndDecrementRef(i, refreshFence: false);
+ }
+
+ _api.DestroyCommandPool(_device, _pool, null);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/CommandBufferScoped.cs b/Ryujinx.Graphics.Vulkan/CommandBufferScoped.cs
new file mode 100644
index 000000000..372950a88
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/CommandBufferScoped.cs
@@ -0,0 +1,44 @@
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct CommandBufferScoped : IDisposable
+ {
+ private readonly CommandBufferPool _pool;
+ public CommandBuffer CommandBuffer { get; }
+ public int CommandBufferIndex { get; }
+
+ public CommandBufferScoped(CommandBufferPool pool, CommandBuffer commandBuffer, int commandBufferIndex)
+ {
+ _pool = pool;
+ CommandBuffer = commandBuffer;
+ CommandBufferIndex = commandBufferIndex;
+ }
+
+ public void AddDependant(IAuto dependant)
+ {
+ _pool.AddDependant(CommandBufferIndex, dependant);
+ }
+
+ public void AddWaitable(MultiFenceHolder waitable)
+ {
+ _pool.AddWaitable(CommandBufferIndex, waitable);
+ }
+
+ public void AddDependency(CommandBufferScoped dependencyCbs)
+ {
+ _pool.AddDependency(CommandBufferIndex, dependencyCbs);
+ }
+
+ public FenceHolder GetFence()
+ {
+ return _pool.GetFence(CommandBufferIndex);
+ }
+
+ public void Dispose()
+ {
+ _pool?.Return(this);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/Constants.cs b/Ryujinx.Graphics.Vulkan/Constants.cs
new file mode 100644
index 000000000..b1b540ec9
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/Constants.cs
@@ -0,0 +1,20 @@
+namespace Ryujinx.Graphics.Vulkan
+{
+ static class Constants
+ {
+ public const int MaxVertexAttributes = 32;
+ public const int MaxVertexBuffers = 32;
+ public const int MaxTransformFeedbackBuffers = 4;
+ public const int MaxRenderTargets = 8;
+ public const int MaxViewports = 16;
+ public const int MaxShaderStages = 5;
+ public const int MaxUniformBuffersPerStage = 18;
+ public const int MaxStorageBuffersPerStage = 16;
+ public const int MaxTexturesPerStage = 32;
+ public const int MaxImagesPerStage = 8;
+ public const int MaxUniformBufferBindings = MaxUniformBuffersPerStage * MaxShaderStages;
+ public const int MaxStorageBufferBindings = MaxStorageBuffersPerStage * MaxShaderStages;
+ public const int MaxTextureBindings = MaxTexturesPerStage * MaxShaderStages;
+ public const int MaxImageBindings = MaxImagesPerStage * MaxShaderStages;
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/DescriptorSetCollection.cs b/Ryujinx.Graphics.Vulkan/DescriptorSetCollection.cs
new file mode 100644
index 000000000..245914e0a
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/DescriptorSetCollection.cs
@@ -0,0 +1,247 @@
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct DescriptorSetCollection : IDisposable
+ {
+ private DescriptorSetManager.DescriptorPoolHolder _holder;
+ private readonly DescriptorSet[] _descriptorSets;
+ public int SetsCount => _descriptorSets.Length;
+
+ public DescriptorSetCollection(DescriptorSetManager.DescriptorPoolHolder holder, DescriptorSet[] descriptorSets)
+ {
+ _holder = holder;
+ _descriptorSets = descriptorSets;
+ }
+
+ public void InitializeBuffers(int setIndex, int baseBinding, int countPerUnit, DescriptorType type)
+ {
+ Span infos = stackalloc DescriptorBufferInfo[countPerUnit];
+
+ for (int j = 0; j < countPerUnit; j++)
+ {
+ infos[j] = new DescriptorBufferInfo()
+ {
+ Range = Vk.WholeSize
+ };
+ }
+
+ UpdateBuffers(setIndex, baseBinding, infos, type);
+ }
+
+ public unsafe void UpdateBuffer(int setIndex, int bindingIndex, DescriptorBufferInfo bufferInfo, DescriptorType type)
+ {
+ if (bufferInfo.Buffer.Handle != 0UL)
+ {
+ var writeDescriptorSet = new WriteDescriptorSet
+ {
+ SType = StructureType.WriteDescriptorSet,
+ DstSet = _descriptorSets[setIndex],
+ DstBinding = (uint)bindingIndex,
+ DescriptorType = type,
+ DescriptorCount = 1,
+ PBufferInfo = &bufferInfo
+ };
+
+ _holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
+ }
+ }
+
+ public unsafe void UpdateBuffers(int setIndex, int baseBinding, ReadOnlySpan bufferInfo, DescriptorType type)
+ {
+ if (bufferInfo.Length == 0)
+ {
+ return;
+ }
+
+ fixed (DescriptorBufferInfo* pBufferInfo = bufferInfo)
+ {
+ var writeDescriptorSet = new WriteDescriptorSet
+ {
+ SType = StructureType.WriteDescriptorSet,
+ DstSet = _descriptorSets[setIndex],
+ DstBinding = (uint)baseBinding,
+ DescriptorType = type,
+ DescriptorCount = (uint)bufferInfo.Length,
+ PBufferInfo = pBufferInfo
+ };
+
+ _holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
+ }
+ }
+
+ public unsafe void UpdateStorageBuffers(int setIndex, int baseBinding, ReadOnlySpan bufferInfo)
+ {
+ if (bufferInfo.Length == 0)
+ {
+ return;
+ }
+
+ fixed (DescriptorBufferInfo* pBufferInfo = bufferInfo)
+ {
+ var writeDescriptorSet = new WriteDescriptorSet
+ {
+ SType = StructureType.WriteDescriptorSet,
+ DstSet = _descriptorSets[setIndex],
+ DstBinding = (uint)(baseBinding & ~(Constants.MaxStorageBuffersPerStage - 1)),
+ DstArrayElement = (uint)(baseBinding & (Constants.MaxStorageBuffersPerStage - 1)),
+ DescriptorType = DescriptorType.StorageBuffer,
+ DescriptorCount = (uint)bufferInfo.Length,
+ PBufferInfo = pBufferInfo
+ };
+
+ _holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
+ }
+ }
+
+ public unsafe void UpdateImage(int setIndex, int bindingIndex, DescriptorImageInfo imageInfo, DescriptorType type)
+ {
+ if (imageInfo.ImageView.Handle != 0UL)
+ {
+ var writeDescriptorSet = new WriteDescriptorSet
+ {
+ SType = StructureType.WriteDescriptorSet,
+ DstSet = _descriptorSets[setIndex],
+ DstBinding = (uint)bindingIndex,
+ DescriptorType = type,
+ DescriptorCount = 1,
+ PImageInfo = &imageInfo
+ };
+
+ _holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
+ }
+ }
+
+ public unsafe void UpdateImages(int setIndex, int baseBinding, ReadOnlySpan imageInfo, DescriptorType type)
+ {
+ if (imageInfo.Length == 0)
+ {
+ return;
+ }
+
+ fixed (DescriptorImageInfo* pImageInfo = imageInfo)
+ {
+ var writeDescriptorSet = new WriteDescriptorSet
+ {
+ SType = StructureType.WriteDescriptorSet,
+ DstSet = _descriptorSets[setIndex],
+ DstBinding = (uint)baseBinding,
+ DescriptorType = type,
+ DescriptorCount = (uint)imageInfo.Length,
+ PImageInfo = pImageInfo
+ };
+
+ _holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
+ }
+ }
+
+ public unsafe void UpdateImagesCombined(int setIndex, int baseBinding, ReadOnlySpan imageInfo, DescriptorType type)
+ {
+ if (imageInfo.Length == 0)
+ {
+ return;
+ }
+
+ fixed (DescriptorImageInfo* pImageInfo = imageInfo)
+ {
+ for (int i = 0; i < imageInfo.Length; i++)
+ {
+ bool nonNull = imageInfo[i].ImageView.Handle != 0 && imageInfo[i].Sampler.Handle != 0;
+ if (nonNull)
+ {
+ int count = 1;
+
+ while (i + count < imageInfo.Length &&
+ imageInfo[i + count].ImageView.Handle != 0 &&
+ imageInfo[i + count].Sampler.Handle != 0)
+ {
+ count++;
+ }
+
+ var writeDescriptorSet = new WriteDescriptorSet
+ {
+ SType = StructureType.WriteDescriptorSet,
+ DstSet = _descriptorSets[setIndex],
+ DstBinding = (uint)(baseBinding + i),
+ DescriptorType = DescriptorType.CombinedImageSampler,
+ DescriptorCount = (uint)count,
+ PImageInfo = pImageInfo
+ };
+
+ _holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
+
+ i += count - 1;
+ }
+ }
+ }
+ }
+
+ public unsafe void UpdateBufferImage(int setIndex, int bindingIndex, BufferView texelBufferView, DescriptorType type)
+ {
+ if (texelBufferView.Handle != 0UL)
+ {
+ var writeDescriptorSet = new WriteDescriptorSet
+ {
+ SType = StructureType.WriteDescriptorSet,
+ DstSet = _descriptorSets[setIndex],
+ DstBinding = (uint)bindingIndex,
+ DescriptorType = type,
+ DescriptorCount = 1,
+ PTexelBufferView = &texelBufferView
+ };
+
+ _holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
+ }
+ }
+
+ public unsafe void UpdateBufferImages(int setIndex, int baseBinding, ReadOnlySpan texelBufferView, DescriptorType type)
+ {
+ if (texelBufferView.Length == 0)
+ {
+ return;
+ }
+
+ fixed (BufferView* pTexelBufferView = texelBufferView)
+ {
+ for (uint i = 0; i < texelBufferView.Length;)
+ {
+ uint count = 1;
+
+ if (texelBufferView[(int)i].Handle != 0UL)
+ {
+ while (i + count < texelBufferView.Length && texelBufferView[(int)(i + count)].Handle != 0UL)
+ {
+ count++;
+ }
+
+ var writeDescriptorSet = new WriteDescriptorSet
+ {
+ SType = StructureType.WriteDescriptorSet,
+ DstSet = _descriptorSets[setIndex],
+ DstBinding = (uint)baseBinding + i,
+ DescriptorType = type,
+ DescriptorCount = count,
+ PTexelBufferView = pTexelBufferView + i
+ };
+
+ _holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
+ }
+
+ i += count;
+ }
+ }
+ }
+
+ public DescriptorSet[] GetSets()
+ {
+ return _descriptorSets;
+ }
+
+ public void Dispose()
+ {
+ _holder?.FreeDescriptorSets(this);
+ _holder = null;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs b/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs
new file mode 100644
index 000000000..a88bb7b12
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs
@@ -0,0 +1,201 @@
+using Silk.NET.Vulkan;
+using System;
+using System.Diagnostics;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class DescriptorSetManager : IDisposable
+ {
+ private const uint DescriptorPoolMultiplier = 16;
+
+ public class DescriptorPoolHolder : IDisposable
+ {
+ public Vk Api { get; }
+ public Device Device { get; }
+
+ private readonly DescriptorPool _pool;
+ private readonly uint _capacity;
+ private int _totalSets;
+ private int _setsInUse;
+ private bool _done;
+
+ public unsafe DescriptorPoolHolder(Vk api, Device device)
+ {
+ Api = api;
+ Device = device;
+
+ var poolSizes = new DescriptorPoolSize[]
+ {
+ new DescriptorPoolSize(DescriptorType.UniformBuffer, (1 + Constants.MaxUniformBufferBindings) * DescriptorPoolMultiplier),
+ new DescriptorPoolSize(DescriptorType.StorageBuffer, Constants.MaxStorageBufferBindings * DescriptorPoolMultiplier),
+ new DescriptorPoolSize(DescriptorType.CombinedImageSampler, Constants.MaxTextureBindings * DescriptorPoolMultiplier),
+ new DescriptorPoolSize(DescriptorType.StorageImage, Constants.MaxImageBindings * DescriptorPoolMultiplier),
+ new DescriptorPoolSize(DescriptorType.UniformTexelBuffer, Constants.MaxTextureBindings * DescriptorPoolMultiplier),
+ new DescriptorPoolSize(DescriptorType.StorageTexelBuffer, Constants.MaxImageBindings * DescriptorPoolMultiplier)
+ };
+
+ uint maxSets = (uint)poolSizes.Length * DescriptorPoolMultiplier;
+
+ _capacity = maxSets;
+
+ fixed (DescriptorPoolSize* pPoolsSize = poolSizes)
+ {
+ var descriptorPoolCreateInfo = new DescriptorPoolCreateInfo()
+ {
+ SType = StructureType.DescriptorPoolCreateInfo,
+ MaxSets = maxSets,
+ PoolSizeCount = (uint)poolSizes.Length,
+ PPoolSizes = pPoolsSize
+ };
+
+ Api.CreateDescriptorPool(device, descriptorPoolCreateInfo, null, out _pool).ThrowOnError();
+ }
+ }
+
+ public unsafe DescriptorSetCollection AllocateDescriptorSets(ReadOnlySpan layouts)
+ {
+ TryAllocateDescriptorSets(layouts, isTry: false, out var dsc);
+ return dsc;
+ }
+
+ public bool TryAllocateDescriptorSets(ReadOnlySpan layouts, out DescriptorSetCollection dsc)
+ {
+ return TryAllocateDescriptorSets(layouts, isTry: true, out dsc);
+ }
+
+ private unsafe bool TryAllocateDescriptorSets(ReadOnlySpan layouts, bool isTry, out DescriptorSetCollection dsc)
+ {
+ Debug.Assert(!_done);
+
+ DescriptorSet[] descriptorSets = new DescriptorSet[layouts.Length];
+
+ fixed (DescriptorSet* pDescriptorSets = descriptorSets)
+ {
+ fixed (DescriptorSetLayout* pLayouts = layouts)
+ {
+ var descriptorSetAllocateInfo = new DescriptorSetAllocateInfo()
+ {
+ SType = StructureType.DescriptorSetAllocateInfo,
+ DescriptorPool = _pool,
+ DescriptorSetCount = (uint)layouts.Length,
+ PSetLayouts = pLayouts
+ };
+
+ var result = Api.AllocateDescriptorSets(Device, &descriptorSetAllocateInfo, pDescriptorSets);
+ if (isTry && result == Result.ErrorOutOfPoolMemory)
+ {
+ _totalSets = (int)_capacity;
+ _done = true;
+ DestroyIfDone();
+ dsc = default;
+ return false;
+ }
+
+ result.ThrowOnError();
+ }
+ }
+
+ _totalSets += layouts.Length;
+ _setsInUse += layouts.Length;
+
+ dsc = new DescriptorSetCollection(this, descriptorSets);
+ return true;
+ }
+
+ public void FreeDescriptorSets(DescriptorSetCollection dsc)
+ {
+ _setsInUse -= dsc.SetsCount;
+ Debug.Assert(_setsInUse >= 0);
+ DestroyIfDone();
+ }
+
+ public bool CanFit(int count)
+ {
+ if (_totalSets + count <= _capacity)
+ {
+ return true;
+ }
+
+ _done = true;
+ DestroyIfDone();
+ return false;
+ }
+
+ private unsafe void DestroyIfDone()
+ {
+ if (_done && _setsInUse == 0)
+ {
+ Api.DestroyDescriptorPool(Device, _pool, null);
+ }
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ unsafe
+ {
+ Api.DestroyDescriptorPool(Device, _pool, null);
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+ }
+
+ private readonly Device _device;
+ private DescriptorPoolHolder _currentPool;
+
+ public DescriptorSetManager(Device device)
+ {
+ _device = device;
+ }
+
+ public Auto AllocateDescriptorSet(Vk api, DescriptorSetLayout layout)
+ {
+ Span layouts = stackalloc DescriptorSetLayout[1];
+ layouts[0] = layout;
+ return AllocateDescriptorSets(api, layouts);
+ }
+
+ public Auto AllocateDescriptorSets(Vk api, ReadOnlySpan layouts)
+ {
+ // If we fail the first time, just create a new pool and try again.
+ if (!GetPool(api, layouts.Length).TryAllocateDescriptorSets(layouts, out var dsc))
+ {
+ dsc = GetPool(api, layouts.Length).AllocateDescriptorSets(layouts);
+ }
+
+ return new Auto(dsc);
+ }
+
+ private DescriptorPoolHolder GetPool(Vk api, int requiredCount)
+ {
+ if (_currentPool == null || !_currentPool.CanFit(requiredCount))
+ {
+ _currentPool = new DescriptorPoolHolder(api, _device);
+ }
+
+ return _currentPool;
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ unsafe
+ {
+ _currentPool?.Dispose();
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs b/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs
new file mode 100644
index 000000000..507b03bab
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs
@@ -0,0 +1,489 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Shader;
+using Silk.NET.Vulkan;
+using System;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class DescriptorSetUpdater
+ {
+ private readonly VulkanGraphicsDevice _gd;
+ private readonly PipelineBase _pipeline;
+
+ private ShaderCollection _program;
+
+ private Auto[] _uniformBufferRefs;
+ private Auto[] _storageBufferRefs;
+ private Auto[] _textureRefs;
+ private Auto[] _samplerRefs;
+ private Auto[] _imageRefs;
+ private TextureBuffer[] _bufferTextureRefs;
+ private TextureBuffer[] _bufferImageRefs;
+
+ private DescriptorBufferInfo[] _uniformBuffers;
+ private DescriptorBufferInfo[] _storageBuffers;
+ private DescriptorImageInfo[] _textures;
+ private DescriptorImageInfo[] _images;
+ private BufferView[] _bufferTextures;
+ private BufferView[] _bufferImages;
+
+ [Flags]
+ private enum DirtyFlags
+ {
+ None = 0,
+ Uniform = 1 << 0,
+ Storage = 1 << 1,
+ Texture = 1 << 2,
+ Image = 1 << 3,
+ BufferTexture = 1 << 4,
+ BufferImage = 1 << 5,
+ All = Uniform | Storage | Texture | Image | BufferTexture | BufferImage
+ }
+
+ private DirtyFlags _dirty;
+
+ private readonly TextureView _dummyTexture;
+ private readonly SamplerHolder _dummySampler;
+
+ public DescriptorSetUpdater(VulkanGraphicsDevice gd, PipelineBase pipeline)
+ {
+ _gd = gd;
+ _pipeline = pipeline;
+
+ _uniformBuffers = Array.Empty();
+ _storageBuffers = Array.Empty();
+ _textures = new DescriptorImageInfo[32 * 5];
+ _textureRefs = new Auto[32 * 5];
+ _samplerRefs = new Auto[32 * 5];
+ _images = Array.Empty();
+ _bufferTextures = Array.Empty();
+ _bufferImages = Array.Empty();
+
+ _dummyTexture = (TextureView)gd.CreateTexture(new GAL.TextureCreateInfo(
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 4,
+ GAL.Format.R8G8B8A8Unorm,
+ DepthStencilMode.Depth,
+ Target.Texture2D,
+ SwizzleComponent.Red,
+ SwizzleComponent.Green,
+ SwizzleComponent.Blue,
+ SwizzleComponent.Alpha), 1f);
+
+ _dummySampler = (SamplerHolder)gd.CreateSampler(new GAL.SamplerCreateInfo(
+ MinFilter.Nearest,
+ MagFilter.Nearest,
+ false,
+ AddressMode.Repeat,
+ AddressMode.Repeat,
+ AddressMode.Repeat,
+ CompareMode.None,
+ GAL.CompareOp.Always,
+ new ColorF(0, 0, 0, 0),
+ 0,
+ 0,
+ 0,
+ 1f));
+ }
+
+ public void SetProgram(ShaderCollection program)
+ {
+ _program = program;
+ _dirty = DirtyFlags.All;
+ }
+
+ public void SetImage(int binding, ITexture image, GAL.Format imageFormat)
+ {
+ if (image == null)
+ {
+ return;
+ }
+
+ if (image is TextureBuffer imageBuffer)
+ {
+ if (_bufferImages.Length <= binding)
+ {
+ Array.Resize(ref _bufferImages, binding + 1);
+ Array.Resize(ref _bufferImageRefs, binding + 1);
+ }
+
+ _bufferImageRefs[binding] = imageBuffer;
+
+ SignalDirty(DirtyFlags.BufferImage);
+ }
+ else
+ {
+ if (_images.Length <= binding)
+ {
+ Array.Resize(ref _images, binding + 1);
+ Array.Resize(ref _imageRefs, binding + 1);
+ }
+
+ if (image != null)
+ {
+ _imageRefs[binding] = ((TextureView)image).GetIdentityImageView();
+ _images[binding] = new DescriptorImageInfo()
+ {
+ ImageLayout = ImageLayout.General
+ };
+ }
+
+ SignalDirty(DirtyFlags.Image);
+ }
+ }
+
+ public void SetStorageBuffers(CommandBuffer commandBuffer, int first, ReadOnlySpan buffers)
+ {
+ Array.Resize(ref _storageBuffers, first + buffers.Length);
+ Array.Resize(ref _storageBufferRefs, first + buffers.Length);
+
+ for (int i = 0; i < buffers.Length; i++)
+ {
+ var buffer = buffers[i];
+
+ _storageBufferRefs[first + i] = _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false);
+
+ _storageBuffers[first + i] = new DescriptorBufferInfo()
+ {
+ Offset = (ulong)buffer.Offset,
+ Range = (ulong)buffer.Size
+ };
+ }
+
+ SignalDirty(DirtyFlags.Storage);
+ }
+
+ public void SetTextureAndSampler(int binding, ITexture texture, ISampler sampler)
+ {
+ if (texture == null)
+ {
+ return;
+ }
+
+ if (texture is TextureBuffer textureBuffer)
+ {
+ if (_bufferTextures.Length <= binding)
+ {
+ Array.Resize(ref _bufferTextures, binding + 1);
+ Array.Resize(ref _bufferTextureRefs, binding + 1);
+ }
+
+ _bufferTextureRefs[binding] = textureBuffer;
+
+ SignalDirty(DirtyFlags.BufferTexture);
+ }
+ else
+ {
+ if (sampler == null)
+ {
+ return;
+ }
+
+ if (_textures.Length <= binding)
+ {
+ Array.Resize(ref _textures, binding + 1);
+ Array.Resize(ref _textureRefs, binding + 1);
+ Array.Resize(ref _samplerRefs, binding + 1);
+ }
+
+ _textureRefs[binding] = ((TextureView)texture).GetImageView();
+ _samplerRefs[binding] = ((SamplerHolder)sampler).GetSampler();
+
+ _textures[binding] = new DescriptorImageInfo()
+ {
+ ImageLayout = ImageLayout.General
+ };
+
+ SignalDirty(DirtyFlags.Texture);
+ }
+ }
+
+ public void SetUniformBuffers(CommandBuffer commandBuffer, int first, ReadOnlySpan buffers)
+ {
+ Array.Resize(ref _uniformBuffers, first + buffers.Length);
+ Array.Resize(ref _uniformBufferRefs, first + buffers.Length);
+
+ for (int i = 0; i < buffers.Length; i++)
+ {
+ var buffer = buffers[i];
+
+ _uniformBufferRefs[first + i] = _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false);
+
+ _uniformBuffers[first + i] = new DescriptorBufferInfo()
+ {
+ Offset = (ulong)buffer.Offset,
+ Range = (ulong)buffer.Size
+ };
+ }
+
+ SignalDirty(DirtyFlags.Uniform);
+ }
+
+ private void SignalDirty(DirtyFlags flag)
+ {
+ _dirty |= flag;
+ }
+
+ public void UpdateAndBindDescriptorSets(CommandBufferScoped cbs, PipelineBindPoint pbp)
+ {
+ if ((_dirty & DirtyFlags.All) == 0)
+ {
+ return;
+ }
+
+ // System.Console.WriteLine("modified " + _dirty + " " + _modified + " on program " + _program.GetHashCode().ToString("X"));
+
+ if (_dirty.HasFlag(DirtyFlags.Uniform))
+ {
+ UpdateAndBind(cbs, PipelineBase.UniformSetIndex, DirtyFlags.Uniform, pbp);
+ }
+
+ if (_dirty.HasFlag(DirtyFlags.Storage))
+ {
+ UpdateAndBind(cbs, PipelineBase.StorageSetIndex, DirtyFlags.Storage, pbp);
+ }
+
+ if (_dirty.HasFlag(DirtyFlags.Texture))
+ {
+ UpdateAndBind(cbs, PipelineBase.TextureSetIndex, DirtyFlags.Texture, pbp);
+ }
+
+ if (_dirty.HasFlag(DirtyFlags.Image))
+ {
+ UpdateAndBind(cbs, PipelineBase.ImageSetIndex, DirtyFlags.Image, pbp);
+ }
+
+ if (_dirty.HasFlag(DirtyFlags.BufferTexture))
+ {
+ UpdateAndBind(cbs, PipelineBase.BufferTextureSetIndex, DirtyFlags.BufferTexture, pbp);
+ }
+
+ if (_dirty.HasFlag(DirtyFlags.BufferImage))
+ {
+ UpdateAndBind(cbs, PipelineBase.BufferImageSetIndex, DirtyFlags.BufferImage, pbp);
+ }
+
+ _dirty = DirtyFlags.None;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void UpdateBuffer(CommandBufferScoped cbs, ref DescriptorBufferInfo info, Auto buffer)
+ {
+ info.Buffer = buffer?.Get(cbs, (int)info.Offset, (int)info.Range).Value ?? default;
+
+ // The spec requires that buffers with null handle have offset as 0 and range as VK_WHOLE_SIZE.
+ if (info.Buffer.Handle == 0)
+ {
+ info.Offset = 0;
+ info.Range = Vk.WholeSize;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void UpdateAndBind(CommandBufferScoped cbs, int setIndex, DirtyFlags flag, PipelineBindPoint pbp)
+ {
+ int stagesCount = _program.Bindings[setIndex].Length;
+ if (stagesCount == 0 && setIndex != PipelineBase.UniformSetIndex)
+ {
+ return;
+ }
+
+ var dsc = _program.GetNewDescriptorSetCollection(_gd, cbs.CommandBufferIndex, setIndex, out var isNew).Get(cbs);
+
+ if (isNew)
+ {
+ Initialize(cbs, setIndex, dsc);
+ }
+
+ if (setIndex == PipelineBase.UniformSetIndex)
+ {
+ Span uniformBuffer = stackalloc DescriptorBufferInfo[1];
+
+ uniformBuffer[0] = new DescriptorBufferInfo()
+ {
+ Offset = 0,
+ Range = (ulong)SupportBuffer.RequiredSize,
+ Buffer = _pipeline.RenderScaleBuffer.GetBuffer().Get(cbs, 0, SupportBuffer.RequiredSize).Value
+ };
+
+ dsc.UpdateBuffers(0, 0, uniformBuffer, DescriptorType.UniformBuffer);
+ }
+
+ for (int stageIndex = 0; stageIndex < stagesCount; stageIndex++)
+ {
+ var stageBindings = _program.Bindings[setIndex][stageIndex];
+ int bindingsCount = stageBindings.Length;
+ int count;
+
+ for (int bindingIndex = 0; bindingIndex < bindingsCount; bindingIndex += count)
+ {
+ int binding = stageBindings[bindingIndex];
+ count = 1;
+
+ while (bindingIndex + count < bindingsCount && stageBindings[bindingIndex + count] == binding + count)
+ {
+ count++;
+ }
+
+ if (setIndex == PipelineBase.UniformSetIndex)
+ {
+ count = Math.Min(count, _uniformBuffers.Length - binding);
+
+ if (count <= 0)
+ {
+ break;
+ }
+
+ for (int i = 0; i < count; i++)
+ {
+ UpdateBuffer(cbs, ref _uniformBuffers[binding + i], _uniformBufferRefs[binding + i]);
+ }
+
+ ReadOnlySpan uniformBuffers = _uniformBuffers;
+ dsc.UpdateBuffers(0, binding, uniformBuffers.Slice(binding, count), DescriptorType.UniformBuffer);
+ }
+ else if (setIndex == PipelineBase.StorageSetIndex)
+ {
+ count = Math.Min(count, _storageBuffers.Length - binding);
+
+ if (count <= 0)
+ {
+ break;
+ }
+
+ for (int i = 0; i < count; i++)
+ {
+ UpdateBuffer(cbs, ref _storageBuffers[binding + i], _storageBufferRefs[binding + i]);
+ }
+
+ ReadOnlySpan storageBuffers = _storageBuffers;
+ dsc.UpdateStorageBuffers(0, binding, storageBuffers.Slice(binding, count));
+ }
+ else if (setIndex == PipelineBase.TextureSetIndex)
+ {
+ for (int i = 0; i < count; i++)
+ {
+ ref var texture = ref _textures[binding + i];
+
+ texture.ImageView = _textureRefs[binding + i]?.Get(cbs).Value ?? default;
+ texture.Sampler = _samplerRefs[binding + i]?.Get(cbs).Value ?? default;
+ texture.ImageLayout = ImageLayout.General;
+
+ if (texture.ImageView.Handle == 0 || texture.Sampler.Handle == 0)
+ {
+ texture.ImageView = _dummyTexture.GetImageView().Get(cbs).Value;
+ texture.Sampler = _dummySampler.GetSampler().Get(cbs).Value;
+ }
+ }
+
+ ReadOnlySpan textures = _textures;
+ dsc.UpdateImages(0, binding, textures.Slice(binding, count), DescriptorType.CombinedImageSampler);
+ }
+ else if (setIndex == PipelineBase.ImageSetIndex)
+ {
+ count = Math.Min(count, _images.Length - binding);
+
+ if (count <= 0)
+ {
+ break;
+ }
+
+ for (int i = 0; i < count; i++)
+ {
+ _images[binding + i].ImageView = _imageRefs[binding + i]?.Get(cbs).Value ?? default;
+ }
+
+ ReadOnlySpan images = _images;
+ dsc.UpdateImages(0, binding, images.Slice(binding, count), DescriptorType.StorageImage);
+ }
+ else if (setIndex == PipelineBase.BufferTextureSetIndex)
+ {
+ count = Math.Min(count, _bufferTextures.Length - binding);
+
+ if (count <= 0)
+ {
+ break;
+ }
+
+ for (int i = 0; i < count; i++)
+ {
+ _bufferTextures[binding + i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs) ?? default;
+ }
+
+ ReadOnlySpan bufferTextures = _bufferTextures;
+ dsc.UpdateBufferImages(0, binding, bufferTextures.Slice(binding, count), DescriptorType.UniformTexelBuffer);
+ }
+ else if (setIndex == PipelineBase.BufferImageSetIndex)
+ {
+ count = Math.Min(count, _bufferImages.Length - binding);
+
+ if (count <= 0)
+ {
+ break;
+ }
+
+ for (int i = 0; i < count; i++)
+ {
+ _bufferImages[binding + i] = _bufferImageRefs[binding + i]?.GetBufferView(cbs) ?? default;
+ }
+
+ ReadOnlySpan bufferImages = _bufferImages;
+ dsc.UpdateBufferImages(0, binding, bufferImages.Slice(binding, count), DescriptorType.StorageTexelBuffer);
+ }
+ }
+ }
+
+ var sets = dsc.GetSets();
+
+ _gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan.Empty);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void Initialize(CommandBufferScoped cbs, int setIndex, DescriptorSetCollection dsc)
+ {
+ uint stages = _program.Stages;
+
+ while (stages != 0)
+ {
+ int stage = BitOperations.TrailingZeroCount(stages);
+ stages &= ~(1u << stage);
+
+ if (setIndex == PipelineBase.UniformSetIndex)
+ {
+ dsc.InitializeBuffers(0, 1 + stage * Constants.MaxUniformBuffersPerStage, Constants.MaxUniformBuffersPerStage, DescriptorType.UniformBuffer);
+ }
+ else if (setIndex == PipelineBase.StorageSetIndex)
+ {
+ dsc.InitializeBuffers(0, stage * Constants.MaxStorageBuffersPerStage, Constants.MaxStorageBuffersPerStage, DescriptorType.StorageBuffer);
+ }
+ }
+ }
+
+ public void SignalCommandBufferChange()
+ {
+ _dirty = DirtyFlags.All;
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _dummyTexture.Dispose();
+ _dummySampler.Dispose();
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/DisposableBuffer.cs b/Ryujinx.Graphics.Vulkan/DisposableBuffer.cs
new file mode 100644
index 000000000..a62b0d149
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/DisposableBuffer.cs
@@ -0,0 +1,24 @@
+using Silk.NET.Vulkan;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct DisposableBuffer : System.IDisposable
+ {
+ private readonly Vk _api;
+ private readonly Device _device;
+
+ public Buffer Value { get; }
+
+ public DisposableBuffer(Vk api, Device device, Buffer buffer)
+ {
+ _api = api;
+ _device = device;
+ Value = buffer;
+ }
+
+ public unsafe void Dispose()
+ {
+ _api.DestroyBuffer(_device, Value, null);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/DisposableBufferView.cs b/Ryujinx.Graphics.Vulkan/DisposableBufferView.cs
new file mode 100644
index 000000000..41d905f12
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/DisposableBufferView.cs
@@ -0,0 +1,24 @@
+using Silk.NET.Vulkan;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct DisposableBufferView : System.IDisposable
+ {
+ private readonly Vk _api;
+ private readonly Device _device;
+
+ public BufferView Value { get; }
+
+ public DisposableBufferView(Vk api, Device device, BufferView bufferView)
+ {
+ _api = api;
+ _device = device;
+ Value = bufferView;
+ }
+
+ public unsafe void Dispose()
+ {
+ _api.DestroyBufferView(_device, Value, null);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/DisposableFramebuffer.cs b/Ryujinx.Graphics.Vulkan/DisposableFramebuffer.cs
new file mode 100644
index 000000000..f8436ddc5
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/DisposableFramebuffer.cs
@@ -0,0 +1,25 @@
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct DisposableFramebuffer : IDisposable
+ {
+ private readonly Vk _api;
+ private readonly Device _device;
+
+ public Framebuffer Value { get; }
+
+ public DisposableFramebuffer(Vk api, Device device, Framebuffer framebuffer)
+ {
+ _api = api;
+ _device = device;
+ Value = framebuffer;
+ }
+
+ public unsafe void Dispose()
+ {
+ _api.DestroyFramebuffer(_device, Value, null);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/DisposableImage.cs b/Ryujinx.Graphics.Vulkan/DisposableImage.cs
new file mode 100644
index 000000000..d10cb7f8e
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/DisposableImage.cs
@@ -0,0 +1,25 @@
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct DisposableImage : IDisposable
+ {
+ private readonly Vk _api;
+ private readonly Device _device;
+
+ public Image Value { get; }
+
+ public DisposableImage(Vk api, Device device, Image image)
+ {
+ _api = api;
+ _device = device;
+ Value = image;
+ }
+
+ public unsafe void Dispose()
+ {
+ _api.DestroyImage(_device, Value, null);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/DisposableImageView.cs b/Ryujinx.Graphics.Vulkan/DisposableImageView.cs
new file mode 100644
index 000000000..e74230f8e
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/DisposableImageView.cs
@@ -0,0 +1,25 @@
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct DisposableImageView : IDisposable
+ {
+ private readonly Vk _api;
+ private readonly Device _device;
+
+ public ImageView Value { get; }
+
+ public DisposableImageView(Vk api, Device device, ImageView imageView)
+ {
+ _api = api;
+ _device = device;
+ Value = imageView;
+ }
+
+ public unsafe void Dispose()
+ {
+ _api.DestroyImageView(_device, Value, null);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/DisposableMemory.cs b/Ryujinx.Graphics.Vulkan/DisposableMemory.cs
new file mode 100644
index 000000000..2dedb5842
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/DisposableMemory.cs
@@ -0,0 +1,24 @@
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct DisposableMemory : IDisposable
+ {
+ private readonly Vk _api;
+ private readonly Device _device;
+ private readonly DeviceMemory _memory;
+
+ public DisposableMemory(Vk api, Device device, DeviceMemory memory)
+ {
+ _api = api;
+ _device = device;
+ _memory = memory;
+ }
+
+ public unsafe void Dispose()
+ {
+ _api.FreeMemory(_device, _memory, null);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/DisposablePipeline.cs b/Ryujinx.Graphics.Vulkan/DisposablePipeline.cs
new file mode 100644
index 000000000..2cbca42d9
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/DisposablePipeline.cs
@@ -0,0 +1,25 @@
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct DisposablePipeline : IDisposable
+ {
+ private readonly Vk _api;
+ private readonly Device _device;
+
+ public Pipeline Value { get; }
+
+ public DisposablePipeline(Vk api, Device device, Pipeline pipeline)
+ {
+ _api = api;
+ _device = device;
+ Value = pipeline;
+ }
+
+ public unsafe void Dispose()
+ {
+ _api.DestroyPipeline(_device, Value, null);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/DisposableRenderPass.cs b/Ryujinx.Graphics.Vulkan/DisposableRenderPass.cs
new file mode 100644
index 000000000..e3f0d0e65
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/DisposableRenderPass.cs
@@ -0,0 +1,25 @@
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct DisposableRenderPass : IDisposable
+ {
+ private readonly Vk _api;
+ private readonly Device _device;
+
+ public RenderPass Value { get; }
+
+ public DisposableRenderPass(Vk api, Device device, RenderPass renderPass)
+ {
+ _api = api;
+ _device = device;
+ Value = renderPass;
+ }
+
+ public unsafe void Dispose()
+ {
+ _api.DestroyRenderPass(_device, Value, null);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/DisposableSampler.cs b/Ryujinx.Graphics.Vulkan/DisposableSampler.cs
new file mode 100644
index 000000000..89b964da4
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/DisposableSampler.cs
@@ -0,0 +1,25 @@
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct DisposableSampler : IDisposable
+ {
+ private readonly Vk _api;
+ private readonly Device _device;
+
+ public Sampler Value { get; }
+
+ public DisposableSampler(Vk api, Device device, Sampler sampler)
+ {
+ _api = api;
+ _device = device;
+ Value = sampler;
+ }
+
+ public unsafe void Dispose()
+ {
+ _api.DestroySampler(_device, Value, null);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/EnumConversion.cs b/Ryujinx.Graphics.Vulkan/EnumConversion.cs
new file mode 100644
index 000000000..944818388
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/EnumConversion.cs
@@ -0,0 +1,489 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Shader;
+using Silk.NET.Vulkan;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ static class EnumConversion
+ {
+ public static ShaderStageFlags Convert(this ShaderStage stage)
+ {
+ switch (stage)
+ {
+ case ShaderStage.Vertex:
+ return ShaderStageFlags.ShaderStageVertexBit;
+ case ShaderStage.Geometry:
+ return ShaderStageFlags.ShaderStageGeometryBit;
+ case ShaderStage.TessellationControl:
+ return ShaderStageFlags.ShaderStageTessellationControlBit;
+ case ShaderStage.TessellationEvaluation:
+ return ShaderStageFlags.ShaderStageTessellationEvaluationBit;
+ case ShaderStage.Fragment:
+ return ShaderStageFlags.ShaderStageFragmentBit;
+ case ShaderStage.Compute:
+ return ShaderStageFlags.ShaderStageComputeBit;
+ };
+
+ Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(ShaderStage)} enum value: {stage}.");
+
+ return 0;
+ }
+
+ public static SamplerAddressMode Convert(this AddressMode mode)
+ {
+ switch (mode)
+ {
+ case AddressMode.Clamp:
+ return SamplerAddressMode.ClampToBorder; // TODO: Should be clamp
+ case AddressMode.Repeat:
+ return SamplerAddressMode.Repeat;
+ case AddressMode.MirrorClamp:
+ return SamplerAddressMode.ClampToBorder; // TODO: Should be mirror clamp
+ case AddressMode.MirrorClampToEdge:
+ return SamplerAddressMode.MirrorClampToEdgeKhr;
+ case AddressMode.MirrorClampToBorder:
+ return SamplerAddressMode.ClampToBorder; // TODO: Should be mirror clamp to border
+ case AddressMode.ClampToBorder:
+ return SamplerAddressMode.ClampToBorder;
+ case AddressMode.MirroredRepeat:
+ return SamplerAddressMode.MirroredRepeat;
+ case AddressMode.ClampToEdge:
+ return SamplerAddressMode.ClampToEdge;
+ }
+
+ Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(AddressMode)} enum value: {mode}.");
+
+ return SamplerAddressMode.ClampToBorder; // TODO: Should be clamp
+ }
+
+ public static Silk.NET.Vulkan.BlendFactor Convert(this GAL.BlendFactor factor)
+ {
+ switch (factor)
+ {
+ case GAL.BlendFactor.Zero:
+ case GAL.BlendFactor.ZeroGl:
+ return Silk.NET.Vulkan.BlendFactor.Zero;
+ case GAL.BlendFactor.One:
+ case GAL.BlendFactor.OneGl:
+ return Silk.NET.Vulkan.BlendFactor.One;
+ case GAL.BlendFactor.SrcColor:
+ case GAL.BlendFactor.SrcColorGl:
+ return Silk.NET.Vulkan.BlendFactor.SrcColor;
+ case GAL.BlendFactor.OneMinusSrcColor:
+ case GAL.BlendFactor.OneMinusSrcColorGl:
+ return Silk.NET.Vulkan.BlendFactor.OneMinusSrcColor;
+ case GAL.BlendFactor.SrcAlpha:
+ case GAL.BlendFactor.SrcAlphaGl:
+ return Silk.NET.Vulkan.BlendFactor.SrcAlpha;
+ case GAL.BlendFactor.OneMinusSrcAlpha:
+ case GAL.BlendFactor.OneMinusSrcAlphaGl:
+ return Silk.NET.Vulkan.BlendFactor.OneMinusSrcAlpha;
+ case GAL.BlendFactor.DstAlpha:
+ case GAL.BlendFactor.DstAlphaGl:
+ return Silk.NET.Vulkan.BlendFactor.DstAlpha;
+ case GAL.BlendFactor.OneMinusDstAlpha:
+ case GAL.BlendFactor.OneMinusDstAlphaGl:
+ return Silk.NET.Vulkan.BlendFactor.OneMinusDstAlpha;
+ case GAL.BlendFactor.DstColor:
+ case GAL.BlendFactor.DstColorGl:
+ return Silk.NET.Vulkan.BlendFactor.DstColor;
+ case GAL.BlendFactor.OneMinusDstColor:
+ case GAL.BlendFactor.OneMinusDstColorGl:
+ return Silk.NET.Vulkan.BlendFactor.OneMinusDstColor;
+ case GAL.BlendFactor.SrcAlphaSaturate:
+ case GAL.BlendFactor.SrcAlphaSaturateGl:
+ return Silk.NET.Vulkan.BlendFactor.SrcAlphaSaturate;
+ case GAL.BlendFactor.Src1Color:
+ case GAL.BlendFactor.Src1ColorGl:
+ return Silk.NET.Vulkan.BlendFactor.Src1Color;
+ case GAL.BlendFactor.OneMinusSrc1Color:
+ case GAL.BlendFactor.OneMinusSrc1ColorGl:
+ return Silk.NET.Vulkan.BlendFactor.OneMinusSrc1Color;
+ case GAL.BlendFactor.Src1Alpha:
+ case GAL.BlendFactor.Src1AlphaGl:
+ return Silk.NET.Vulkan.BlendFactor.Src1Alpha;
+ case GAL.BlendFactor.OneMinusSrc1Alpha:
+ case GAL.BlendFactor.OneMinusSrc1AlphaGl:
+ return Silk.NET.Vulkan.BlendFactor.OneMinusSrc1Alpha;
+ case GAL.BlendFactor.ConstantColor:
+ return Silk.NET.Vulkan.BlendFactor.ConstantColor;
+ case GAL.BlendFactor.OneMinusConstantColor:
+ return Silk.NET.Vulkan.BlendFactor.OneMinusConstantColor;
+ case GAL.BlendFactor.ConstantAlpha:
+ return Silk.NET.Vulkan.BlendFactor.ConstantAlpha;
+ case GAL.BlendFactor.OneMinusConstantAlpha:
+ return Silk.NET.Vulkan.BlendFactor.OneMinusConstantAlpha;
+ }
+
+ Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(GAL.BlendFactor)} enum value: {factor}.");
+
+ return Silk.NET.Vulkan.BlendFactor.Zero;
+ }
+
+ public static Silk.NET.Vulkan.BlendOp Convert(this GAL.BlendOp op)
+ {
+ switch (op)
+ {
+ case GAL.BlendOp.Add:
+ case GAL.BlendOp.AddGl:
+ return Silk.NET.Vulkan.BlendOp.Add;
+ case GAL.BlendOp.Subtract:
+ case GAL.BlendOp.SubtractGl:
+ return Silk.NET.Vulkan.BlendOp.Subtract;
+ case GAL.BlendOp.ReverseSubtract:
+ case GAL.BlendOp.ReverseSubtractGl:
+ return Silk.NET.Vulkan.BlendOp.ReverseSubtract;
+ case GAL.BlendOp.Minimum:
+ case GAL.BlendOp.MinimumGl:
+ return Silk.NET.Vulkan.BlendOp.Min;
+ case GAL.BlendOp.Maximum:
+ case GAL.BlendOp.MaximumGl:
+ return Silk.NET.Vulkan.BlendOp.Max;
+ }
+
+ Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(GAL.BlendOp)} enum value: {op}.");
+
+ return Silk.NET.Vulkan.BlendOp.Add;
+ }
+
+ public static Silk.NET.Vulkan.CompareOp Convert(this GAL.CompareOp op)
+ {
+ switch (op)
+ {
+ case GAL.CompareOp.Never:
+ case GAL.CompareOp.NeverGl:
+ return Silk.NET.Vulkan.CompareOp.Never;
+ case GAL.CompareOp.Less:
+ case GAL.CompareOp.LessGl:
+ return Silk.NET.Vulkan.CompareOp.Less;
+ case GAL.CompareOp.Equal:
+ case GAL.CompareOp.EqualGl:
+ return Silk.NET.Vulkan.CompareOp.Equal;
+ case GAL.CompareOp.LessOrEqual:
+ case GAL.CompareOp.LessOrEqualGl:
+ return Silk.NET.Vulkan.CompareOp.LessOrEqual;
+ case GAL.CompareOp.Greater:
+ case GAL.CompareOp.GreaterGl:
+ return Silk.NET.Vulkan.CompareOp.Greater;
+ case GAL.CompareOp.NotEqual:
+ case GAL.CompareOp.NotEqualGl:
+ return Silk.NET.Vulkan.CompareOp.NotEqual;
+ case GAL.CompareOp.GreaterOrEqual:
+ case GAL.CompareOp.GreaterOrEqualGl:
+ return Silk.NET.Vulkan.CompareOp.GreaterOrEqual;
+ case GAL.CompareOp.Always:
+ case GAL.CompareOp.AlwaysGl:
+ return Silk.NET.Vulkan.CompareOp.Always;
+ }
+
+ Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(GAL.CompareOp)} enum value: {op}.");
+
+ return Silk.NET.Vulkan.CompareOp.Never;
+ }
+
+ public static CullModeFlags Convert(this Face face)
+ {
+ switch (face)
+ {
+ case Face.Back:
+ return CullModeFlags.CullModeBackBit;
+ case Face.Front:
+ return CullModeFlags.CullModeFrontBit;
+ case Face.FrontAndBack:
+ return CullModeFlags.CullModeFrontAndBack;
+ }
+
+ Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(Face)} enum value: {face}.");
+
+ return CullModeFlags.CullModeBackBit;
+ }
+
+ public static Silk.NET.Vulkan.FrontFace Convert(this GAL.FrontFace frontFace)
+ {
+ // Flipped to account for origin differences.
+ switch (frontFace)
+ {
+ case GAL.FrontFace.Clockwise:
+ return Silk.NET.Vulkan.FrontFace.CounterClockwise;
+ case GAL.FrontFace.CounterClockwise:
+ return Silk.NET.Vulkan.FrontFace.Clockwise;
+ }
+
+ Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(GAL.FrontFace)} enum value: {frontFace}.");
+
+ return Silk.NET.Vulkan.FrontFace.Clockwise;
+ }
+
+ public static Silk.NET.Vulkan.IndexType Convert(this GAL.IndexType type)
+ {
+ switch (type)
+ {
+ case GAL.IndexType.UByte:
+ return Silk.NET.Vulkan.IndexType.Uint8Ext;
+ case GAL.IndexType.UShort:
+ return Silk.NET.Vulkan.IndexType.Uint16;
+ case GAL.IndexType.UInt:
+ return Silk.NET.Vulkan.IndexType.Uint32;
+ }
+
+ Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(GAL.IndexType)} enum value: {type}.");
+
+ return Silk.NET.Vulkan.IndexType.Uint16;
+ }
+
+ public static Filter Convert(this MagFilter filter)
+ {
+ switch (filter)
+ {
+ case MagFilter.Nearest:
+ return Filter.Nearest;
+ case MagFilter.Linear:
+ return Filter.Linear;
+ }
+
+ Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(MagFilter)} enum value: {filter}.");
+
+ return Filter.Nearest;
+ }
+
+ public static (Filter, SamplerMipmapMode) Convert(this MinFilter filter)
+ {
+ switch (filter)
+ {
+ case MinFilter.Nearest:
+ return (Filter.Nearest, SamplerMipmapMode.Nearest);
+ case MinFilter.Linear:
+ return (Filter.Linear, SamplerMipmapMode.Nearest);
+ case MinFilter.NearestMipmapNearest:
+ return (Filter.Nearest, SamplerMipmapMode.Nearest);
+ case MinFilter.LinearMipmapNearest:
+ return (Filter.Linear, SamplerMipmapMode.Nearest);
+ case MinFilter.NearestMipmapLinear:
+ return (Filter.Nearest, SamplerMipmapMode.Linear);
+ case MinFilter.LinearMipmapLinear:
+ return (Filter.Linear, SamplerMipmapMode.Linear);
+ }
+
+ Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(MinFilter)} enum value: {filter}.");
+
+ return (Filter.Nearest, SamplerMipmapMode.Nearest);
+ }
+
+ public static Silk.NET.Vulkan.PrimitiveTopology Convert(this GAL.PrimitiveTopology topology)
+ {
+ switch (topology)
+ {
+ case GAL.PrimitiveTopology.Points:
+ return Silk.NET.Vulkan.PrimitiveTopology.PointList;
+ case GAL.PrimitiveTopology.Lines:
+ return Silk.NET.Vulkan.PrimitiveTopology.LineList;
+ case GAL.PrimitiveTopology.LineStrip:
+ return Silk.NET.Vulkan.PrimitiveTopology.LineStrip;
+ case GAL.PrimitiveTopology.Triangles:
+ return Silk.NET.Vulkan.PrimitiveTopology.TriangleList;
+ case GAL.PrimitiveTopology.TriangleStrip:
+ return Silk.NET.Vulkan.PrimitiveTopology.TriangleStrip;
+ case GAL.PrimitiveTopology.TriangleFan:
+ return Silk.NET.Vulkan.PrimitiveTopology.TriangleFan;
+ case GAL.PrimitiveTopology.LinesAdjacency:
+ return Silk.NET.Vulkan.PrimitiveTopology.LineListWithAdjacency;
+ case GAL.PrimitiveTopology.LineStripAdjacency:
+ return Silk.NET.Vulkan.PrimitiveTopology.LineStripWithAdjacency;
+ case GAL.PrimitiveTopology.TrianglesAdjacency:
+ return Silk.NET.Vulkan.PrimitiveTopology.TriangleListWithAdjacency;
+ case GAL.PrimitiveTopology.TriangleStripAdjacency:
+ return Silk.NET.Vulkan.PrimitiveTopology.TriangleStripWithAdjacency;
+ case GAL.PrimitiveTopology.Patches:
+ return Silk.NET.Vulkan.PrimitiveTopology.PatchList;
+ case GAL.PrimitiveTopology.Quads: // Emulated with triangle fans.
+ return Silk.NET.Vulkan.PrimitiveTopology.TriangleFan;
+ case GAL.PrimitiveTopology.QuadStrip: // Emulated with triangle strips.
+ return Silk.NET.Vulkan.PrimitiveTopology.TriangleStrip;
+ }
+
+ Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(GAL.PrimitiveTopology)} enum value: {topology}.");
+
+ return Silk.NET.Vulkan.PrimitiveTopology.TriangleList;
+ }
+
+ public static Silk.NET.Vulkan.StencilOp Convert(this GAL.StencilOp op)
+ {
+ switch (op)
+ {
+ case GAL.StencilOp.Keep:
+ return Silk.NET.Vulkan.StencilOp.Keep;
+ case GAL.StencilOp.Zero:
+ return Silk.NET.Vulkan.StencilOp.Zero;
+ case GAL.StencilOp.Replace:
+ return Silk.NET.Vulkan.StencilOp.Replace;
+ case GAL.StencilOp.IncrementAndClamp:
+ return Silk.NET.Vulkan.StencilOp.IncrementAndClamp;
+ case GAL.StencilOp.DecrementAndClamp:
+ return Silk.NET.Vulkan.StencilOp.DecrementAndClamp;
+ case GAL.StencilOp.Invert:
+ return Silk.NET.Vulkan.StencilOp.Invert;
+ case GAL.StencilOp.IncrementAndWrap:
+ return Silk.NET.Vulkan.StencilOp.IncrementAndWrap;
+ case GAL.StencilOp.DecrementAndWrap:
+ return Silk.NET.Vulkan.StencilOp.DecrementAndWrap;
+ }
+
+ Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(GAL.StencilOp)} enum value: {op}.");
+
+ return Silk.NET.Vulkan.StencilOp.Keep;
+ }
+
+ public static ComponentSwizzle Convert(this SwizzleComponent swizzleComponent)
+ {
+ switch (swizzleComponent)
+ {
+ case SwizzleComponent.Zero:
+ return ComponentSwizzle.Zero;
+ case SwizzleComponent.One:
+ return ComponentSwizzle.One;
+ case SwizzleComponent.Red:
+ return ComponentSwizzle.R;
+ case SwizzleComponent.Green:
+ return ComponentSwizzle.G;
+ case SwizzleComponent.Blue:
+ return ComponentSwizzle.B;
+ case SwizzleComponent.Alpha:
+ return ComponentSwizzle.A;
+ }
+
+ Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(SwizzleComponent)} enum value: {swizzleComponent}.");
+
+ return ComponentSwizzle.Zero;
+ }
+
+ public static ImageType Convert(this Target target)
+ {
+ switch (target)
+ {
+ case Target.Texture1D:
+ case Target.Texture1DArray:
+ case Target.TextureBuffer:
+ return ImageType.ImageType1D;
+ case Target.Texture2D:
+ case Target.Texture2DArray:
+ case Target.Texture2DMultisample:
+ case Target.Cubemap:
+ case Target.CubemapArray:
+ return ImageType.ImageType2D;
+ case Target.Texture3D:
+ return ImageType.ImageType3D;
+ }
+
+ Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(Target)} enum value: {target}.");
+
+ return ImageType.ImageType2D;
+ }
+
+ public static ImageViewType ConvertView(this Target target)
+ {
+ switch (target)
+ {
+ case Target.Texture1D:
+ return ImageViewType.ImageViewType1D;
+ case Target.Texture2D:
+ case Target.Texture2DMultisample:
+ return ImageViewType.ImageViewType2D;
+ case Target.Texture3D:
+ return ImageViewType.ImageViewType3D;
+ case Target.Texture1DArray:
+ return ImageViewType.ImageViewType1DArray;
+ case Target.Texture2DArray:
+ return ImageViewType.ImageViewType2DArray;
+ case Target.Cubemap:
+ return ImageViewType.Cube;
+ case Target.CubemapArray:
+ return ImageViewType.CubeArray;
+ }
+
+ Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(Target)} enum value: {target}.");
+
+ return ImageViewType.ImageViewType2D;
+ }
+
+ public static ImageAspectFlags ConvertAspectFlags(this GAL.Format format)
+ {
+ switch (format)
+ {
+ case GAL.Format.D16Unorm:
+ case GAL.Format.D32Float:
+ return ImageAspectFlags.ImageAspectDepthBit;
+ case GAL.Format.S8Uint:
+
+ return ImageAspectFlags.ImageAspectStencilBit;
+ case GAL.Format.D24UnormS8Uint:
+ case GAL.Format.D32FloatS8Uint:
+ case GAL.Format.S8UintD24Unorm:
+ return ImageAspectFlags.ImageAspectDepthBit | ImageAspectFlags.ImageAspectStencilBit;
+ default:
+ return ImageAspectFlags.ImageAspectColorBit;
+ }
+ }
+
+ public static ImageAspectFlags ConvertAspectFlags(this GAL.Format format, DepthStencilMode depthStencilMode)
+ {
+ switch (format)
+ {
+ case GAL.Format.D16Unorm:
+ case GAL.Format.D32Float:
+ return ImageAspectFlags.ImageAspectDepthBit;
+ case GAL.Format.S8Uint:
+ return ImageAspectFlags.ImageAspectStencilBit;
+ case GAL.Format.D24UnormS8Uint:
+ case GAL.Format.D32FloatS8Uint:
+ case GAL.Format.S8UintD24Unorm:
+ return depthStencilMode == DepthStencilMode.Stencil ? ImageAspectFlags.ImageAspectStencilBit : ImageAspectFlags.ImageAspectDepthBit;
+ default:
+ return ImageAspectFlags.ImageAspectColorBit;
+ }
+ }
+
+ public static LogicOp Convert(this LogicalOp op)
+ {
+ switch (op)
+ {
+ case LogicalOp.Clear:
+ return LogicOp.Clear;
+ case LogicalOp.And:
+ return LogicOp.And;
+ case LogicalOp.AndReverse:
+ return LogicOp.AndReverse;
+ case LogicalOp.Copy:
+ return LogicOp.Copy;
+ case LogicalOp.AndInverted:
+ return LogicOp.AndInverted;
+ case LogicalOp.Noop:
+ return LogicOp.NoOp;
+ case LogicalOp.Xor:
+ return LogicOp.Xor;
+ case LogicalOp.Or:
+ return LogicOp.Or;
+ case LogicalOp.Nor:
+ return LogicOp.Nor;
+ case LogicalOp.Equiv:
+ return LogicOp.Equivalent;
+ case LogicalOp.Invert:
+ return LogicOp.Invert;
+ case LogicalOp.OrReverse:
+ return LogicOp.OrReverse;
+ case LogicalOp.CopyInverted:
+ return LogicOp.CopyInverted;
+ case LogicalOp.OrInverted:
+ return LogicOp.OrInverted;
+ case LogicalOp.Nand:
+ return LogicOp.Nand;
+ case LogicalOp.Set:
+ return LogicOp.Set;
+ }
+
+ Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(LogicalOp)} enum value: {op}.");
+
+ return LogicOp.Copy;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/FenceHelper.cs b/Ryujinx.Graphics.Vulkan/FenceHelper.cs
new file mode 100644
index 000000000..d6731c0eb
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/FenceHelper.cs
@@ -0,0 +1,30 @@
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ static class FenceHelper
+ {
+ private const ulong DefaultTimeout = 100000000; // 100ms
+
+ public static bool AnySignaled(Vk api, Device device, ReadOnlySpan fences, ulong timeout = 0)
+ {
+ return api.WaitForFences(device, (uint)fences.Length, fences, false, timeout) == Result.Success;
+ }
+
+ public static bool AllSignaled(Vk api, Device device, ReadOnlySpan fences, ulong timeout = 0)
+ {
+ return api.WaitForFences(device, (uint)fences.Length, fences, true, timeout) == Result.Success;
+ }
+
+ public static void WaitAllIndefinitely(Vk api, Device device, ReadOnlySpan fences)
+ {
+ Result result;
+ while ((result = api.WaitForFences(device, (uint)fences.Length, fences, true, DefaultTimeout)) == Result.Timeout)
+ {
+ // Keep waiting while the fence is not signaled.
+ }
+ result.ThrowOnError();
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/FenceHolder.cs b/Ryujinx.Graphics.Vulkan/FenceHolder.cs
new file mode 100644
index 000000000..ba5b18829
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/FenceHolder.cs
@@ -0,0 +1,73 @@
+using Silk.NET.Vulkan;
+using System;
+using System.Threading;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class FenceHolder : IDisposable
+ {
+ private readonly Vk _api;
+ private readonly Device _device;
+ private Fence _fence;
+ private int _referenceCount;
+ public bool _disposed;
+
+ public unsafe FenceHolder(Vk api, Device device)
+ {
+ _api = api;
+ _device = device;
+
+ var fenceCreateInfo = new FenceCreateInfo()
+ {
+ SType = StructureType.FenceCreateInfo
+ };
+
+ api.CreateFence(device, in fenceCreateInfo, null, out _fence).ThrowOnError();
+
+ _referenceCount = 1;
+ }
+
+ public Fence GetUnsafe()
+ {
+ return _fence;
+ }
+
+ public Fence Get()
+ {
+ Interlocked.Increment(ref _referenceCount);
+ return _fence;
+ }
+
+ public unsafe void Put()
+ {
+ if (Interlocked.Decrement(ref _referenceCount) == 0)
+ {
+ _api.DestroyFence(_device, _fence, null);
+ _fence = default;
+ }
+ }
+
+ public void Wait()
+ {
+ Span fences = stackalloc Fence[1];
+ fences[0] = _fence;
+ FenceHelper.WaitAllIndefinitely(_api, _device, fences);
+ }
+
+ public bool IsSignaled()
+ {
+ Span fences = stackalloc Fence[1];
+ fences[0] = _fence;
+ return FenceHelper.AllSignaled(_api, _device, fences);
+ }
+
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ Put();
+ _disposed = true;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs b/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs
new file mode 100644
index 000000000..456eb4ebe
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs
@@ -0,0 +1,75 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
+using System;
+using VkFormat = Silk.NET.Vulkan.Format;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class FormatCapabilities
+ {
+ private readonly FormatFeatureFlags[] _table;
+
+ private readonly Vk _api;
+ private readonly PhysicalDevice _physicalDevice;
+
+ public FormatCapabilities(Vk api, PhysicalDevice physicalDevice)
+ {
+ _api = api;
+ _physicalDevice = physicalDevice;
+ _table = new FormatFeatureFlags[Enum.GetNames(typeof(GAL.Format)).Length];
+ }
+
+ public bool FormatSupports(GAL.Format format, FormatFeatureFlags flags)
+ {
+ var formatFeatureFlags = _table[(int)format];
+
+ if (formatFeatureFlags == 0)
+ {
+ _api.GetPhysicalDeviceFormatProperties(_physicalDevice, FormatTable.GetFormat(format), out var fp);
+ formatFeatureFlags = fp.OptimalTilingFeatures;
+ _table[(int)format] = formatFeatureFlags;
+ }
+
+ return (formatFeatureFlags & flags) == flags;
+ }
+
+ public VkFormat ConvertToVkFormat(GAL.Format srcFormat)
+ {
+ var format = FormatTable.GetFormat(srcFormat);
+
+ var requiredFeatures = FormatFeatureFlags.FormatFeatureSampledImageBit |
+ FormatFeatureFlags.FormatFeatureTransferSrcBit |
+ FormatFeatureFlags.FormatFeatureTransferDstBit;
+
+ if (srcFormat.IsDepthOrStencil())
+ {
+ requiredFeatures |= FormatFeatureFlags.FormatFeatureDepthStencilAttachmentBit;
+ }
+ else if (srcFormat.IsRtColorCompatible())
+ {
+ requiredFeatures |= FormatFeatureFlags.FormatFeatureColorAttachmentBit;
+ }
+
+ if (srcFormat.IsImageCompatible())
+ {
+ requiredFeatures |= FormatFeatureFlags.FormatFeatureStorageImageBit;
+ }
+
+ if (!FormatSupports(srcFormat, requiredFeatures))
+ {
+ // The format is not supported. Can we convert it to a higher precision format?
+ if (srcFormat == GAL.Format.D24UnormS8Uint)
+ {
+ format = VkFormat.D32SfloatS8Uint;
+ }
+ else
+ {
+ Logger.Error?.Print(LogClass.Gpu, $"Format {srcFormat} is not supported by the host.");
+ }
+ }
+
+ return format;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/FormatConverter.cs b/Ryujinx.Graphics.Vulkan/FormatConverter.cs
new file mode 100644
index 000000000..e42bd778a
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/FormatConverter.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class FormatConverter
+ {
+ public unsafe static void ConvertD24S8ToD32FS8(Span output, ReadOnlySpan input)
+ {
+ const float UnormToFloat = 1f / 0xffffff;
+
+ Span outputUint = MemoryMarshal.Cast(output);
+ ReadOnlySpan inputUint = MemoryMarshal.Cast(input);
+
+ int i = 0;
+
+ for (; i < inputUint.Length; i++)
+ {
+ uint depthStencil = inputUint[i];
+ uint depth = depthStencil >> 8;
+ uint stencil = depthStencil & 0xff;
+
+ int j = i * 2;
+
+ outputUint[j] = (uint)BitConverter.SingleToInt32Bits(depth * UnormToFloat);
+ outputUint[j + 1] = stencil;
+ }
+ }
+
+ public unsafe static void ConvertD32FS8ToD24S8(Span output, ReadOnlySpan input)
+ {
+ Span outputUint = MemoryMarshal.Cast(output);
+ ReadOnlySpan inputUint = MemoryMarshal.Cast(input);
+
+ int i = 0;
+
+ for (; i < inputUint.Length; i += 2)
+ {
+ float depth = BitConverter.Int32BitsToSingle((int)inputUint[i]);
+ uint stencil = inputUint[i + 1];
+ uint depthStencil = (Math.Clamp((uint)(depth * 0xffffff), 0, 0xffffff) << 8) | (stencil & 0xff);
+
+ int j = i >> 1;
+
+ outputUint[j] = depthStencil;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/FormatTable.cs b/Ryujinx.Graphics.Vulkan/FormatTable.cs
new file mode 100644
index 000000000..6f1e0b54a
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/FormatTable.cs
@@ -0,0 +1,181 @@
+using Ryujinx.Graphics.GAL;
+using System;
+using VkFormat = Silk.NET.Vulkan.Format;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ static class FormatTable
+ {
+ private static readonly VkFormat[] Table;
+
+ static FormatTable()
+ {
+ Table = new VkFormat[Enum.GetNames(typeof(Format)).Length];
+
+ Add(Format.R8Unorm, VkFormat.R8Unorm);
+ Add(Format.R8Snorm, VkFormat.R8SNorm);
+ Add(Format.R8Uint, VkFormat.R8Uint);
+ Add(Format.R8Sint, VkFormat.R8Sint);
+ Add(Format.R16Float, VkFormat.R16Sfloat);
+ Add(Format.R16Unorm, VkFormat.R16Unorm);
+ Add(Format.R16Snorm, VkFormat.R16SNorm);
+ Add(Format.R16Uint, VkFormat.R16Uint);
+ Add(Format.R16Sint, VkFormat.R16Sint);
+ Add(Format.R32Float, VkFormat.R32Sfloat);
+ Add(Format.R32Uint, VkFormat.R32Uint);
+ Add(Format.R32Sint, VkFormat.R32Sint);
+ Add(Format.R8G8Unorm, VkFormat.R8G8Unorm);
+ Add(Format.R8G8Snorm, VkFormat.R8G8SNorm);
+ Add(Format.R8G8Uint, VkFormat.R8G8Uint);
+ Add(Format.R8G8Sint, VkFormat.R8G8Sint);
+ Add(Format.R16G16Float, VkFormat.R16G16Sfloat);
+ Add(Format.R16G16Unorm, VkFormat.R16G16Unorm);
+ Add(Format.R16G16Snorm, VkFormat.R16G16SNorm);
+ Add(Format.R16G16Uint, VkFormat.R16G16Uint);
+ Add(Format.R16G16Sint, VkFormat.R16G16Sint);
+ Add(Format.R32G32Float, VkFormat.R32G32Sfloat);
+ Add(Format.R32G32Uint, VkFormat.R32G32Uint);
+ Add(Format.R32G32Sint, VkFormat.R32G32Sint);
+ Add(Format.R8G8B8Unorm, VkFormat.R8G8B8Unorm);
+ Add(Format.R8G8B8Snorm, VkFormat.R8G8B8SNorm);
+ Add(Format.R8G8B8Uint, VkFormat.R8G8B8Uint);
+ Add(Format.R8G8B8Sint, VkFormat.R8G8B8Sint);
+ Add(Format.R16G16B16Float, VkFormat.R16G16B16Sfloat);
+ Add(Format.R16G16B16Unorm, VkFormat.R16G16B16Unorm);
+ Add(Format.R16G16B16Snorm, VkFormat.R16G16B16SNorm);
+ Add(Format.R16G16B16Uint, VkFormat.R16G16B16Uint);
+ Add(Format.R16G16B16Sint, VkFormat.R16G16B16Sint);
+ Add(Format.R32G32B32Float, VkFormat.R32G32B32Sfloat);
+ Add(Format.R32G32B32Uint, VkFormat.R32G32B32Uint);
+ Add(Format.R32G32B32Sint, VkFormat.R32G32B32Sint);
+ Add(Format.R8G8B8A8Unorm, VkFormat.R8G8B8A8Unorm);
+ Add(Format.R8G8B8A8Snorm, VkFormat.R8G8B8A8SNorm);
+ Add(Format.R8G8B8A8Uint, VkFormat.R8G8B8A8Uint);
+ Add(Format.R8G8B8A8Sint, VkFormat.R8G8B8A8Sint);
+ Add(Format.R16G16B16A16Float, VkFormat.R16G16B16A16Sfloat);
+ Add(Format.R16G16B16A16Unorm, VkFormat.R16G16B16A16Unorm);
+ Add(Format.R16G16B16A16Snorm, VkFormat.R16G16B16A16SNorm);
+ Add(Format.R16G16B16A16Uint, VkFormat.R16G16B16A16Uint);
+ Add(Format.R16G16B16A16Sint, VkFormat.R16G16B16A16Sint);
+ Add(Format.R32G32B32A32Float, VkFormat.R32G32B32A32Sfloat);
+ Add(Format.R32G32B32A32Uint, VkFormat.R32G32B32A32Uint);
+ Add(Format.R32G32B32A32Sint, VkFormat.R32G32B32A32Sint);
+ Add(Format.S8Uint, VkFormat.S8Uint);
+ Add(Format.D16Unorm, VkFormat.D16Unorm);
+ Add(Format.S8UintD24Unorm, VkFormat.D24UnormS8Uint);
+ Add(Format.D32Float, VkFormat.D32Sfloat);
+ Add(Format.D24UnormS8Uint, VkFormat.D24UnormS8Uint);
+ Add(Format.D32FloatS8Uint, VkFormat.D32SfloatS8Uint);
+ Add(Format.R8G8B8X8Srgb, VkFormat.R8G8B8Srgb);
+ Add(Format.R8G8B8A8Srgb, VkFormat.R8G8B8A8Srgb);
+ Add(Format.R4G4B4A4Unorm, VkFormat.R4G4B4A4UnormPack16);
+ Add(Format.R5G5B5X1Unorm, VkFormat.A1R5G5B5UnormPack16);
+ Add(Format.R5G5B5A1Unorm, VkFormat.A1R5G5B5UnormPack16);
+ Add(Format.R5G6B5Unorm, VkFormat.R5G6B5UnormPack16);
+ Add(Format.R10G10B10A2Unorm, VkFormat.A2B10G10R10UnormPack32);
+ Add(Format.R10G10B10A2Uint, VkFormat.A2B10G10R10UintPack32);
+ Add(Format.R11G11B10Float, VkFormat.B10G11R11UfloatPack32);
+ Add(Format.R9G9B9E5Float, VkFormat.E5B9G9R9UfloatPack32);
+ Add(Format.Bc1RgbaUnorm, VkFormat.BC1RgbaUnormBlock);
+ Add(Format.Bc2Unorm, VkFormat.BC2UnormBlock);
+ Add(Format.Bc3Unorm, VkFormat.BC3UnormBlock);
+ Add(Format.Bc1RgbaSrgb, VkFormat.BC1RgbaSrgbBlock);
+ Add(Format.Bc2Srgb, VkFormat.BC2SrgbBlock);
+ Add(Format.Bc3Srgb, VkFormat.BC3SrgbBlock);
+ Add(Format.Bc4Unorm, VkFormat.BC4UnormBlock);
+ Add(Format.Bc4Snorm, VkFormat.BC4SNormBlock);
+ Add(Format.Bc5Unorm, VkFormat.BC5UnormBlock);
+ Add(Format.Bc5Snorm, VkFormat.BC5SNormBlock);
+ Add(Format.Bc7Unorm, VkFormat.BC7UnormBlock);
+ Add(Format.Bc7Srgb, VkFormat.BC7SrgbBlock);
+ Add(Format.Bc6HSfloat, VkFormat.BC6HSfloatBlock);
+ Add(Format.Bc6HUfloat, VkFormat.BC6HUfloatBlock);
+ Add(Format.R8Uscaled, VkFormat.R8Uscaled);
+ Add(Format.R8Sscaled, VkFormat.R8Sscaled);
+ Add(Format.R16Uscaled, VkFormat.R16Uscaled);
+ Add(Format.R16Sscaled, VkFormat.R16Sscaled);
+ // Add(Format.R32Uscaled, VkFormat.R32Uscaled);
+ // Add(Format.R32Sscaled, VkFormat.R32Sscaled);
+ Add(Format.R8G8Uscaled, VkFormat.R8G8Uscaled);
+ Add(Format.R8G8Sscaled, VkFormat.R8G8Sscaled);
+ Add(Format.R16G16Uscaled, VkFormat.R16G16Uscaled);
+ Add(Format.R16G16Sscaled, VkFormat.R16G16Sscaled);
+ // Add(Format.R32G32Uscaled, VkFormat.R32G32Uscaled);
+ // Add(Format.R32G32Sscaled, VkFormat.R32G32Sscaled);
+ Add(Format.R8G8B8Uscaled, VkFormat.R8G8B8Uscaled);
+ Add(Format.R8G8B8Sscaled, VkFormat.R8G8B8Sscaled);
+ Add(Format.R16G16B16Uscaled, VkFormat.R16G16B16Uscaled);
+ Add(Format.R16G16B16Sscaled, VkFormat.R16G16B16Sscaled);
+ // Add(Format.R32G32B32Uscaled, VkFormat.R32G32B32Uscaled);
+ // Add(Format.R32G32B32Sscaled, VkFormat.R32G32B32Sscaled);
+ Add(Format.R8G8B8A8Uscaled, VkFormat.R8G8B8A8Uscaled);
+ Add(Format.R8G8B8A8Sscaled, VkFormat.R8G8B8A8Sscaled);
+ Add(Format.R16G16B16A16Uscaled, VkFormat.R16G16B16A16Uscaled);
+ Add(Format.R16G16B16A16Sscaled, VkFormat.R16G16B16A16Sscaled);
+ // Add(Format.R32G32B32A32Uscaled, VkFormat.R32G32B32A32Uscaled);
+ // Add(Format.R32G32B32A32Sscaled, VkFormat.R32G32B32A32Sscaled);
+ Add(Format.R10G10B10A2Snorm, VkFormat.A2B10G10R10SNormPack32);
+ Add(Format.R10G10B10A2Sint, VkFormat.A2B10G10R10SintPack32);
+ Add(Format.R10G10B10A2Uscaled, VkFormat.A2B10G10R10UscaledPack32);
+ Add(Format.R10G10B10A2Sscaled, VkFormat.A2B10G10R10SscaledPack32);
+ Add(Format.R8G8B8X8Unorm, VkFormat.R8G8B8Unorm);
+ Add(Format.R8G8B8X8Snorm, VkFormat.R8G8B8SNorm);
+ Add(Format.R8G8B8X8Uint, VkFormat.R8G8B8Uint);
+ Add(Format.R8G8B8X8Sint, VkFormat.R8G8B8Sint);
+ Add(Format.R16G16B16X16Float, VkFormat.R16G16B16Sfloat);
+ Add(Format.R16G16B16X16Unorm, VkFormat.R16G16B16Unorm);
+ Add(Format.R16G16B16X16Snorm, VkFormat.R16G16B16SNorm);
+ Add(Format.R16G16B16X16Uint, VkFormat.R16G16B16Uint);
+ Add(Format.R16G16B16X16Sint, VkFormat.R16G16B16Sint);
+ Add(Format.R32G32B32X32Float, VkFormat.R32G32B32Sfloat);
+ Add(Format.R32G32B32X32Uint, VkFormat.R32G32B32Uint);
+ Add(Format.R32G32B32X32Sint, VkFormat.R32G32B32Sint);
+ Add(Format.Astc4x4Unorm, VkFormat.Astc4x4UnormBlock);
+ Add(Format.Astc5x4Unorm, VkFormat.Astc5x4UnormBlock);
+ Add(Format.Astc5x5Unorm, VkFormat.Astc5x5UnormBlock);
+ Add(Format.Astc6x5Unorm, VkFormat.Astc6x5UnormBlock);
+ Add(Format.Astc6x6Unorm, VkFormat.Astc6x6UnormBlock);
+ Add(Format.Astc8x5Unorm, VkFormat.Astc8x5UnormBlock);
+ Add(Format.Astc8x6Unorm, VkFormat.Astc8x6UnormBlock);
+ Add(Format.Astc8x8Unorm, VkFormat.Astc8x8UnormBlock);
+ Add(Format.Astc10x5Unorm, VkFormat.Astc10x5UnormBlock);
+ Add(Format.Astc10x6Unorm, VkFormat.Astc10x6UnormBlock);
+ Add(Format.Astc10x8Unorm, VkFormat.Astc10x8UnormBlock);
+ Add(Format.Astc10x10Unorm, VkFormat.Astc10x10UnormBlock);
+ Add(Format.Astc12x10Unorm, VkFormat.Astc12x10UnormBlock);
+ Add(Format.Astc12x12Unorm, VkFormat.Astc12x12UnormBlock);
+ Add(Format.Astc4x4Srgb, VkFormat.Astc4x4SrgbBlock);
+ Add(Format.Astc5x4Srgb, VkFormat.Astc5x4SrgbBlock);
+ Add(Format.Astc5x5Srgb, VkFormat.Astc5x5SrgbBlock);
+ Add(Format.Astc6x5Srgb, VkFormat.Astc6x5SrgbBlock);
+ Add(Format.Astc6x6Srgb, VkFormat.Astc6x6SrgbBlock);
+ Add(Format.Astc8x5Srgb, VkFormat.Astc8x5SrgbBlock);
+ Add(Format.Astc8x6Srgb, VkFormat.Astc8x6SrgbBlock);
+ Add(Format.Astc8x8Srgb, VkFormat.Astc8x8SrgbBlock);
+ Add(Format.Astc10x5Srgb, VkFormat.Astc10x5SrgbBlock);
+ Add(Format.Astc10x6Srgb, VkFormat.Astc10x6SrgbBlock);
+ Add(Format.Astc10x8Srgb, VkFormat.Astc10x8SrgbBlock);
+ Add(Format.Astc10x10Srgb, VkFormat.Astc10x10SrgbBlock);
+ Add(Format.Astc12x10Srgb, VkFormat.Astc12x10SrgbBlock);
+ Add(Format.Astc12x12Srgb, VkFormat.Astc12x12SrgbBlock);
+ Add(Format.B5G6R5Unorm, VkFormat.R5G6B5UnormPack16);
+ Add(Format.B5G5R5X1Unorm, VkFormat.A1R5G5B5UnormPack16);
+ Add(Format.B5G5R5A1Unorm, VkFormat.A1R5G5B5UnormPack16);
+ Add(Format.A1B5G5R5Unorm, VkFormat.R5G5B5A1UnormPack16);
+ Add(Format.B8G8R8X8Unorm, VkFormat.B8G8R8Unorm);
+ Add(Format.B8G8R8A8Unorm, VkFormat.B8G8R8A8Unorm);
+ Add(Format.B8G8R8X8Srgb, VkFormat.B8G8R8Srgb);
+ Add(Format.B8G8R8A8Srgb, VkFormat.B8G8R8A8Srgb);
+ }
+
+ private static void Add(Format format, VkFormat vkFormat)
+ {
+ Table[(int)format] = vkFormat;
+ }
+
+ public static VkFormat GetFormat(Format format)
+ {
+ return Table[(int)format];
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/FramebufferParams.cs b/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
new file mode 100644
index 000000000..817c26bb4
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
@@ -0,0 +1,153 @@
+using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
+using System;
+using System.Linq;
+using VkFormat = Silk.NET.Vulkan.Format;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class FramebufferParams
+ {
+ private readonly Device _device;
+ private readonly Auto[] _attachments;
+
+ public uint Width { get; }
+ public uint Height { get; }
+ public uint Layers { get; }
+
+ public VkFormat[] AttachmentFormats { get; }
+ public int[] AttachmentIndices { get; }
+
+ public int AttachmentsCount { get; }
+ public int MaxColorAttachmentIndex { get; }
+ public bool HasDepthStencil { get; }
+ public int ColorAttachmentsCount => AttachmentsCount - (HasDepthStencil ? 1 : 0);
+
+ public FramebufferParams(
+ Device device,
+ Auto view,
+ uint width,
+ uint height,
+ bool isDepthStencil,
+ VkFormat format)
+ {
+ _device = device;
+ _attachments = new[] { view };
+
+ Width = width;
+ Height = height;
+ Layers = 1;
+
+ AttachmentFormats = new[] { format };
+ AttachmentIndices = new[] { 0 };
+
+ AttachmentsCount = 1;
+
+ HasDepthStencil = isDepthStencil;
+ }
+
+ public FramebufferParams(Device device, ITexture[] colors, ITexture depthStencil)
+ {
+ _device = device;
+
+ int colorsCount = colors.Count(IsValidTextureView);
+
+ int count = colorsCount + (IsValidTextureView(depthStencil) ? 1 : 0);
+
+ _attachments = new Auto[count];
+
+ AttachmentFormats = new VkFormat[count];
+ AttachmentIndices = new int[count];
+ MaxColorAttachmentIndex = colors.Length - 1;
+
+ uint width = uint.MaxValue;
+ uint height = uint.MaxValue;
+ uint layers = uint.MaxValue;
+
+ int index = 0;
+ int bindIndex = 0;
+
+ foreach (ITexture color in colors)
+ {
+ if (IsValidTextureView(color))
+ {
+ var texture = (TextureView)color;
+
+ _attachments[index] = texture.GetImageViewForAttachment();
+
+ AttachmentFormats[index] = texture.VkFormat;
+ AttachmentIndices[index] = bindIndex;
+
+ width = Math.Min(width, (uint)texture.Width);
+ height = Math.Min(height, (uint)texture.Height);
+ layers = Math.Min(layers, (uint)texture.Layers);
+
+ if (++index >= colorsCount)
+ {
+ break;
+ }
+ }
+
+ bindIndex++;
+ }
+
+ if (depthStencil is TextureView dsTexture && dsTexture.Valid)
+ {
+ _attachments[count - 1] = dsTexture.GetImageViewForAttachment();
+
+ AttachmentFormats[count - 1] = dsTexture.VkFormat;
+
+ width = Math.Min(width, (uint)dsTexture.Width);
+ height = Math.Min(height, (uint)dsTexture.Height);
+ layers = Math.Min(layers, (uint)dsTexture.Layers);
+
+ HasDepthStencil = true;
+ }
+
+ if (count == 0)
+ {
+ width = height = layers = 1;
+ }
+
+ Width = width;
+ Height = height;
+ Layers = layers;
+
+ AttachmentsCount = count;
+ }
+
+ private static bool IsValidTextureView(ITexture texture)
+ {
+ return texture is TextureView view && view.Valid;
+ }
+
+ public ClearRect GetClearRect()
+ {
+ return new ClearRect(new Rect2D(null, new Extent2D(Width, Height)), 0, Layers);
+ }
+
+ public unsafe Auto Create(Vk api, CommandBufferScoped cbs, Auto