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 renderPass) + { + ImageView* attachments = stackalloc ImageView[_attachments.Length]; + + for (int i = 0; i < _attachments.Length; i++) + { + attachments[i] = _attachments[i].Get(cbs).Value; + } + + var framebufferCreateInfo = new FramebufferCreateInfo() + { + SType = StructureType.FramebufferCreateInfo, + RenderPass = renderPass.Get(cbs).Value, + AttachmentCount = (uint)_attachments.Length, + PAttachments = attachments, + Width = Width, + Height = Height, + Layers = Layers + }; + + api.CreateFramebuffer(_device, framebufferCreateInfo, null, out var framebuffer).ThrowOnError(); + return new Auto(new DisposableFramebuffer(api, _device, framebuffer), null, _attachments); + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs b/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs new file mode 100644 index 000000000..d387ee024 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Graphics.Vulkan +{ + struct HardwareCapabilities + { + public bool SupportsConditionalRendering { get; } + public bool SupportsExtendedDynamicState { get; } + + public HardwareCapabilities( + bool supportsConditionalRendering, + bool supportsExtendedDynamicState) + { + SupportsConditionalRendering = supportsConditionalRendering; + SupportsExtendedDynamicState = supportsExtendedDynamicState; + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/HashTableSlim.cs b/Ryujinx.Graphics.Vulkan/HashTableSlim.cs new file mode 100644 index 000000000..195b6848b --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/HashTableSlim.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Vulkan +{ + interface IRefEquatable + { + bool Equals(ref T other); + } + + class HashTableSlim where K : IRefEquatable + { + private const int TotalBuckets = 16; // Must be power of 2 + private const int TotalBucketsMask = TotalBuckets - 1; + + private struct Entry + { + public K Key; + public V Value; + } + + private readonly Entry[][] _hashTable = new Entry[TotalBuckets][]; + + public IEnumerable Keys + { + get + { + foreach (Entry[] bucket in _hashTable) + { + foreach (Entry entry in bucket) + { + yield return entry.Key; + } + } + } + } + + public IEnumerable Values + { + get + { + foreach (Entry[] bucket in _hashTable) + { + if (bucket != null) + { + foreach (Entry entry in bucket) + { + yield return entry.Value; + } + } + } + } + } + + public void Add(ref K key, V value) + { + var entry = new Entry() + { + Key = key, + Value = value + }; + + int hashCode = key.GetHashCode(); + int bucketIndex = hashCode & TotalBucketsMask; + + var bucket = _hashTable[bucketIndex]; + if (bucket != null) + { + int index = bucket.Length; + + Array.Resize(ref _hashTable[bucketIndex], index + 1); + + _hashTable[bucketIndex][index] = entry; + } + else + { + _hashTable[bucketIndex] = new Entry[] + { + entry + }; + } + } + + public bool TryGetValue(ref K key, out V value) + { + int hashCode = key.GetHashCode(); + + /* for (int i = 0; i < _hashTable.Length; i++) + { + var b = _hashTable[i]; + if (b != null) + { + System.Console.WriteLine(typeof(K).Name + " " + i + " " + b.Length); + } + } */ + + var bucket = _hashTable[hashCode & TotalBucketsMask]; + if (bucket != null) + { + + for (int i = 0; i < bucket.Length; i++) + { + ref var entry = ref bucket[i]; + + if (entry.Key.Equals(ref key)) + { + value = entry.Value; + return true; + } + } + } + + value = default; + return false; + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/HelperShader.cs b/Ryujinx.Graphics.Vulkan/HelperShader.cs new file mode 100644 index 000000000..922db9172 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/HelperShader.cs @@ -0,0 +1,261 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; +using Silk.NET.Vulkan; +using System; +using VkFormat = Silk.NET.Vulkan.Format; + +namespace Ryujinx.Graphics.Vulkan +{ + class HelperShader : IDisposable + { + private const string VertexShaderSource = @"#version 450 core + +layout (std140, binding = 1) uniform tex_coord_in +{ + vec4 tex_coord_in_data; +}; + +layout (location = 0) out vec2 tex_coord; + +void main() +{ + int low = gl_VertexIndex & 1; + int high = gl_VertexIndex >> 1; + tex_coord.x = tex_coord_in_data[low]; + tex_coord.y = tex_coord_in_data[2 + high]; + gl_Position.x = (float(low) - 0.5f) * 2.0f; + gl_Position.y = (float(high) - 0.5f) * 2.0f; + gl_Position.z = 0.0f; + gl_Position.w = 1.0f; +}"; + + private const string ColorBlitFragmentShaderSource = @"#version 450 core + +layout (binding = 32, set = 2) uniform sampler2D tex; + +layout (location = 0) in vec2 tex_coord; +layout (location = 0) out vec4 colour; + +void main() +{ + colour = texture(tex, tex_coord); +}"; + + private const string ClearAlphaFragmentShaderSource = @"#version 450 core + +layout (binding = 32, set = 2) uniform sampler2D tex; + +layout (location = 0) in vec2 tex_coord; +layout (location = 0) out vec4 colour; + +void main() +{ + colour = vec4(texture(tex, tex_coord).rgb, 1.0f); +}"; + + private readonly PipelineBlit _pipeline; + private readonly ISampler _samplerLinear; + private readonly ISampler _samplerNearest; + private readonly IProgram _programColorBlit; + private readonly IProgram _programClearAlpha; + + public HelperShader(VulkanGraphicsDevice gd, Device device) + { + _pipeline = new PipelineBlit(gd, device); + + static GAL.SamplerCreateInfo GetSamplerCreateInfo(MinFilter minFilter, MagFilter magFilter) + { + return new GAL.SamplerCreateInfo( + minFilter, + magFilter, + false, + AddressMode.ClampToEdge, + AddressMode.ClampToEdge, + AddressMode.ClampToEdge, + CompareMode.None, + GAL.CompareOp.Always, + new ColorF(0f, 0f, 0f, 0f), + 0f, + 0f, + 0f, + 1f); + } + + _samplerLinear = gd.CreateSampler(GetSamplerCreateInfo(MinFilter.Linear, MagFilter.Linear)); + _samplerNearest = gd.CreateSampler(GetSamplerCreateInfo(MinFilter.Nearest, MagFilter.Nearest)); + + var vertexBindings = new ShaderBindings( + new[] { 1 }, + Array.Empty(), + Array.Empty(), + Array.Empty(), + Array.Empty(), + Array.Empty()); + + var fragmentBindings = new ShaderBindings( + Array.Empty(), + Array.Empty(), + new[] { 32 }, + Array.Empty(), + Array.Empty(), + Array.Empty()); + + var vertexShader = gd.CompileShader(ShaderStage.Vertex, vertexBindings, VertexShaderSource); + var fragmentShaderColorBlit = gd.CompileShader(ShaderStage.Fragment, fragmentBindings, ColorBlitFragmentShaderSource); + var fragmentShaderClearAlpha = gd.CompileShader(ShaderStage.Fragment, fragmentBindings, ClearAlphaFragmentShaderSource); + + _programColorBlit = gd.CreateProgram(new[] { vertexShader, fragmentShaderColorBlit }, new ShaderInfo(-1)); + _programClearAlpha = gd.CreateProgram(new[] { vertexShader, fragmentShaderClearAlpha }, new ShaderInfo(-1)); + } + + public void Blit( + VulkanGraphicsDevice gd, + TextureView src, + Auto dst, + int dstWidth, + int dstHeight, + VkFormat dstFormat, + Extents2D srcRegion, + Extents2D dstRegion, + bool linearFilter, + bool clearAlpha = false) + { + gd.FlushAllCommands(); + + using var cbs = gd.CommandBufferPool.Rent(); + + Blit(gd, cbs, src, dst, dstWidth, dstHeight, dstFormat, srcRegion, dstRegion, linearFilter, clearAlpha); + } + + public void Blit( + VulkanGraphicsDevice gd, + CommandBufferScoped cbs, + TextureView src, + Auto dst, + int dstWidth, + int dstHeight, + VkFormat dstFormat, + Extents2D srcRegion, + Extents2D dstRegion, + bool linearFilter, + bool clearAlpha = false) + { + _pipeline.SetCommandBuffer(cbs); + + const int RegionBufferSize = 16; + + var sampler = linearFilter ? _samplerLinear : _samplerNearest; + + _pipeline.SetTextureAndSampler(32, src, sampler); + + Span region = stackalloc float[RegionBufferSize / sizeof(float)]; + + region[0] = (float)srcRegion.X1 / src.Width; + region[1] = (float)srcRegion.X2 / src.Width; + region[2] = (float)srcRegion.Y1 / src.Height; + region[3] = (float)srcRegion.Y2 / src.Height; + + var bufferHandle = gd.BufferManager.CreateWithHandle(gd, RegionBufferSize); + + gd.BufferManager.SetData(bufferHandle, 0, region); + + Span bufferRanges = stackalloc BufferRange[1]; + + bufferRanges[0] = new BufferRange(bufferHandle, 0, RegionBufferSize); + + _pipeline.SetUniformBuffers(1, bufferRanges); + + Span viewports = stackalloc GAL.Viewport[1]; + + viewports[0] = new GAL.Viewport( + new Rectangle(dstRegion.X1, dstRegion.Y1, dstRegion.X2 - dstRegion.X1, dstRegion.Y2 - dstRegion.Y1), + ViewportSwizzle.PositiveX, + ViewportSwizzle.PositiveY, + ViewportSwizzle.PositiveZ, + ViewportSwizzle.PositiveW, + 0f, + 1f); + + Span> scissors = stackalloc Rectangle[1]; + + scissors[0] = new Rectangle(0, 0, dstWidth, dstHeight); + + _pipeline.SetProgram(clearAlpha ? _programClearAlpha : _programColorBlit); + _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, false, dstFormat); + _pipeline.SetRenderTargetColorMasks(new uint[] { 0xf }); + + if (clearAlpha) + { + _pipeline.ClearRenderTargetColor(0, 0xf, new ColorF(0f, 0f, 0f, 1f)); + } + + _pipeline.SetViewports(0, viewports); + _pipeline.SetScissors(scissors); + _pipeline.SetPrimitiveTopology(GAL.PrimitiveTopology.TriangleStrip); + _pipeline.Draw(4, 1, 0, 0); + _pipeline.Finish(); + + gd.BufferManager.Delete(bufferHandle); + } + + public unsafe void ConvertI8ToI16(VulkanGraphicsDevice gd, CommandBufferScoped cbs, BufferHolder src, BufferHolder dst, int srcOffset, int size) + { + // TODO: Do this with a compute shader? + var srcBuffer = src.GetBuffer().Get(cbs, srcOffset, size).Value; + var dstBuffer = dst.GetBuffer().Get(cbs, 0, size * 2).Value; + + gd.Api.CmdFillBuffer(cbs.CommandBuffer, dstBuffer, 0, Vk.WholeSize, 0); + + var bufferCopy = new BufferCopy[size]; + + for (ulong i = 0; i < (ulong)size; i++) + { + bufferCopy[i] = new BufferCopy((ulong)srcOffset + i, i * 2, 1); + } + + BufferHolder.InsertBufferBarrier( + gd, + cbs.CommandBuffer, + dstBuffer, + BufferHolder.DefaultAccessFlags, + AccessFlags.AccessTransferWriteBit, + PipelineStageFlags.PipelineStageAllCommandsBit, + PipelineStageFlags.PipelineStageTransferBit, + 0, + size * 2); + + fixed (BufferCopy* pBufferCopy = bufferCopy) + { + gd.Api.CmdCopyBuffer(cbs.CommandBuffer, srcBuffer, dstBuffer, (uint)size, pBufferCopy); + } + + BufferHolder.InsertBufferBarrier( + gd, + cbs.CommandBuffer, + dstBuffer, + AccessFlags.AccessTransferWriteBit, + BufferHolder.DefaultAccessFlags, + PipelineStageFlags.PipelineStageTransferBit, + PipelineStageFlags.PipelineStageAllCommandsBit, + 0, + size * 2); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _programClearAlpha.Dispose(); + _programColorBlit.Dispose(); + _samplerNearest.Dispose(); + _samplerLinear.Dispose(); + _pipeline.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/MemoryAllocation.cs b/Ryujinx.Graphics.Vulkan/MemoryAllocation.cs new file mode 100644 index 000000000..04956e36a --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/MemoryAllocation.cs @@ -0,0 +1,37 @@ +using Silk.NET.Vulkan; +using System; + +namespace Ryujinx.Graphics.Vulkan +{ + struct MemoryAllocation : IDisposable + { + private readonly MemoryAllocatorBlockList _owner; + private readonly MemoryAllocatorBlockList.Block _block; + + public DeviceMemory Memory { get; } + public IntPtr HostPointer { get;} + public ulong Offset { get; } + public ulong Size { get; } + + public MemoryAllocation( + MemoryAllocatorBlockList owner, + MemoryAllocatorBlockList.Block block, + DeviceMemory memory, + IntPtr hostPointer, + ulong offset, + ulong size) + { + _owner = owner; + _block = block; + Memory = memory; + HostPointer = hostPointer; + Offset = offset; + Size = size; + } + + public void Dispose() + { + _owner.Free(_block, Offset, Size); + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/MemoryAllocator.cs b/Ryujinx.Graphics.Vulkan/MemoryAllocator.cs new file mode 100644 index 000000000..2414aafd1 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/MemoryAllocator.cs @@ -0,0 +1,84 @@ +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Vulkan +{ + class MemoryAllocator : IDisposable + { + private ulong MaxDeviceMemoryUsageEstimate = 16UL * 1024 * 1024 * 1024; + + private readonly Vk _api; + private readonly Device _device; + private readonly List _blockLists; + + private int _blockAlignment; + + public MemoryAllocator(Vk api, Device device, uint maxMemoryAllocationCount) + { + _api = api; + _device = device; + _blockLists = new List(); + _blockAlignment = (int)Math.Min(int.MaxValue, MaxDeviceMemoryUsageEstimate / (ulong)maxMemoryAllocationCount); + } + + public MemoryAllocation AllocateDeviceMemory( + PhysicalDevice physicalDevice, + MemoryRequirements requirements, + MemoryPropertyFlags flags = 0) + { + int memoryTypeIndex = FindSuitableMemoryTypeIndex(_api, physicalDevice, requirements.MemoryTypeBits, flags); + if (memoryTypeIndex < 0) + { + return default; + } + + bool map = flags.HasFlag(MemoryPropertyFlags.MemoryPropertyHostVisibleBit); + return Allocate(memoryTypeIndex, requirements.Size, requirements.Alignment, map); + } + + private MemoryAllocation Allocate(int memoryTypeIndex, ulong size, ulong alignment, bool map) + { + for (int i = 0; i < _blockLists.Count; i++) + { + var bl = _blockLists[i]; + if (bl.MemoryTypeIndex == memoryTypeIndex) + { + lock (bl) + { + return bl.Allocate(size, alignment, map); + } + } + } + + var newBl = new MemoryAllocatorBlockList(_api, _device, memoryTypeIndex, _blockAlignment); + _blockLists.Add(newBl); + return newBl.Allocate(size, alignment, map); + } + + private static int FindSuitableMemoryTypeIndex(Vk api, PhysicalDevice physicalDevice, uint memoryTypeBits, MemoryPropertyFlags flags) + { + api.GetPhysicalDeviceMemoryProperties(physicalDevice, out var properties); + + for (int i = 0; i < properties.MemoryTypeCount; i++) + { + var type = properties.MemoryTypes[i]; + + if ((memoryTypeBits & (1 << i)) != 0 && type.PropertyFlags.HasFlag(flags)) + { + return i; + } + } + + return -1; + } + + public unsafe void Dispose() + { + for (int i = 0; i < _blockLists.Count; i++) + { + _blockLists[i].Dispose(); + } + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/MemoryAllocatorBlockList.cs b/Ryujinx.Graphics.Vulkan/MemoryAllocatorBlockList.cs new file mode 100644 index 000000000..53deaa4ec --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/MemoryAllocatorBlockList.cs @@ -0,0 +1,280 @@ +using Ryujinx.Common; +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Ryujinx.Graphics.Vulkan +{ + class MemoryAllocatorBlockList : IDisposable + { + private const ulong InvalidOffset = ulong.MaxValue; + + public class Block : IComparable + { + public DeviceMemory Memory { get; private set; } + public IntPtr HostPointer { get; private set; } + public ulong Size { get; } + public bool Maped => HostPointer != IntPtr.Zero; + + private struct Range : IComparable + { + public ulong Offset { get; } + public ulong Size { get; } + + public Range(ulong offset, ulong size) + { + Offset = offset; + Size = size; + } + + public int CompareTo(Range other) + { + return Offset.CompareTo(other.Offset); + } + } + + private readonly List _freeRanges; + + public Block(DeviceMemory memory, IntPtr hostPointer, ulong size) + { + Memory = memory; + HostPointer = hostPointer; + Size = size; + _freeRanges = new List + { + new Range(0, size) + }; + } + + public ulong Allocate(ulong size, ulong alignment) + { + for (int i = 0; i < _freeRanges.Count; i++) + { + var range = _freeRanges[i]; + + ulong alignedOffset = BitUtils.AlignUp(range.Offset, (int)alignment); + ulong sizeDelta = alignedOffset - range.Offset; + ulong usableSize = range.Size - sizeDelta; + + if (sizeDelta < range.Size && usableSize >= size) + { + _freeRanges.RemoveAt(i); + + if (sizeDelta != 0) + { + InsertFreeRange(range.Offset, sizeDelta); + } + + ulong endOffset = range.Offset + range.Size; + ulong remainingSize = endOffset - (alignedOffset + size); + if (remainingSize != 0) + { + InsertFreeRange(endOffset - remainingSize, remainingSize); + } + + return alignedOffset; + } + } + + return InvalidOffset; + } + + public void Free(ulong offset, ulong size) + { + InsertFreeRangeComingled(offset, size); + } + + private void InsertFreeRange(ulong offset, ulong size) + { + var range = new Range(offset, size); + int index = _freeRanges.BinarySearch(range); + if (index < 0) + { + index = ~index; + } + + _freeRanges.Insert(index, range); + } + + private void InsertFreeRangeComingled(ulong offset, ulong size) + { + ulong endOffset = offset + size; + var range = new Range(offset, size); + int index = _freeRanges.BinarySearch(range); + if (index < 0) + { + index = ~index; + } + + if (index < _freeRanges.Count && _freeRanges[index].Offset == endOffset) + { + endOffset = _freeRanges[index].Offset + _freeRanges[index].Size; + _freeRanges.RemoveAt(index); + } + + if (index > 0 && _freeRanges[index - 1].Offset + _freeRanges[index - 1].Size == offset) + { + offset = _freeRanges[index - 1].Offset; + _freeRanges.RemoveAt(--index); + } + + range = new Range(offset, endOffset - offset); + + _freeRanges.Insert(index, range); + } + + public bool IsTotallyFree() + { + if (_freeRanges.Count == 1 && _freeRanges[0].Size == Size) + { + Debug.Assert(_freeRanges[0].Offset == 0); + return true; + } + + return false; + } + + public int CompareTo(Block other) + { + return Size.CompareTo(other.Size); + } + + public unsafe void Destroy(Vk api, Device device) + { + if (Maped) + { + api.UnmapMemory(device, Memory); + HostPointer = IntPtr.Zero; + } + + if (Memory.Handle != 0) + { + api.FreeMemory(device, Memory, null); + Memory = default; + } + } + } + + private readonly List _blocks; + + private readonly Vk _api; + private readonly Device _device; + + public int MemoryTypeIndex { get; } + + private readonly int _blockAlignment; + + public MemoryAllocatorBlockList(Vk api, Device device, int memoryTypeIndex, int blockAlignment) + { + _blocks = new List(); + _api = api; + _device = device; + MemoryTypeIndex = memoryTypeIndex; + _blockAlignment = blockAlignment; + } + + public unsafe MemoryAllocation Allocate(ulong size, ulong alignment, bool map) + { + // Ensure we have a sane alignment value. + if ((ulong)(int)alignment != alignment || (int)alignment <= 0) + { + throw new ArgumentOutOfRangeException(nameof(alignment), $"Invalid alignment 0x{alignment:X}."); + } + + for (int i = 0; i < _blocks.Count; i++) + { + var block = _blocks[i]; + + if (block.Maped == map && block.Size >= size) + { + ulong offset = block.Allocate(size, alignment); + if (offset != InvalidOffset) + { + return new MemoryAllocation(this, block, block.Memory, GetHostPointer(block, offset), offset, size); + } + } + } + + ulong blockAlignedSize = BitUtils.AlignUp(size, _blockAlignment); + + var memoryAllocateInfo = new MemoryAllocateInfo() + { + SType = StructureType.MemoryAllocateInfo, + AllocationSize = blockAlignedSize, + MemoryTypeIndex = (uint)MemoryTypeIndex + }; + + _api.AllocateMemory(_device, memoryAllocateInfo, null, out var deviceMemory).ThrowOnError(); + + IntPtr hostPointer = IntPtr.Zero; + + if (map) + { + unsafe + { + void* pointer = null; + _api.MapMemory(_device, deviceMemory, 0, blockAlignedSize, 0, ref pointer).ThrowOnError(); + hostPointer = (IntPtr)pointer; + } + } + + var newBlock = new Block(deviceMemory, hostPointer, blockAlignedSize); + + InsertBlock(newBlock); + + ulong newBlockOffset = newBlock.Allocate(size, alignment); + Debug.Assert(newBlockOffset != InvalidOffset); + + return new MemoryAllocation(this, newBlock, deviceMemory, GetHostPointer(newBlock, newBlockOffset), newBlockOffset, size); + } + + private static IntPtr GetHostPointer(Block block, ulong offset) + { + if (block.HostPointer == IntPtr.Zero) + { + return IntPtr.Zero; + } + + return (IntPtr)((nuint)(nint)block.HostPointer + offset); + } + + public unsafe void Free(Block block, ulong offset, ulong size) + { + block.Free(offset, size); + + if (block.IsTotallyFree()) + { + for (int i = 0; i < _blocks.Count; i++) + { + if (_blocks[i] == block) + { + _blocks.RemoveAt(i); + break; + } + } + + block.Destroy(_api, _device); + } + } + + private void InsertBlock(Block block) + { + int index = _blocks.BinarySearch(block); + if (index < 0) + { + index = ~index; + } + + _blocks.Insert(index, block); + } + + public unsafe void Dispose() + { + for (int i = 0; i < _blocks.Count; i++) + { + _blocks[i].Destroy(_api, _device); + } + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs b/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs new file mode 100644 index 000000000..9e5b7809f --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs @@ -0,0 +1,203 @@ +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.Graphics.Vulkan +{ + /// + /// Holder for multiple host GPU fences. + /// + class MultiFenceHolder + { + private readonly Dictionary _fences; + private BufferRangeList _rangeList; + + /// + /// Creates a new instance of the multiple fence holder. + /// + public MultiFenceHolder() + { + _fences = new Dictionary(); + _rangeList.Initialize(); + } + + + public void AddBufferUse(int cbIndex, int offset, int size) + { + if (VulkanConfiguration.UseGranularBufferTracking) + { + _rangeList.Add(cbIndex, offset, size); + } + } + + public void RemoveBufferUses(int cbIndex) + { + if (VulkanConfiguration.UseGranularBufferTracking) + { + _rangeList.Clear(cbIndex); + } + } + + public bool IsBufferRangeInUse(int cbIndex, int offset, int size) + { + if (VulkanConfiguration.UseGranularBufferTracking) + { + return _rangeList.OverlapsWith(cbIndex, offset, size); + } + else + { + return true; + } + } + + /// + /// Adds a fence to the holder. + /// + /// Command buffer index of the command buffer that owns the fence + /// Fence to be added + public void AddFence(int cbIndex, FenceHolder fence) + { + lock (_fences) + { + _fences.TryAdd(fence, cbIndex); + } + } + + /// + /// Removes a fence from the holder. + /// + /// Command buffer index of the command buffer that owns the fence + /// Fence to be removed + public void RemoveFence(int cbIndex, FenceHolder fence) + { + lock (_fences) + { + _fences.Remove(fence); + } + } + + /// + /// Wait until all the fences on the holder are signaled. + /// + /// + /// GPU device that the fences belongs to + public void WaitForFences(Vk api, Device device) + { + WaitForFencesImpl(api, device, 0, 0, false, 0UL); + } + + /// + /// Wait until all the fences on the holder with buffer uses overlapping the specified range are signaled. + /// + /// + /// + /// + /// + public void WaitForFences(Vk api, Device device, int offset, int size) + { + WaitForFencesImpl(api, device, offset, size, false, 0UL); + } + + /// + /// Wait until all the fences on the holder are signaled, or the timeout expires. + /// + /// + /// GPU device that the fences belongs to + /// Timeout in nanoseconds + /// True if all fences were signaled, false otherwise + public bool WaitForFences(Vk api, Device device, ulong timeout) + { + return WaitForFencesImpl(api, device, 0, 0, true, timeout); + } + + private bool WaitForFencesImpl(Vk api, Device device, int offset, int size, bool hasTimeout, ulong timeout) + { + FenceHolder[] fenceHolders; + Fence[] fences; + + lock (_fences) + { + fenceHolders = size != 0 && VulkanConfiguration.UseGranularBufferTracking ? GetOverlappingFences(offset, size) : _fences.Keys.ToArray(); + fences = new Fence[fenceHolders.Length]; + + for (int i = 0; i < fenceHolders.Length; i++) + { + fences[i] = fenceHolders[i].Get(); + } + } + + if (fences.Length == 0) + { + return true; + } + + bool signaled = true; + + if (hasTimeout) + { + signaled = FenceHelper.AllSignaled(api, device, fences, timeout); + } + else + { + FenceHelper.WaitAllIndefinitely(api, device, fences); + } + + for (int i = 0; i < fenceHolders.Length; i++) + { + fenceHolders[i].Put(); + } + + return signaled; + } + + public bool MayWait(Vk api, Device device, int offset, int size) + { + if (_fences.Count == 0) + { + return false; + } + + if (VulkanConfiguration.UseGranularBufferTracking) + { + lock (_fences) + { + foreach (var kv in _fences) + { + var fence = kv.Key; + var ownerCbIndex = kv.Value; + + if (_rangeList.OverlapsWith(ownerCbIndex, offset, size)) + { + return true; + } + } + } + + return false; + } + else + { + return true; + } + } + + private FenceHolder[] GetOverlappingFences(int offset, int size) + { + List overlapping = new List(); + + foreach (var kv in _fences) + { + var fence = kv.Key; + var ownerCbIndex = kv.Value; + + if (_rangeList.OverlapsWith(ownerCbIndex, offset, size)) + { + overlapping.Add(fence); + } + } + + return overlapping.ToArray(); + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/Native/shaderc/libs/shaderc_shared.dll b/Ryujinx.Graphics.Vulkan/Native/shaderc/libs/shaderc_shared.dll new file mode 100644 index 000000000..9c6cdc591 Binary files /dev/null and b/Ryujinx.Graphics.Vulkan/Native/shaderc/libs/shaderc_shared.dll differ diff --git a/Ryujinx.Graphics.Vulkan/NativeArray.cs b/Ryujinx.Graphics.Vulkan/NativeArray.cs new file mode 100644 index 000000000..9d66ce8d7 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/NativeArray.cs @@ -0,0 +1,45 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Vulkan +{ + unsafe class NativeArray : IDisposable where T : unmanaged + { + public T* Pointer { get; private set; } + public int Length { get; } + + public ref T this[int index] + { + get => ref Pointer[Checked(index)]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int Checked(int index) + { + if ((uint)index >= (uint)Length) + { + throw new IndexOutOfRangeException(); + } + + return index; + } + + public NativeArray(int length) + { + Pointer = (T*)Marshal.AllocHGlobal(checked(length * Unsafe.SizeOf())); + Length = length; + } + + public Span ToSpan() + { + return new Span(Pointer, Length); + } + + public void Dispose() + { + Marshal.FreeHGlobal((IntPtr)Pointer); + Pointer = null; + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/Ryujinx.Graphics.Vulkan/PipelineBase.cs new file mode 100644 index 000000000..c14daed31 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/PipelineBase.cs @@ -0,0 +1,1171 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; +using Silk.NET.Vulkan; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Vulkan +{ + class PipelineBase : IDisposable + { + public const int DescriptorSetLayouts = 6; + + public const int UniformSetIndex = 0; + public const int StorageSetIndex = 1; + public const int TextureSetIndex = 2; + public const int ImageSetIndex = 3; + public const int BufferTextureSetIndex = 4; + public const int BufferImageSetIndex = 5; + + protected readonly VulkanGraphicsDevice Gd; + protected readonly Device Device; + + private PipelineDynamicState _dynamicState; + private PipelineState _newState; + private bool _stateDirty; + private GAL.PrimitiveTopology _topology; + + private ulong _currentPipelineHandle; + + protected Auto Pipeline; + + protected PipelineBindPoint Pbp; + + private PipelineCache _pipelineCache; + + protected CommandBufferScoped Cbs; + protected CommandBuffer CommandBuffer; + + public CommandBufferScoped CurrentCommandBuffer => Cbs; + + private ShaderCollection _program; + + private struct Vector4 + { + public T X; + public T Y; + public T Z; + public T W; + } + + private Vector4[] _renderScale = new Vector4[65]; + private Vector4[] _cpRenderScale = new Vector4[64]; + + protected FramebufferParams FramebufferParams; + private Auto _framebuffer; + private Auto _renderPass; + private bool _renderPassActive; + + private readonly DescriptorSetUpdater _descriptorSetUpdater; + + private BufferState _indexBuffer; + private readonly BufferState[] _transformFeedbackBuffers; + private readonly BufferState[] _vertexBuffers; + + public BufferHolder RenderScaleBuffer { get; } + + private bool _needsIndexBufferRebind; + private bool _needsTransformFeedbackBuffersRebind; + private bool _needsVertexBuffersRebind; + + private bool _tfEnabled; + + public ulong DrawCount { get; private set; } + + public unsafe PipelineBase(VulkanGraphicsDevice gd, Device device) + { + Gd = gd; + Device = device; + + var pipelineCacheCreateInfo = new PipelineCacheCreateInfo() + { + SType = StructureType.PipelineCacheCreateInfo + }; + + gd.Api.CreatePipelineCache(device, pipelineCacheCreateInfo, null, out _pipelineCache).ThrowOnError(); + + _descriptorSetUpdater = new DescriptorSetUpdater(gd, this); + + _transformFeedbackBuffers = new BufferState[Constants.MaxTransformFeedbackBuffers]; + _vertexBuffers = new BufferState[Constants.MaxVertexBuffers + 1]; + + const int EmptyVbSize = 16; + + using var emptyVb = gd.BufferManager.Create(gd, EmptyVbSize); + emptyVb.SetData(0, new byte[EmptyVbSize]); + _vertexBuffers[0] = new BufferState(emptyVb.GetBuffer(), 0, EmptyVbSize, 0UL); + _needsVertexBuffersRebind = true; + + var defaultScale = new Vector4 { X = 1f, Y = 0f, Z = 0f, W = 0f }; + new Span>(_renderScale).Fill(defaultScale); + + RenderScaleBuffer = gd.BufferManager.Create(gd, SupportBuffer.RequiredSize); + + SetSupportBufferDataCpu>(SupportBuffer.GraphicsRenderScaleOffset, _renderScale, SupportBuffer.RenderScaleMaxCount); + + _newState.Initialize(); + _newState.LineWidth = 1f; + } + + protected virtual DescriptorSetLayout[] CreateDescriptorSetLayouts(VulkanGraphicsDevice gd, Device device, out PipelineLayout layout) + { + throw new NotSupportedException(); + } + + public unsafe void Barrier() + { + MemoryBarrier memoryBarrier = new MemoryBarrier() + { + SType = StructureType.MemoryBarrier, + SrcAccessMask = AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit, + DstAccessMask = AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit + }; + + Gd.Api.CmdPipelineBarrier( + CommandBuffer, + PipelineStageFlags.PipelineStageFragmentShaderBit, + PipelineStageFlags.PipelineStageFragmentShaderBit, + 0, + 1, + memoryBarrier, + 0, + null, + 0, + null); + } + + public void BeginTransformFeedback(GAL.PrimitiveTopology topology) + { + if (!_tfEnabled) + { + BeginTransformFeedbackInternal(); + _tfEnabled = true; + } + } + + public void ClearBuffer(BufferHandle destination, int offset, int size, uint value) + { + EndRenderPass(); + + var dst = Gd.BufferManager.GetBuffer(CommandBuffer, destination, true).Get(Cbs, offset, size).Value; + + Gd.Api.CmdFillBuffer(CommandBuffer, dst, (ulong)offset, (ulong)size, value); + } + + public unsafe void ClearRenderTargetColor(int index, uint componentMask, ColorF color) + { + // TODO: Use componentMask + + if (_framebuffer == null) + { + return; + } + + if (_renderPass == null) + { + CreateRenderPass(); + } + + BeginRenderPass(); + + var clearValue = new ClearValue(new ClearColorValue(color.Red, color.Green, color.Blue, color.Alpha)); + var attachment = new ClearAttachment(ImageAspectFlags.ImageAspectColorBit, (uint)index, clearValue); + var clearRect = FramebufferParams?.GetClearRect() ?? default; + + Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect); + } + + public unsafe void ClearRenderTargetDepthStencil(float depthValue, bool depthMask, int stencilValue, int stencilMask) + { + // TODO: Use stencilMask (fully) + + if (_framebuffer == null) + { + return; + } + + if (_renderPass == null) + { + CreateRenderPass(); + } + + BeginRenderPass(); + + var clearValue = new ClearValue(null, new ClearDepthStencilValue(depthValue, (uint)stencilValue)); + var flags = depthMask ? ImageAspectFlags.ImageAspectDepthBit : 0; + + if (stencilMask != 0) + { + flags |= ImageAspectFlags.ImageAspectStencilBit; + } + + var attachment = new ClearAttachment(flags, 0, clearValue); + var clearRect = FramebufferParams?.GetClearRect() ?? default; + + Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect); + } + + public void CommandBufferBarrier() + { + // TODO: More specific barrier? + Barrier(); + } + + public void CopyBuffer(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size) + { + EndRenderPass(); + + var src = Gd.BufferManager.GetBuffer(CommandBuffer, source, false); + var dst = Gd.BufferManager.GetBuffer(CommandBuffer, destination, true); + + BufferHolder.Copy(Gd, Cbs, src, dst, srcOffset, dstOffset, size); + } + + public void DispatchCompute(int groupsX, int groupsY, int groupsZ) + { + if (_program.LinkStatus != ProgramLinkStatus.Success) + { + return; + } + + EndRenderPass(); + RecreatePipelineIfNeeded(PipelineBindPoint.Compute); + + Gd.Api.CmdDispatch(CommandBuffer, (uint)groupsX, (uint)groupsY, (uint)groupsZ); + } + + public void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance) + { + // System.Console.WriteLine("draw"); + + if (_program.LinkStatus != ProgramLinkStatus.Success) + { + return; + } + + BeginRenderPass(); + RecreatePipelineIfNeeded(PipelineBindPoint.Graphics); + DrawCount++; + + if (_topology == GAL.PrimitiveTopology.Quads) + { + int quadsCount = vertexCount / 4; + + for (int i = 0; i < quadsCount; i++) + { + Gd.Api.CmdDraw(CommandBuffer, 4, (uint)instanceCount, (uint)(firstVertex + i * 4), (uint)firstInstance); + } + } + else + { + Gd.Api.CmdDraw(CommandBuffer, (uint)vertexCount, (uint)instanceCount, (uint)firstVertex, (uint)firstInstance); + } + } + + public void DrawIndexed(int indexCount, int instanceCount, int firstIndex, int firstVertex, int firstInstance) + { + // System.Console.WriteLine("draw indexed"); + + if (_program.LinkStatus != ProgramLinkStatus.Success) + { + return; + } + + BeginRenderPass(); + RecreatePipelineIfNeeded(PipelineBindPoint.Graphics); + DrawCount++; + + if (_topology == GAL.PrimitiveTopology.Quads) + { + int quadsCount = indexCount / 4; + + for (int i = 0; i < quadsCount; i++) + { + Gd.Api.CmdDrawIndexed(CommandBuffer, 4, (uint)instanceCount, (uint)(firstIndex + i * 4), firstVertex, (uint)firstInstance); + } + } + else + { + Gd.Api.CmdDrawIndexed(CommandBuffer, (uint)indexCount, (uint)instanceCount, (uint)firstIndex, firstVertex, (uint)firstInstance); + } + } + + public void DrawTexture(ITexture texture, ISampler sampler, Extents2DF srcRegion, Extents2DF dstRegion) + { + throw new NotImplementedException(); + } + + public void EndTransformFeedback() + { + if (_tfEnabled) + { + EndTransformFeedbackInternal(); + _tfEnabled = false; + } + } + + public void MultiDrawIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride) + { + if (!Gd.SupportsIndirectParameters) + { + throw new NotSupportedException(); + } + + if (_program.LinkStatus != ProgramLinkStatus.Success) + { + return; + } + + BeginRenderPass(); + RecreatePipelineIfNeeded(PipelineBindPoint.Graphics); + DrawCount++; + + var buffer = Gd.BufferManager.GetBuffer(CommandBuffer, indirectBuffer.Handle, true).Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value; + var countBuffer = Gd.BufferManager.GetBuffer(CommandBuffer, parameterBuffer.Handle, true).Get(Cbs, parameterBuffer.Offset, parameterBuffer.Size).Value; + + Gd.DrawIndirectCountApi.CmdDrawIndirectCount( + CommandBuffer, + buffer, + (ulong)indirectBuffer.Offset, + countBuffer, + (ulong)parameterBuffer.Offset, + (uint)maxDrawCount, + (uint)stride); + } + + public void MultiDrawIndexedIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride) + { + if (!Gd.SupportsIndirectParameters) + { + throw new NotSupportedException(); + } + + if (_program.LinkStatus != ProgramLinkStatus.Success) + { + return; + } + + BeginRenderPass(); + RecreatePipelineIfNeeded(PipelineBindPoint.Graphics); + DrawCount++; + + var buffer = Gd.BufferManager.GetBuffer(CommandBuffer, indirectBuffer.Handle, true).Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value; + var countBuffer = Gd.BufferManager.GetBuffer(CommandBuffer, parameterBuffer.Handle, true).Get(Cbs, parameterBuffer.Offset, parameterBuffer.Size).Value; + + Gd.DrawIndirectCountApi.CmdDrawIndexedIndirectCount( + CommandBuffer, + buffer, + (ulong)indirectBuffer.Offset, + countBuffer, + (ulong)parameterBuffer.Offset, + (uint)maxDrawCount, + (uint)stride); + } + + public void SetAlphaTest(bool enable, float reference, GAL.CompareOp op) + { + // TODO. + } + + public void SetBlendState(int index, BlendDescriptor blend) + { + ref var vkBlend = ref _newState.Internal.ColorBlendAttachmentState[index]; + + vkBlend.BlendEnable = blend.Enable; + vkBlend.SrcColorBlendFactor = blend.ColorSrcFactor.Convert(); + vkBlend.DstColorBlendFactor = blend.ColorDstFactor.Convert(); + vkBlend.ColorBlendOp = blend.ColorOp.Convert(); + vkBlend.SrcAlphaBlendFactor = blend.AlphaSrcFactor.Convert(); + vkBlend.DstAlphaBlendFactor = blend.AlphaDstFactor.Convert(); + vkBlend.AlphaBlendOp = blend.AlphaOp.Convert(); + + _newState.BlendConstantR = blend.BlendConstant.Red; + _newState.BlendConstantG = blend.BlendConstant.Green; + _newState.BlendConstantB = blend.BlendConstant.Blue; + _newState.BlendConstantA = blend.BlendConstant.Alpha; + + SignalStateChange(); + } + + public void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp) + { + if (VulkanConfiguration.UseDynamicState) + { + _dynamicState.SetDepthBias(factor, units, clamp); + } + else + { + _newState.DepthBiasSlopeFactor = factor; + _newState.DepthBiasConstantFactor = units; + _newState.DepthBiasClamp = clamp; + } + + _newState.DepthBiasEnable = enables != 0; + SignalStateChange(); + } + + public void SetDepthClamp(bool clamp) + { + _newState.DepthClampEnable = clamp; + SignalStateChange(); + } + + public void SetDepthMode(DepthMode mode) + { + // TODO. + } + + public void SetDepthTest(DepthTestDescriptor depthTest) + { + _newState.DepthTestEnable = depthTest.TestEnable; + _newState.DepthWriteEnable = depthTest.WriteEnable; + _newState.DepthCompareOp = depthTest.Func.Convert(); + SignalStateChange(); + } + + public void SetFaceCulling(bool enable, Face face) + { + _newState.CullMode = enable ? face.Convert() : 0; + SignalStateChange(); + } + + public void SetFrontFace(GAL.FrontFace frontFace) + { + _newState.FrontFace = frontFace.Convert(); + SignalStateChange(); + } + + public void SetImage(int binding, ITexture image, GAL.Format imageFormat) + { + _descriptorSetUpdater.SetImage(binding, image, imageFormat); + } + + public void SetIndexBuffer(BufferRange buffer, GAL.IndexType type) + { + _indexBuffer.Dispose(); + + if (buffer.Handle != BufferHandle.Null) + { + Auto ib = null; + int offset = buffer.Offset; + int size = buffer.Size; + + if (type == GAL.IndexType.UByte && !Gd.SupportsIndexTypeUint8) + { + ib = Gd.BufferManager.GetBufferI8ToI16(Cbs, buffer.Handle, offset, size); + offset = 0; + size *= 2; + type = GAL.IndexType.UShort; + } + else + { + ib = Gd.BufferManager.GetBuffer(CommandBuffer, buffer.Handle, false); + } + + _indexBuffer = new BufferState(ib, offset, size, type.Convert()); + } + else + { + _indexBuffer = BufferState.Null; + } + + _indexBuffer.BindIndexBuffer(Gd.Api, Cbs); + } + + public void SetLineParameters(float width, bool smooth) + { + _newState.LineWidth = width; + SignalStateChange(); + } + + public void SetLogicOpState(bool enable, LogicalOp op) + { + _newState.LogicOpEnable = enable; + _newState.LogicOp = op.Convert(); + SignalStateChange(); + } + + public void SetOrigin(Origin origin) + { + // TODO. + } + + public unsafe void SetPatchParameters(int vertices, ReadOnlySpan defaultOuterLevel, ReadOnlySpan defaultInnerLevel) + { + _newState.PatchControlPoints = (uint)vertices; + SignalStateChange(); + + // TODO: Default levels (likely needs emulation on shaders?) + } + + public void SetPointParameters(float size, bool isProgramPointSize, bool enablePointSprite, Origin origin) + { + // TODO. + } + + public void SetPolygonMode(GAL.PolygonMode frontMode, GAL.PolygonMode backMode) + { + // TODO. + } + + public void SetPrimitiveRestart(bool enable, int index) + { + _newState.PrimitiveRestartEnable = enable; + // TODO: What to do about the index? + SignalStateChange(); + } + + public void SetPrimitiveTopology(GAL.PrimitiveTopology topology) + { + _topology = topology; + + var vkTopology = topology.Convert(); + + _newState.Topology = vkTopology; + + SignalStateChange(); + } + + public void SetProgram(IProgram program) + { + var internalProgram = (ShaderCollection)program; + var stages = internalProgram.GetInfos(); + + _program = internalProgram; + + _descriptorSetUpdater.SetProgram(internalProgram); + + _newState.PipelineLayout = internalProgram.PipelineLayout; + _newState.StagesCount = (uint)stages.Length; + + stages.CopyTo(_newState.Stages.ToSpan().Slice(0, stages.Length)); + + SignalStateChange(); + SignalProgramChange(); + } + + protected virtual void SignalProgramChange() + { + } + + public void SetRasterizerDiscard(bool discard) + { + _newState.RasterizerDiscardEnable = discard; + SignalStateChange(); + } + + public void SetRenderTargetColorMasks(ReadOnlySpan componentMask) + { + int count = Math.Min(Constants.MaxRenderTargets, componentMask.Length); + + for (int i = 0; i < count; i++) + { + ref var vkBlend = ref _newState.Internal.ColorBlendAttachmentState[i]; + + vkBlend.ColorWriteMask = (ColorComponentFlags)componentMask[i]; + } + + SignalStateChange(); + } + + public void SetRenderTargets(ITexture[] colors, ITexture depthStencil) + { + CreateFramebuffer(colors, depthStencil); + CreateRenderPass(); + SignalStateChange(); + } + + public void SetRenderTargetScale(float scale) + { + _renderScale[0].X = scale; + SetSupportBufferData>(SupportBuffer.GraphicsRenderScaleOffset, _renderScale, 1); // Just the first element. + } + + public void SetScissors(ReadOnlySpan> regions) + { + int count = Math.Min(Constants.MaxViewports, regions.Length); + + for (int i = 0; i < count; i++) + { + var region = regions[i]; + var offset = new Offset2D(region.X, region.Y); + var extent = new Extent2D((uint)region.Width, (uint)region.Height); + + if (VulkanConfiguration.UseDynamicState) + { + _dynamicState.SetScissor(i, new Rect2D(offset, extent)); + } + else + { + _newState.Internal.Scissors[i] = new Rect2D(offset, extent); + } + } + + if (VulkanConfiguration.UseDynamicState) + { + _dynamicState.ScissorsCount = count; + } + + _newState.ScissorsCount = (uint)count; + SignalStateChange(); + } + + public void SetStencilTest(StencilTestDescriptor stencilTest) + { + if (VulkanConfiguration.UseDynamicState) + { + _dynamicState.SetStencilMasks( + (uint)stencilTest.BackFuncMask, + (uint)stencilTest.BackMask, + (uint)stencilTest.BackFuncRef, + (uint)stencilTest.FrontFuncMask, + (uint)stencilTest.FrontMask, + (uint)stencilTest.FrontFuncRef); + } + else + { + _newState.StencilBackCompareMask = (uint)stencilTest.BackFuncMask; + _newState.StencilBackWriteMask = (uint)stencilTest.BackMask; + _newState.StencilBackReference = (uint)stencilTest.BackFuncRef; + _newState.StencilFrontCompareMask = (uint)stencilTest.FrontFuncMask; + _newState.StencilFrontWriteMask = (uint)stencilTest.FrontMask; + _newState.StencilFrontReference = (uint)stencilTest.FrontFuncRef; + } + + _newState.StencilTestEnable = stencilTest.TestEnable; + _newState.StencilBackFailOp = stencilTest.BackSFail.Convert(); + _newState.StencilBackPassOp = stencilTest.BackDpPass.Convert(); + _newState.StencilBackDepthFailOp = stencilTest.BackDpFail.Convert(); + _newState.StencilBackCompareOp = stencilTest.BackFunc.Convert(); + _newState.StencilFrontFailOp = stencilTest.FrontSFail.Convert(); + _newState.StencilFrontPassOp = stencilTest.FrontDpPass.Convert(); + _newState.StencilFrontDepthFailOp = stencilTest.FrontDpFail.Convert(); + _newState.StencilFrontCompareOp = stencilTest.FrontFunc.Convert(); + SignalStateChange(); + } + + public void SetStorageBuffers(int first, ReadOnlySpan buffers) + { + _descriptorSetUpdater.SetStorageBuffers(CommandBuffer, first, buffers); + } + + public void SetTextureAndSampler(int binding, ITexture texture, ISampler sampler) + { + _descriptorSetUpdater.SetTextureAndSampler(binding, texture, sampler); + } + + public void SetTransformFeedbackBuffers(ReadOnlySpan buffers) + { + PauseTransformFeedbackInternal(); + + int count = Math.Min(Constants.MaxTransformFeedbackBuffers, buffers.Length); + + for (int i = 0; i < count; i++) + { + var range = buffers[i]; + + _transformFeedbackBuffers[i].Dispose(); + + if (range.Handle != BufferHandle.Null) + { + _transformFeedbackBuffers[i] = new BufferState(Gd.BufferManager.GetBuffer(CommandBuffer, range.Handle, true), range.Offset, range.Size); + _transformFeedbackBuffers[i].BindTransformFeedbackBuffer(Gd, Cbs, (uint)i); + } + else + { + _transformFeedbackBuffers[i] = BufferState.Null; + } + } + + ResumeTransformFeedbackInternal(); + } + + public void SetUniformBuffers(int first, ReadOnlySpan buffers) + { + _descriptorSetUpdater.SetUniformBuffers(CommandBuffer, first, buffers); + } + + public void SetUserClipDistance(int index, bool enableClip) + { + // TODO. + } + + public void SetVertexAttribs(ReadOnlySpan vertexAttribs) + { + int count = Math.Min(Constants.MaxVertexAttributes, vertexAttribs.Length); + + for (int i = 0; i < count; i++) + { + var attribute = vertexAttribs[i]; + var bufferIndex = attribute.IsZero ? 0 : attribute.BufferIndex + 1; + + _newState.Internal.VertexAttributeDescriptions[i] = new VertexInputAttributeDescription( + (uint)i, + (uint)bufferIndex, + FormatTable.GetFormat(attribute.Format), + (uint)attribute.Offset); + } + + _newState.VertexAttributeDescriptionsCount = (uint)count; + + SignalStateChange(); + } + + public void SetVertexBuffers(ReadOnlySpan vertexBuffers) + { + int count = Math.Min(Constants.MaxVertexBuffers, vertexBuffers.Length); + + _newState.Internal.VertexBindingDescriptions[0] = new VertexInputBindingDescription(0, 0, VertexInputRate.Vertex); + + int validCount = 1; + + for (int i = 0; i < count; i++) + { + var vertexBuffer = vertexBuffers[i]; + + // TODO: Support divisor > 1 + var inputRate = vertexBuffer.Divisor != 0 ? VertexInputRate.Instance : VertexInputRate.Vertex; + + if (vertexBuffer.Buffer.Handle != BufferHandle.Null) + { + var vb = Gd.BufferManager.GetBuffer(CommandBuffer, vertexBuffer.Buffer.Handle, false); + if (vb != null) + { + int binding = i + 1; + int descriptorIndex = validCount++; + + _newState.Internal.VertexBindingDescriptions[descriptorIndex] = new VertexInputBindingDescription( + (uint)binding, + (uint)vertexBuffer.Stride, + inputRate); + + _vertexBuffers[binding].Dispose(); + _vertexBuffers[binding] = new BufferState( + vb, + vertexBuffer.Buffer.Offset, + vertexBuffer.Buffer.Size, + (ulong)vertexBuffer.Stride); + + _vertexBuffers[binding].BindVertexBuffer(Gd, Cbs, (uint)binding); + } + } + } + + _newState.VertexBindingDescriptionsCount = (uint)validCount; + + SignalStateChange(); + } + + // TODO: Remove first parameter. + public void SetViewports(int first, ReadOnlySpan viewports) + { + int count = Math.Min(Constants.MaxViewports, viewports.Length); + + static float Clamp(float value) + { + return Math.Clamp(value, 0f, 1f); + } + + if (VulkanConfiguration.UseDynamicState) + { + for (int i = 0; i < count; i++) + { + var viewport = viewports[i]; + + _dynamicState.SetViewport(i, new Silk.NET.Vulkan.Viewport( + viewport.Region.X, + viewport.Region.Y, + viewport.Region.Width == 0f ? 1f : viewport.Region.Width, + viewport.Region.Height == 0f ? 1f : viewport.Region.Height, + Clamp(viewport.DepthNear), + Clamp(viewport.DepthFar))); + } + + _dynamicState.ViewportsCount = count; + } + else + { + for (int i = 0; i < count; i++) + { + var viewport = viewports[i]; + + ref var vkViewport = ref _newState.Internal.Viewports[i]; + + vkViewport.X = viewport.Region.X; + vkViewport.Y = viewport.Region.Y; + vkViewport.Width = viewport.Region.Width == 0f ? 1f : viewport.Region.Width; + vkViewport.Height = viewport.Region.Height == 0f ? 1f : viewport.Region.Height; + vkViewport.MinDepth = Clamp(viewport.DepthNear); + vkViewport.MaxDepth = Clamp(viewport.DepthFar); + } + } + + _newState.ViewportsCount = (uint)count; + SignalStateChange(); + } + + public unsafe void TextureBarrier() + { + MemoryBarrier memoryBarrier = new MemoryBarrier() + { + SType = StructureType.MemoryBarrier, + SrcAccessMask = AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit, + DstAccessMask = AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit + }; + + Gd.Api.CmdPipelineBarrier( + CommandBuffer, + PipelineStageFlags.PipelineStageFragmentShaderBit, + PipelineStageFlags.PipelineStageFragmentShaderBit, + 0, + 1, + memoryBarrier, + 0, + null, + 0, + null); + } + + public void TextureBarrierTiled() + { + TextureBarrier(); + } + + public void UpdateRenderScale(ReadOnlySpan scales, int textureCount, int imageCount) + { + bool changed = false; + + for (int index = 0; index < textureCount + imageCount; index++) + { + if (_renderScale[1 + index].X != scales[index]) + { + _renderScale[1 + index].X = scales[index]; + changed = true; + } + } + + if (changed) + { + SetSupportBufferData>(SupportBuffer.GraphicsRenderScaleOffset, _renderScale, 1 + textureCount + imageCount); + } + } + + private void SetSupportBufferData(int offset, ReadOnlySpan data, int count) where T : unmanaged + { + RenderScaleBuffer.SetDataInline(Cbs, EndRenderPass, offset, MemoryMarshal.Cast(data.Slice(0, count))); + } + + private void SetSupportBufferDataCpu(int offset, ReadOnlySpan data, int count) where T : unmanaged + { + RenderScaleBuffer.SetDataUnchecked(offset, MemoryMarshal.Cast(data.Slice(0, count))); + } + + protected void SignalCommandBufferChange() + { + _needsIndexBufferRebind = true; + _needsTransformFeedbackBuffersRebind = true; + _needsVertexBuffersRebind = true; + + _descriptorSetUpdater.SignalCommandBufferChange(); + _dynamicState.ForceAllDirty(); + _currentPipelineHandle = 0; + } + + private void CreateFramebuffer(ITexture[] colors, ITexture depthStencil) + { + FramebufferParams = new FramebufferParams(Device, colors, depthStencil); + UpdatePipelineAttachmentFormats(); + } + + protected void UpdatePipelineAttachmentFormats() + { + var dstAttachmentFormats = _newState.Internal.AttachmentFormats.ToSpan(); + FramebufferParams.AttachmentFormats.CopyTo(dstAttachmentFormats); + + int maxAttachmentIndex = FramebufferParams.MaxColorAttachmentIndex + (FramebufferParams.HasDepthStencil ? 1 : 0); + for (int i = FramebufferParams.AttachmentFormats.Length; i <= maxAttachmentIndex; i++) + { + dstAttachmentFormats[i] = 0; + } + + _newState.ColorBlendAttachmentStateCount = (uint)(FramebufferParams.MaxColorAttachmentIndex + 1); + _newState.HasDepthStencil = FramebufferParams.HasDepthStencil; + } + + protected unsafe void CreateRenderPass() + { + const int MaxAttachments = Constants.MaxRenderTargets + 1; + + AttachmentDescription[] attachmentDescs = null; + + var subpass = new SubpassDescription() + { + PipelineBindPoint = PipelineBindPoint.Graphics + }; + + AttachmentReference* attachmentReferences = stackalloc AttachmentReference[MaxAttachments]; + + var hasFramebuffer = FramebufferParams != null; + + if (hasFramebuffer && FramebufferParams.AttachmentsCount != 0) + { + attachmentDescs = new AttachmentDescription[FramebufferParams.AttachmentsCount]; + + for (int i = 0; i < FramebufferParams.AttachmentsCount; i++) + { + int bindIndex = FramebufferParams.AttachmentIndices[i]; + + attachmentDescs[i] = new AttachmentDescription( + 0, + FramebufferParams.AttachmentFormats[i], + SampleCountFlags.SampleCount1Bit, + AttachmentLoadOp.Load, + AttachmentStoreOp.Store, + AttachmentLoadOp.Load, + AttachmentStoreOp.Store, + ImageLayout.General, + ImageLayout.General); + } + + int colorAttachmentsCount = FramebufferParams.ColorAttachmentsCount; + + if (colorAttachmentsCount > MaxAttachments - 1) + { + colorAttachmentsCount = MaxAttachments - 1; + } + + if (colorAttachmentsCount != 0) + { + int maxAttachmentIndex = FramebufferParams.MaxColorAttachmentIndex; + subpass.ColorAttachmentCount = (uint)maxAttachmentIndex + 1; + subpass.PColorAttachments = &attachmentReferences[0]; + + // Fill with VK_ATTACHMENT_UNUSED to cover any gaps. + for (int i = 0; i <= maxAttachmentIndex; i++) + { + subpass.PColorAttachments[i] = new AttachmentReference(Vk.AttachmentUnused, ImageLayout.Undefined); + } + + for (int i = 0; i < colorAttachmentsCount; i++) + { + int bindIndex = FramebufferParams.AttachmentIndices[i]; + + subpass.PColorAttachments[bindIndex] = new AttachmentReference((uint)i, ImageLayout.General); + } + } + + if (FramebufferParams.HasDepthStencil) + { + uint dsIndex = (uint)FramebufferParams.AttachmentsCount - 1; + + subpass.PDepthStencilAttachment = &attachmentReferences[MaxAttachments - 1]; + *subpass.PDepthStencilAttachment = new AttachmentReference(dsIndex, ImageLayout.General); + } + } + + var subpassDependency = new SubpassDependency( + 0, + 0, + PipelineStageFlags.PipelineStageAllGraphicsBit, + PipelineStageFlags.PipelineStageAllGraphicsBit, + AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit, + AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit, + 0); + + fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs) + { + var renderPassCreateInfo = new RenderPassCreateInfo() + { + SType = StructureType.RenderPassCreateInfo, + PAttachments = pAttachmentDescs, + AttachmentCount = attachmentDescs != null ? (uint)attachmentDescs.Length : 0, + PSubpasses = &subpass, + SubpassCount = 1, + PDependencies = &subpassDependency, + DependencyCount = 1 + }; + + Gd.Api.CreateRenderPass(Device, renderPassCreateInfo, null, out var renderPass).ThrowOnError(); + + _renderPass?.Dispose(); + _renderPass = new Auto(new DisposableRenderPass(Gd.Api, Device, renderPass)); + } + + EndRenderPass(); + + _framebuffer?.Dispose(); + _framebuffer = hasFramebuffer ? FramebufferParams.Create(Gd.Api, Cbs, _renderPass) : null; + } + + protected void SignalStateChange([System.Runtime.CompilerServices.CallerMemberName] string caller = "") + { + // System.Console.WriteLine("state change by " + caller); + _stateDirty = true; + } + + private void RecreatePipelineIfNeeded(PipelineBindPoint pbp) + { + // Take the opportunity to process any pending work requested by other threads. + _dynamicState.ReplayIfDirty(Gd.Api, CommandBuffer); + + if (_stateDirty || Pbp != pbp) + { + CreatePipeline(pbp); + _stateDirty = false; + Pbp = pbp; + } + + if (_needsIndexBufferRebind) + { + _indexBuffer.BindIndexBuffer(Gd.Api, Cbs); + _needsIndexBufferRebind = false; + } + + if (_needsTransformFeedbackBuffersRebind) + { + PauseTransformFeedbackInternal(); + + for (int i = 0; i < Constants.MaxTransformFeedbackBuffers; i++) + { + _transformFeedbackBuffers[i].BindTransformFeedbackBuffer(Gd, Cbs, (uint)i); + } + + ResumeTransformFeedbackInternal(); + _needsTransformFeedbackBuffersRebind = false; + } + + if (_needsVertexBuffersRebind) + { + for (int i = 0; i < Constants.MaxVertexBuffers + 1; i++) + { + _vertexBuffers[i].BindVertexBuffer(Gd, Cbs, (uint)i); + } + + _needsVertexBuffersRebind = false; + } + + _descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, pbp); + } + + private void CreatePipeline(PipelineBindPoint pbp) + { + // We can only create a pipeline if the have the shader stages set. + if (_newState.Stages != null) + { + if (pbp == PipelineBindPoint.Graphics && _renderPass == null) + { + CreateRenderPass(); + } + + var pipeline = pbp == PipelineBindPoint.Compute + ? _newState.CreateComputePipeline(Gd.Api, Device, _program, _pipelineCache) + : _newState.CreateGraphicsPipeline(Gd.Api, Device, _program, _pipelineCache, _renderPass.Get(Cbs).Value); + + ulong pipelineHandle = pipeline.GetUnsafe().Value.Handle; + + if (_currentPipelineHandle != pipelineHandle) + { + _currentPipelineHandle = pipelineHandle; + // _pipeline?.Dispose(); + Pipeline = pipeline; + + Gd.Api.CmdBindPipeline(CommandBuffer, pbp, Pipeline.Get(Cbs).Value); + } + } + } + + private unsafe void BeginRenderPass() + { + if (!_renderPassActive) + { + var renderArea = new Rect2D(null, new Extent2D(FramebufferParams.Width, FramebufferParams.Height)); + var clearValue = new ClearValue(); + + var renderPassBeginInfo = new RenderPassBeginInfo() + { + SType = StructureType.RenderPassBeginInfo, + RenderPass = _renderPass.Get(Cbs).Value, + Framebuffer = _framebuffer.Get(Cbs).Value, + RenderArea = renderArea, + PClearValues = &clearValue, + ClearValueCount = 1 + }; + + Gd.Api.CmdBeginRenderPass(CommandBuffer, renderPassBeginInfo, SubpassContents.Inline); + _renderPassActive = true; + } + } + + public void EndRenderPass() + { + if (_renderPassActive) + { + // System.Console.WriteLine("render pass ended " + caller); + Gd.Api.CmdEndRenderPass(CommandBuffer); + _renderPassActive = false; + } + } + + protected void PauseTransformFeedbackInternal() + { + if (_tfEnabled) + { + EndTransformFeedbackInternal(); + } + } + + protected void ResumeTransformFeedbackInternal() + { + if (_tfEnabled) + { + BeginTransformFeedbackInternal(); + } + } + + private unsafe void BeginTransformFeedbackInternal() + { + Gd.TransformFeedbackApi.CmdBeginTransformFeedback(CommandBuffer, 0, 0, null, null); + } + + private unsafe void EndTransformFeedbackInternal() + { + Gd.TransformFeedbackApi.CmdEndTransformFeedback(CommandBuffer, 0, 0, null, null); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _renderPass?.Dispose(); + _framebuffer?.Dispose(); + _indexBuffer.Dispose(); + _newState.Dispose(); + _descriptorSetUpdater.Dispose(); + + for (int i = 0; i < _vertexBuffers.Length; i++) + { + _vertexBuffers[i].Dispose(); + } + + for (int i = 0; i < _transformFeedbackBuffers.Length; i++) + { + _transformFeedbackBuffers[i].Dispose(); + } + + Pipeline?.Dispose(); + + unsafe + { + Gd.Api.DestroyPipelineCache(Device, _pipelineCache, null); + } + + RenderScaleBuffer.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/PipelineBlit.cs b/Ryujinx.Graphics.Vulkan/PipelineBlit.cs new file mode 100644 index 000000000..adf921118 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/PipelineBlit.cs @@ -0,0 +1,103 @@ +using Silk.NET.Vulkan; +using VkFormat = Silk.NET.Vulkan.Format; + +namespace Ryujinx.Graphics.Vulkan +{ + class PipelineBlit : PipelineBase + { + public PipelineBlit(VulkanGraphicsDevice gd, Device device) : base(gd, device) + { + } + + protected override unsafe DescriptorSetLayout[] CreateDescriptorSetLayouts(VulkanGraphicsDevice gd, Device device, out PipelineLayout layout) + { + DescriptorSetLayoutBinding uLayoutBindings = new DescriptorSetLayoutBinding + { + Binding = 0, + DescriptorType = DescriptorType.UniformBuffer, + DescriptorCount = 1, + StageFlags = ShaderStageFlags.ShaderStageVertexBit + }; + + DescriptorSetLayoutBinding tLayoutBindings = new DescriptorSetLayoutBinding + { + Binding = 0, + DescriptorType = DescriptorType.CombinedImageSampler, + DescriptorCount = 1, + StageFlags = ShaderStageFlags.ShaderStageFragmentBit + }; + + DescriptorSetLayout[] layouts = new DescriptorSetLayout[3]; + + var uDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo() + { + SType = StructureType.DescriptorSetLayoutCreateInfo, + PBindings = &uLayoutBindings, + BindingCount = 1 + }; + + var sDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo() + { + SType = StructureType.DescriptorSetLayoutCreateInfo, + BindingCount = 0 + }; + + var tDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo() + { + SType = StructureType.DescriptorSetLayoutCreateInfo, + PBindings = &tLayoutBindings, + BindingCount = 1 + }; + + gd.Api.CreateDescriptorSetLayout(device, uDescriptorSetLayoutCreateInfo, null, out layouts[0]).ThrowOnError(); + gd.Api.CreateDescriptorSetLayout(device, sDescriptorSetLayoutCreateInfo, null, out layouts[1]).ThrowOnError(); + gd.Api.CreateDescriptorSetLayout(device, tDescriptorSetLayoutCreateInfo, null, out layouts[2]).ThrowOnError(); + + fixed (DescriptorSetLayout* pLayouts = layouts) + { + var pipelineLayoutCreateInfo = new PipelineLayoutCreateInfo() + { + SType = StructureType.PipelineLayoutCreateInfo, + PSetLayouts = pLayouts, + SetLayoutCount = 3 + }; + + gd.Api.CreatePipelineLayout(device, &pipelineLayoutCreateInfo, null, out layout).ThrowOnError(); + } + + return layouts; + } + + public void SetRenderTarget(Auto view, uint width, uint height, bool isDepthStencil, VkFormat format) + { + CreateFramebuffer(view, width, height, isDepthStencil, format); + CreateRenderPass(); + SignalStateChange(); + } + + private void CreateFramebuffer(Auto view, uint width, uint height, bool isDepthStencil, VkFormat format) + { + FramebufferParams = new FramebufferParams(Device, view, width, height, isDepthStencil, format); + UpdatePipelineAttachmentFormats(); + } + + public void SetCommandBuffer(CommandBufferScoped cbs) + { + CommandBuffer = (Cbs = cbs).CommandBuffer; + + // Restore per-command buffer state. + + if (Pipeline != null) + { + Gd.Api.CmdBindPipeline(CommandBuffer, Pbp, Pipeline.Get(CurrentCommandBuffer).Value); + } + + SignalCommandBufferChange(); + } + + public void Finish() + { + EndRenderPass(); + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs b/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs new file mode 100644 index 000000000..96b961800 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs @@ -0,0 +1,133 @@ +using Ryujinx.Common.Memory; +using Silk.NET.Vulkan; + +namespace Ryujinx.Graphics.Vulkan +{ + struct PipelineDynamicState + { + private float _depthBiasSlopeFactor; + private float _depthBiasConstantFactor; + private float _depthBiasClamp; + + public int ScissorsCount; + private Array16 _scissors; + + private uint _backCompareMask; + private uint _backWriteMask; + private uint _backReference; + private uint _frontCompareMask; + private uint _frontWriteMask; + private uint _frontReference; + + public int ViewportsCount; + private Array16 _viewports; + + private enum DirtyFlags + { + None = 0, + DepthBias = 1 << 0, + Scissor = 1 << 1, + Stencil = 1 << 2, + Viewport = 1 << 3, + All = DepthBias | Scissor | Stencil | Viewport + } + + private DirtyFlags _dirty; + + public void SetDepthBias(float slopeFactor, float constantFactor, float clamp) + { + _depthBiasSlopeFactor = slopeFactor; + _depthBiasConstantFactor = constantFactor; + _depthBiasClamp = clamp; + + _dirty |= DirtyFlags.DepthBias; + } + + public void SetScissor(int index, Rect2D scissor) + { + _scissors[index] = scissor; + + _dirty |= DirtyFlags.Scissor; + } + + public void SetStencilMasks( + uint backCompareMask, + uint backWriteMask, + uint backReference, + uint frontCompareMask, + uint frontWriteMask, + uint frontReference) + { + _backCompareMask = backCompareMask; + _backWriteMask = backWriteMask; + _backReference = backReference; + _frontCompareMask = frontCompareMask; + _frontWriteMask = frontWriteMask; + _frontReference = frontReference; + + _dirty |= DirtyFlags.Stencil; + } + + public void SetViewport(int index, Viewport viewport) + { + _viewports[index] = viewport; + + _dirty |= DirtyFlags.Viewport; + } + + public void ForceAllDirty() + { + _dirty = DirtyFlags.All; + } + + public void ReplayIfDirty(Vk api, CommandBuffer commandBuffer) + { + if (_dirty.HasFlag(DirtyFlags.DepthBias)) + { + RecordDepthBias(api, commandBuffer); + } + + if (_dirty.HasFlag(DirtyFlags.Scissor)) + { + RecordScissor(api, commandBuffer); + } + + if (_dirty.HasFlag(DirtyFlags.Stencil)) + { + RecordStencilMasks(api, commandBuffer); + } + + if (_dirty.HasFlag(DirtyFlags.Viewport)) + { + RecordViewport(api, commandBuffer); + } + + _dirty = DirtyFlags.None; + } + + private void RecordDepthBias(Vk api, CommandBuffer commandBuffer) + { + api.CmdSetDepthBias(commandBuffer, _depthBiasConstantFactor, _depthBiasClamp, _depthBiasSlopeFactor); + } + + private void RecordScissor(Vk api, CommandBuffer commandBuffer) + { + api.CmdSetScissor(commandBuffer, 0, (uint)ScissorsCount, _scissors.ToSpan()); + } + + private void RecordStencilMasks(Vk api, CommandBuffer commandBuffer) + { + api.CmdSetStencilCompareMask(commandBuffer, StencilFaceFlags.StencilFaceBackBit, _backCompareMask); + api.CmdSetStencilWriteMask(commandBuffer, StencilFaceFlags.StencilFaceBackBit, _backWriteMask); + api.CmdSetStencilReference(commandBuffer, StencilFaceFlags.StencilFaceBackBit, _backReference); + api.CmdSetStencilCompareMask(commandBuffer, StencilFaceFlags.StencilFaceFrontBit, _frontCompareMask); + api.CmdSetStencilWriteMask(commandBuffer, StencilFaceFlags.StencilFaceFrontBit, _frontWriteMask); + api.CmdSetStencilReference(commandBuffer, StencilFaceFlags.StencilFaceFrontBit, _frontReference); + } + + private void RecordViewport(Vk api, CommandBuffer commandBuffer) + { + api.CmdSetViewport(commandBuffer, 0, (uint)ViewportsCount, _viewports.ToSpan()); + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/PipelineFull.cs b/Ryujinx.Graphics.Vulkan/PipelineFull.cs new file mode 100644 index 000000000..0d461b213 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/PipelineFull.cs @@ -0,0 +1,295 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Vulkan.Queries; +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Vulkan +{ + class PipelineFull : PipelineBase, IPipeline + { + private bool _hasPendingQuery; + + private readonly List _activeQueries; + + public PipelineFull(VulkanGraphicsDevice gd, Device device) : base(gd, device) + { + _activeQueries = new List(); + + CommandBuffer = (Cbs = gd.CommandBufferPool.Rent()).CommandBuffer; + } + + protected override unsafe DescriptorSetLayout[] CreateDescriptorSetLayouts(VulkanGraphicsDevice gd, Device device, out PipelineLayout layout) + { + DescriptorSetLayoutBinding* uLayoutBindings = stackalloc DescriptorSetLayoutBinding[Constants.MaxUniformBufferBindings]; + DescriptorSetLayoutBinding* sLayoutBindings = stackalloc DescriptorSetLayoutBinding[Constants.MaxStorageBufferBindings]; + DescriptorSetLayoutBinding* tLayoutBindings = stackalloc DescriptorSetLayoutBinding[Constants.MaxTextureBindings]; + DescriptorSetLayoutBinding* iLayoutBindings = stackalloc DescriptorSetLayoutBinding[Constants.MaxImageBindings]; + DescriptorSetLayoutBinding* bTLayoutBindings = stackalloc DescriptorSetLayoutBinding[Constants.MaxTextureBindings]; + DescriptorSetLayoutBinding* bILayoutBindings = stackalloc DescriptorSetLayoutBinding[Constants.MaxImageBindings]; + + DescriptorBindingFlags* pUBindingsFlags = stackalloc DescriptorBindingFlags[Constants.MaxUniformBufferBindings]; + DescriptorBindingFlags* pSBindingsFlags = stackalloc DescriptorBindingFlags[Constants.MaxStorageBufferBindings]; + DescriptorBindingFlags* pTBindingsFlags = stackalloc DescriptorBindingFlags[Constants.MaxTextureBindings]; + DescriptorBindingFlags* pIBindingsFlags = stackalloc DescriptorBindingFlags[Constants.MaxImageBindings]; + DescriptorBindingFlags* pBTBindingsFlags = stackalloc DescriptorBindingFlags[Constants.MaxTextureBindings]; + DescriptorBindingFlags* pBIBindingsFlags = stackalloc DescriptorBindingFlags[Constants.MaxImageBindings]; + + static DescriptorSetLayoutBindingFlagsCreateInfo CreateFlagsInfo(DescriptorBindingFlags* pBindingFlags, uint count) + { + return new DescriptorSetLayoutBindingFlagsCreateInfo() + { + SType = StructureType.DescriptorSetLayoutBindingFlagsCreateInfo, + PBindingFlags = pBindingFlags, + BindingCount = count + }; + } + + var uLayoutBindingFlags = CreateFlagsInfo(pUBindingsFlags, Constants.MaxUniformBufferBindings); + var sLayoutBindingFlags = CreateFlagsInfo(pSBindingsFlags, Constants.MaxStorageBufferBindings); + var tLayoutBindingFlags = CreateFlagsInfo(pTBindingsFlags, Constants.MaxTextureBindings); + var iLayoutBindingFlags = CreateFlagsInfo(pIBindingsFlags, Constants.MaxImageBindings); + var bTLayoutBindingFlags = CreateFlagsInfo(pBTBindingsFlags, Constants.MaxTextureBindings); + var bILayoutBindingFlags = CreateFlagsInfo(pBIBindingsFlags, Constants.MaxImageBindings); + + for (int stage = 0; stage < Constants.MaxShaderStages; stage++) + { + var stageFlags = (ShaderStageFlags)(1 << stage); + + if (stage == 0) + { + stageFlags |= ShaderStageFlags.ShaderStageComputeBit; + } + + void Set( + DescriptorSetLayoutBinding* bindings, + DescriptorSetLayoutBindingFlagsCreateInfo bindingFlagsCreateInfo, + int maxPerStage, + DescriptorType type) + { + for (int i = 0; i < maxPerStage; i++) + { + int j = stage * maxPerStage + i; + + bindings[j] = new DescriptorSetLayoutBinding + { + Binding = (uint)j, + DescriptorType = type, + DescriptorCount = 1, + StageFlags = stageFlags + }; + + bindingFlagsCreateInfo.PBindingFlags[j] = DescriptorBindingFlags.DescriptorBindingPartiallyBoundBit; + } + } + + Set(uLayoutBindings, uLayoutBindingFlags, Constants.MaxUniformBuffersPerStage, DescriptorType.UniformBuffer); + Set(sLayoutBindings, sLayoutBindingFlags, Constants.MaxStorageBuffersPerStage, DescriptorType.StorageBuffer); + Set(tLayoutBindings, tLayoutBindingFlags, Constants.MaxTexturesPerStage, DescriptorType.CombinedImageSampler); + Set(iLayoutBindings, iLayoutBindingFlags, Constants.MaxImagesPerStage, DescriptorType.StorageImage); + Set(bTLayoutBindings, bTLayoutBindingFlags, Constants.MaxTexturesPerStage, DescriptorType.UniformTexelBuffer); + Set(bILayoutBindings, bILayoutBindingFlags, Constants.MaxImagesPerStage, DescriptorType.StorageTexelBuffer); + } + + DescriptorSetLayout[] layouts = new DescriptorSetLayout[DescriptorSetLayouts]; + + var uDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo() + { + SType = StructureType.DescriptorSetLayoutCreateInfo, + PBindings = uLayoutBindings, + BindingCount = Constants.MaxUniformBufferBindings + }; + + var sDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo() + { + SType = StructureType.DescriptorSetLayoutCreateInfo, + PBindings = sLayoutBindings, + BindingCount = Constants.MaxStorageBufferBindings + }; + + var tDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo() + { + SType = StructureType.DescriptorSetLayoutCreateInfo, + PBindings = tLayoutBindings, + BindingCount = Constants.MaxTextureBindings + }; + + var iDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo() + { + SType = StructureType.DescriptorSetLayoutCreateInfo, + PBindings = iLayoutBindings, + BindingCount = Constants.MaxImageBindings + }; + + var bTDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo() + { + SType = StructureType.DescriptorSetLayoutCreateInfo, + PBindings = bTLayoutBindings, + BindingCount = Constants.MaxTextureBindings + }; + + var bIDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo() + { + SType = StructureType.DescriptorSetLayoutCreateInfo, + PBindings = bILayoutBindings, + BindingCount = Constants.MaxImageBindings + }; + + gd.Api.CreateDescriptorSetLayout(device, uDescriptorSetLayoutCreateInfo, null, out layouts[UniformSetIndex]).ThrowOnError(); + gd.Api.CreateDescriptorSetLayout(device, sDescriptorSetLayoutCreateInfo, null, out layouts[StorageSetIndex]).ThrowOnError(); + gd.Api.CreateDescriptorSetLayout(device, tDescriptorSetLayoutCreateInfo, null, out layouts[TextureSetIndex]).ThrowOnError(); + gd.Api.CreateDescriptorSetLayout(device, iDescriptorSetLayoutCreateInfo, null, out layouts[ImageSetIndex]).ThrowOnError(); + gd.Api.CreateDescriptorSetLayout(device, bTDescriptorSetLayoutCreateInfo, null, out layouts[BufferTextureSetIndex]).ThrowOnError(); + gd.Api.CreateDescriptorSetLayout(device, bIDescriptorSetLayoutCreateInfo, null, out layouts[BufferImageSetIndex]).ThrowOnError(); + + fixed (DescriptorSetLayout* pLayouts = layouts) + { + var pipelineLayoutCreateInfo = new PipelineLayoutCreateInfo() + { + SType = StructureType.PipelineLayoutCreateInfo, + PSetLayouts = pLayouts, + SetLayoutCount = DescriptorSetLayouts + }; + + gd.Api.CreatePipelineLayout(device, &pipelineLayoutCreateInfo, null, out layout).ThrowOnError(); + } + + return layouts; + } + + public void EndHostConditionalRendering() + { + if (Gd.Capabilities.SupportsConditionalRendering) + { + // Gd.ConditionalRenderingApi.CmdEndConditionalRendering(CommandBuffer); + } + else + { + throw new NotSupportedException(); + } + } + + public bool TryHostConditionalRendering(ICounterEvent value, ulong compare, bool isEqual) + { + // Compare an event and a constant value. + if (value is CounterQueueEvent evt) + { + // Easy host conditional rendering when the check matches what GL can do: + // - Event is of type samples passed. + // - Result is not a combination of multiple queries. + // - Comparing against 0. + // - Event has not already been flushed. + + if (evt.Disposed) + { + // If the event has been flushed, then just use the values on the CPU. + // The query object may already be repurposed for another draw (eg. begin + end). + return false; + } + + if (Gd.Capabilities.SupportsConditionalRendering && compare == 0 && evt.Type == CounterType.SamplesPassed && evt.ClearCounter) + { + var buffer = evt.GetBuffer().Get(Cbs, 0, sizeof(long)).Value; + var flags = isEqual ? ConditionalRenderingFlagsEXT.ConditionalRenderingInvertedBitExt : 0; + + var conditionalRenderingBeginInfo = new ConditionalRenderingBeginInfoEXT() + { + SType = StructureType.ConditionalRenderingBeginInfoExt, + Buffer = buffer, + Flags = flags + }; + + // Gd.ConditionalRenderingApi.CmdBeginConditionalRendering(CommandBuffer, conditionalRenderingBeginInfo); + return true; + } + } + + // The GPU will flush the queries to CPU and evaluate the condition there instead. + + FlushCommandsImpl(); // The thread will be stalled manually flushing the counter, so flush GL commands now. + return false; + } + + public bool TryHostConditionalRendering(ICounterEvent value, ICounterEvent compare, bool isEqual) + { + FlushCommandsImpl(); // The thread will be stalled manually flushing the counter, so flush GL commands now. + return false; + } + + public void FlushCommandsImpl([System.Runtime.CompilerServices.CallerMemberName] string caller = "") + { + // System.Console.WriteLine("flush by " + caller); + + EndRenderPass(); + PauseTransformFeedbackInternal(); + + foreach (var queryPool in _activeQueries) + { + Gd.Api.CmdEndQuery(CommandBuffer, queryPool, 0); + } + + CommandBuffer = (Cbs = Gd.CommandBufferPool.ReturnAndRent(Cbs)).CommandBuffer; + + // Restore per-command buffer state. + + if (Pipeline != null) + { + Gd.Api.CmdBindPipeline(CommandBuffer, Pbp, Pipeline.Get(Cbs).Value); + } + + foreach (var queryPool in _activeQueries) + { + Gd.Api.CmdResetQueryPool(CommandBuffer, queryPool, 0, 1); + Gd.Api.CmdBeginQuery(CommandBuffer, queryPool, 0, 0); + } + + ResumeTransformFeedbackInternal(); + SignalCommandBufferChange(); + } + + public void BeginQuery(QueryPool pool) + { + EndRenderPass(); + + Gd.Api.CmdResetQueryPool(CommandBuffer, pool, 0, 1); + Gd.Api.CmdBeginQuery(CommandBuffer, pool, 0, 0); + + _activeQueries.Add(pool); + } + + public void EndQuery(QueryPool pool) + { + Gd.Api.CmdEndQuery(CommandBuffer, pool, 0); + + _activeQueries.Remove(pool); + } + + public void CopyQueryResults(QueryPool pool, BufferHolder holder) + { + EndRenderPass(); + + var buffer = holder.GetBuffer(CommandBuffer, true).Get(Cbs, 0, sizeof(long)).Value; + + Gd.Api.CmdCopyQueryPoolResults( + CommandBuffer, + pool, + 0, + 1, + buffer, + 0, + sizeof(long), + QueryResultFlags.QueryResult64Bit | QueryResultFlags.QueryResultWaitBit); + + _hasPendingQuery = true; + } + + protected override void SignalProgramChange() + { + if (_hasPendingQuery) + { + _hasPendingQuery = false; + FlushCommandsImpl(); + } + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/PipelineLayoutCache.cs b/Ryujinx.Graphics.Vulkan/PipelineLayoutCache.cs new file mode 100644 index 000000000..a1de6781e --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/PipelineLayoutCache.cs @@ -0,0 +1,40 @@ +using Silk.NET.Vulkan; + +namespace Ryujinx.Graphics.Vulkan +{ + class PipelineLayoutCache + { + private PipelineLayoutCacheEntry[] _plce; + + public PipelineLayoutCache() + { + _plce = new PipelineLayoutCacheEntry[1 << Constants.MaxShaderStages]; + } + + public PipelineLayoutCacheEntry GetOrCreate(VulkanGraphicsDevice gd, Device device, uint stages) + { + if (_plce[stages] == null) + { + _plce[stages] = new PipelineLayoutCacheEntry(gd, device, stages); + } + + return _plce[stages]; + } + + protected virtual unsafe void Dispose(bool disposing) + { + if (disposing) + { + for (int i = 0; i < _plce.Length; i++) + { + _plce[i]?.Dispose(); + } + } + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs b/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs new file mode 100644 index 000000000..4505362d5 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs @@ -0,0 +1,108 @@ +using Silk.NET.Vulkan; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Vulkan +{ + class PipelineLayoutCacheEntry + { + private readonly VulkanGraphicsDevice _gd; + private readonly Device _device; + + public DescriptorSetLayout[] DescriptorSetLayouts { get; } + public PipelineLayout PipelineLayout { get; } + + private readonly List>[][] _dsCache; + private readonly int[] _dsCacheCursor; + private int _dsLastCbIndex; + + private readonly uint _stages; + + public PipelineLayoutCacheEntry(VulkanGraphicsDevice gd, Device device, uint stages) + { + _gd = gd; + _device = device; + _stages = stages; + + DescriptorSetLayouts = PipelineLayoutFactory.Create(gd, device, stages, out var pipelineLayout); + PipelineLayout = pipelineLayout; + + _dsCache = new List>[CommandBufferPool.MaxCommandBuffers][]; + + for (int i = 0; i < CommandBufferPool.MaxCommandBuffers; i++) + { + _dsCache[i] = new List>[PipelineBase.DescriptorSetLayouts]; + + for (int j = 0; j < PipelineBase.DescriptorSetLayouts; j++) + { + _dsCache[i][j] = new List>(); + } + } + + _dsCacheCursor = new int[PipelineBase.DescriptorSetLayouts]; + } + + public Auto GetNewDescriptorSetCollection( + VulkanGraphicsDevice gd, + int commandBufferIndex, + int setIndex, + out bool isNew) + { + if (_dsLastCbIndex != commandBufferIndex) + { + _dsLastCbIndex = commandBufferIndex; + + for (int i = 0; i < PipelineBase.DescriptorSetLayouts; i++) + { + _dsCacheCursor[i] = 0; + } + } + + var list = _dsCache[commandBufferIndex][setIndex]; + int index = _dsCacheCursor[setIndex]++; + if (index == list.Count) + { + var dsc = gd.DescriptorSetManager.AllocateDescriptorSet(gd.Api, DescriptorSetLayouts[setIndex]); + list.Add(dsc); + isNew = true; + return dsc; + } + + isNew = false; + return list[index]; + } + + protected virtual unsafe void Dispose(bool disposing) + { + if (disposing) + { + unsafe + { + for (int i = 0; i < _dsCache.Length; i++) + { + for (int y = 0; y < _dsCache[i].Length; y++) + { + for (int z = 0; z < _dsCache[i][y].Count; z++) + { + _dsCache[i][y][z].Dispose(); + } + + _dsCache[i][y].Clear(); + } + } + + _gd.Api.DestroyPipelineLayout(_device, PipelineLayout, null); + + for (int i = 0; i < DescriptorSetLayouts.Length; i++) + { + _gd.Api.DestroyDescriptorSetLayout(_device, DescriptorSetLayouts[i], null); + } + } + } + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs b/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs new file mode 100644 index 000000000..3ceacfd0e --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs @@ -0,0 +1,150 @@ +using Silk.NET.Vulkan; +using System.Numerics; + +namespace Ryujinx.Graphics.Vulkan +{ + static class PipelineLayoutFactory + { + public static unsafe DescriptorSetLayout[] Create(VulkanGraphicsDevice gd, Device device, uint stages, out PipelineLayout layout) + { + int stagesCount = BitOperations.PopCount(stages); + + int uCount = Constants.MaxUniformBuffersPerStage * stagesCount + 1; + int tCount = Constants.MaxTexturesPerStage * stagesCount; + int iCount = Constants.MaxImagesPerStage * stagesCount; + int bTCount = tCount; + int bICount = iCount; + + DescriptorSetLayoutBinding* uLayoutBindings = stackalloc DescriptorSetLayoutBinding[uCount]; + DescriptorSetLayoutBinding* sLayoutBindings = stackalloc DescriptorSetLayoutBinding[stagesCount]; + DescriptorSetLayoutBinding* tLayoutBindings = stackalloc DescriptorSetLayoutBinding[tCount]; + DescriptorSetLayoutBinding* iLayoutBindings = stackalloc DescriptorSetLayoutBinding[iCount]; + DescriptorSetLayoutBinding* bTLayoutBindings = stackalloc DescriptorSetLayoutBinding[bTCount]; + DescriptorSetLayoutBinding* bILayoutBindings = stackalloc DescriptorSetLayoutBinding[bICount]; + + uLayoutBindings[0] = new DescriptorSetLayoutBinding + { + Binding = 0, + DescriptorType = DescriptorType.UniformBuffer, + DescriptorCount = 1, + StageFlags = ShaderStageFlags.ShaderStageFragmentBit | ShaderStageFlags.ShaderStageComputeBit + }; + + int iter = 0; + + while (stages != 0) + { + int stage = BitOperations.TrailingZeroCount(stages); + stages &= ~(1u << stage); + + var stageFlags = stage switch + { + 1 => ShaderStageFlags.ShaderStageFragmentBit, + 2 => ShaderStageFlags.ShaderStageGeometryBit, + 3 => ShaderStageFlags.ShaderStageTessellationControlBit, + 4 => ShaderStageFlags.ShaderStageTessellationEvaluationBit, + _ => ShaderStageFlags.ShaderStageVertexBit | ShaderStageFlags.ShaderStageComputeBit + }; + + void Set(DescriptorSetLayoutBinding* bindings, int maxPerStage, DescriptorType type, int start = 0) + { + for (int i = 0; i < maxPerStage; i++) + { + bindings[start + iter * maxPerStage + i] = new DescriptorSetLayoutBinding + { + Binding = (uint)(start + stage * maxPerStage + i), + DescriptorType = type, + DescriptorCount = 1, + StageFlags = stageFlags + }; + } + } + + void SetStorage(DescriptorSetLayoutBinding* bindings, int maxPerStage, int start = 0) + { + bindings[start + iter] = new DescriptorSetLayoutBinding + { + Binding = (uint)(start + stage * maxPerStage), + DescriptorType = DescriptorType.StorageBuffer, + DescriptorCount = (uint)maxPerStage, + StageFlags = stageFlags + }; + } + + Set(uLayoutBindings, Constants.MaxUniformBuffersPerStage, DescriptorType.UniformBuffer, 1); + SetStorage(sLayoutBindings, Constants.MaxStorageBuffersPerStage); + Set(tLayoutBindings, Constants.MaxTexturesPerStage, DescriptorType.CombinedImageSampler); + Set(iLayoutBindings, Constants.MaxImagesPerStage, DescriptorType.StorageImage); + Set(bTLayoutBindings, Constants.MaxTexturesPerStage, DescriptorType.UniformTexelBuffer); + Set(bILayoutBindings, Constants.MaxImagesPerStage, DescriptorType.StorageTexelBuffer); + + iter++; + } + + DescriptorSetLayout[] layouts = new DescriptorSetLayout[PipelineFull.DescriptorSetLayouts]; + + var uDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo() + { + SType = StructureType.DescriptorSetLayoutCreateInfo, + PBindings = uLayoutBindings, + BindingCount = (uint)uCount + }; + + var sDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo() + { + SType = StructureType.DescriptorSetLayoutCreateInfo, + PBindings = sLayoutBindings, + BindingCount = (uint)stagesCount + }; + + var tDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo() + { + SType = StructureType.DescriptorSetLayoutCreateInfo, + PBindings = tLayoutBindings, + BindingCount = (uint)tCount + }; + + var iDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo() + { + SType = StructureType.DescriptorSetLayoutCreateInfo, + PBindings = iLayoutBindings, + BindingCount = (uint)iCount + }; + + var bTDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo() + { + SType = StructureType.DescriptorSetLayoutCreateInfo, + PBindings = bTLayoutBindings, + BindingCount = (uint)bTCount + }; + + var bIDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo() + { + SType = StructureType.DescriptorSetLayoutCreateInfo, + PBindings = bILayoutBindings, + BindingCount = (uint)bICount + }; + + gd.Api.CreateDescriptorSetLayout(device, uDescriptorSetLayoutCreateInfo, null, out layouts[PipelineFull.UniformSetIndex]).ThrowOnError(); + gd.Api.CreateDescriptorSetLayout(device, sDescriptorSetLayoutCreateInfo, null, out layouts[PipelineFull.StorageSetIndex]).ThrowOnError(); + gd.Api.CreateDescriptorSetLayout(device, tDescriptorSetLayoutCreateInfo, null, out layouts[PipelineFull.TextureSetIndex]).ThrowOnError(); + gd.Api.CreateDescriptorSetLayout(device, iDescriptorSetLayoutCreateInfo, null, out layouts[PipelineFull.ImageSetIndex]).ThrowOnError(); + gd.Api.CreateDescriptorSetLayout(device, bTDescriptorSetLayoutCreateInfo, null, out layouts[PipelineFull.BufferTextureSetIndex]).ThrowOnError(); + gd.Api.CreateDescriptorSetLayout(device, bIDescriptorSetLayoutCreateInfo, null, out layouts[PipelineFull.BufferImageSetIndex]).ThrowOnError(); + + fixed (DescriptorSetLayout* pLayouts = layouts) + { + var pipelineLayoutCreateInfo = new PipelineLayoutCreateInfo() + { + SType = StructureType.PipelineLayoutCreateInfo, + PSetLayouts = pLayouts, + SetLayoutCount = PipelineFull.DescriptorSetLayouts + }; + + gd.Api.CreatePipelineLayout(device, &pipelineLayoutCreateInfo, null, out layout).ThrowOnError(); + } + + return layouts; + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/PipelineState.cs b/Ryujinx.Graphics.Vulkan/PipelineState.cs new file mode 100644 index 000000000..246a84c79 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/PipelineState.cs @@ -0,0 +1,526 @@ +using Silk.NET.Vulkan; +using System; + +namespace Ryujinx.Graphics.Vulkan +{ + struct PipelineState : IDisposable + { + public PipelineUid Internal; + + public float LineWidth + { + get => BitConverter.Int32BitsToSingle((int)((Internal.Id0 >> 0) & 0xFFFFFFFF)); + set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFF00000000) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 0); + } + + public float DepthBiasClamp + { + get => BitConverter.Int32BitsToSingle((int)((Internal.Id0 >> 32) & 0xFFFFFFFF)); + set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFF) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 32); + } + + public float DepthBiasConstantFactor + { + get => BitConverter.Int32BitsToSingle((int)((Internal.Id1 >> 0) & 0xFFFFFFFF)); + set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFF00000000) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 0); + } + + public float DepthBiasSlopeFactor + { + get => BitConverter.Int32BitsToSingle((int)((Internal.Id1 >> 32) & 0xFFFFFFFF)); + set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFF) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 32); + } + + public uint StencilFrontCompareMask + { + get => (UInt32)((Internal.Id2 >> 0) & 0xFFFFFFFF); + set => Internal.Id2 = (Internal.Id2 & 0xFFFFFFFF00000000) | ((ulong)value << 0); + } + + public uint StencilFrontWriteMask + { + get => (UInt32)((Internal.Id2 >> 32) & 0xFFFFFFFF); + set => Internal.Id2 = (Internal.Id2 & 0xFFFFFFFF) | ((ulong)value << 32); + } + + public uint StencilFrontReference + { + get => (UInt32)((Internal.Id3 >> 0) & 0xFFFFFFFF); + set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFF00000000) | ((ulong)value << 0); + } + + public uint StencilBackCompareMask + { + get => (UInt32)((Internal.Id3 >> 32) & 0xFFFFFFFF); + set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFF) | ((ulong)value << 32); + } + + public uint StencilBackWriteMask + { + get => (UInt32)((Internal.Id4 >> 0) & 0xFFFFFFFF); + set => Internal.Id4 = (Internal.Id4 & 0xFFFFFFFF00000000) | ((ulong)value << 0); + } + + public uint StencilBackReference + { + get => (UInt32)((Internal.Id4 >> 32) & 0xFFFFFFFF); + set => Internal.Id4 = (Internal.Id4 & 0xFFFFFFFF) | ((ulong)value << 32); + } + + public float MinDepthBounds + { + get => BitConverter.Int32BitsToSingle((int)((Internal.Id5 >> 0) & 0xFFFFFFFF)); + set => Internal.Id5 = (Internal.Id5 & 0xFFFFFFFF00000000) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 0); + } + + public float MaxDepthBounds + { + get => BitConverter.Int32BitsToSingle((int)((Internal.Id5 >> 32) & 0xFFFFFFFF)); + set => Internal.Id5 = (Internal.Id5 & 0xFFFFFFFF) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 32); + } + + public float BlendConstantR + { + get => BitConverter.Int32BitsToSingle((int)((Internal.Id6 >> 0) & 0xFFFFFFFF)); + set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFF00000000) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 0); + } + + public float BlendConstantG + { + get => BitConverter.Int32BitsToSingle((int)((Internal.Id6 >> 32) & 0xFFFFFFFF)); + set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFF) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 32); + } + + public float BlendConstantB + { + get => BitConverter.Int32BitsToSingle((int)((Internal.Id7 >> 0) & 0xFFFFFFFF)); + set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFF00000000) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 0); + } + + public float BlendConstantA + { + get => BitConverter.Int32BitsToSingle((int)((Internal.Id7 >> 32) & 0xFFFFFFFF)); + set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFF) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 32); + } + + public PolygonMode PolygonMode + { + get => (PolygonMode)((Internal.Id8 >> 0) & 0x3FFFFFFF); + set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFC0000000) | ((ulong)value << 0); + } + + public uint StagesCount + { + get => (byte)((Internal.Id8 >> 30) & 0xFF); + set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFC03FFFFFFF) | ((ulong)value << 30); + } + + public uint VertexAttributeDescriptionsCount + { + get => (byte)((Internal.Id8 >> 38) & 0xFF); + set => Internal.Id8 = (Internal.Id8 & 0xFFFFC03FFFFFFFFF) | ((ulong)value << 38); + } + + public uint VertexBindingDescriptionsCount + { + get => (byte)((Internal.Id8 >> 46) & 0xFF); + set => Internal.Id8 = (Internal.Id8 & 0xFFC03FFFFFFFFFFF) | ((ulong)value << 46); + } + + public uint ViewportsCount + { + get => (byte)((Internal.Id8 >> 54) & 0xFF); + set => Internal.Id8 = (Internal.Id8 & 0xC03FFFFFFFFFFFFF) | ((ulong)value << 54); + } + + public uint ScissorsCount + { + get => (byte)((Internal.Id9 >> 0) & 0xFF); + set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFF00) | ((ulong)value << 0); + } + + public uint ColorBlendAttachmentStateCount + { + get => (byte)((Internal.Id9 >> 8) & 0xFF); + set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFF00FF) | ((ulong)value << 8); + } + + public PrimitiveTopology Topology + { + get => (PrimitiveTopology)((Internal.Id9 >> 16) & 0xF); + set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFF0FFFF) | ((ulong)value << 16); + } + + public LogicOp LogicOp + { + get => (LogicOp)((Internal.Id9 >> 20) & 0xF); + set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFF0FFFFF) | ((ulong)value << 20); + } + + public CompareOp DepthCompareOp + { + get => (CompareOp)((Internal.Id9 >> 24) & 0x7); + set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFF8FFFFFF) | ((ulong)value << 24); + } + + public StencilOp StencilFrontFailOp + { + get => (StencilOp)((Internal.Id9 >> 27) & 0x7); + set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFC7FFFFFF) | ((ulong)value << 27); + } + + public StencilOp StencilFrontPassOp + { + get => (StencilOp)((Internal.Id9 >> 30) & 0x7); + set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFE3FFFFFFF) | ((ulong)value << 30); + } + + public StencilOp StencilFrontDepthFailOp + { + get => (StencilOp)((Internal.Id9 >> 33) & 0x7); + set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFF1FFFFFFFF) | ((ulong)value << 33); + } + + public CompareOp StencilFrontCompareOp + { + get => (CompareOp)((Internal.Id9 >> 36) & 0x7); + set => Internal.Id9 = (Internal.Id9 & 0xFFFFFF8FFFFFFFFF) | ((ulong)value << 36); + } + + public StencilOp StencilBackFailOp + { + get => (StencilOp)((Internal.Id9 >> 39) & 0x7); + set => Internal.Id9 = (Internal.Id9 & 0xFFFFFC7FFFFFFFFF) | ((ulong)value << 39); + } + + public StencilOp StencilBackPassOp + { + get => (StencilOp)((Internal.Id9 >> 42) & 0x7); + set => Internal.Id9 = (Internal.Id9 & 0xFFFFE3FFFFFFFFFF) | ((ulong)value << 42); + } + + public StencilOp StencilBackDepthFailOp + { + get => (StencilOp)((Internal.Id9 >> 45) & 0x7); + set => Internal.Id9 = (Internal.Id9 & 0xFFFF1FFFFFFFFFFF) | ((ulong)value << 45); + } + + public CompareOp StencilBackCompareOp + { + get => (CompareOp)((Internal.Id9 >> 48) & 0x7); + set => Internal.Id9 = (Internal.Id9 & 0xFFF8FFFFFFFFFFFF) | ((ulong)value << 48); + } + + public CullModeFlags CullMode + { + get => (CullModeFlags)((Internal.Id9 >> 51) & 0x3); + set => Internal.Id9 = (Internal.Id9 & 0xFFE7FFFFFFFFFFFF) | ((ulong)value << 51); + } + + public bool PrimitiveRestartEnable + { + get => ((Internal.Id9 >> 53) & 0x1) != 0UL; + set => Internal.Id9 = (Internal.Id9 & 0xFFDFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 53); + } + + public bool DepthClampEnable + { + get => ((Internal.Id9 >> 54) & 0x1) != 0UL; + set => Internal.Id9 = (Internal.Id9 & 0xFFBFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 54); + } + + public bool RasterizerDiscardEnable + { + get => ((Internal.Id9 >> 55) & 0x1) != 0UL; + set => Internal.Id9 = (Internal.Id9 & 0xFF7FFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 55); + } + + public FrontFace FrontFace + { + get => (FrontFace)((Internal.Id9 >> 56) & 0x1); + set => Internal.Id9 = (Internal.Id9 & 0xFEFFFFFFFFFFFFFF) | ((ulong)value << 56); + } + + public bool DepthBiasEnable + { + get => ((Internal.Id9 >> 57) & 0x1) != 0UL; + set => Internal.Id9 = (Internal.Id9 & 0xFDFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 57); + } + + public bool DepthTestEnable + { + get => ((Internal.Id9 >> 58) & 0x1) != 0UL; + set => Internal.Id9 = (Internal.Id9 & 0xFBFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 58); + } + + public bool DepthWriteEnable + { + get => ((Internal.Id9 >> 59) & 0x1) != 0UL; + set => Internal.Id9 = (Internal.Id9 & 0xF7FFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 59); + } + + public bool DepthBoundsTestEnable + { + get => ((Internal.Id9 >> 60) & 0x1) != 0UL; + set => Internal.Id9 = (Internal.Id9 & 0xEFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 60); + } + + public bool StencilTestEnable + { + get => ((Internal.Id9 >> 61) & 0x1) != 0UL; + set => Internal.Id9 = (Internal.Id9 & 0xDFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 61); + } + + public bool LogicOpEnable + { + get => ((Internal.Id9 >> 62) & 0x1) != 0UL; + set => Internal.Id9 = (Internal.Id9 & 0xBFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 62); + } + + public bool HasDepthStencil + { + get => ((Internal.Id9 >> 63) & 0x1) != 0UL; + set => Internal.Id9 = (Internal.Id9 & 0x7FFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 63); + } + + public uint PatchControlPoints + { + get => (UInt32)((Internal.Id10 >> 0) & 0xFFFFFFFF); + set => Internal.Id10 = (Internal.Id10 & 0xFFFFFFFF00000000) | ((ulong)value << 0); + } + + public NativeArray Stages; + public PipelineLayout PipelineLayout; + + public void Initialize() + { + Stages = new NativeArray(Constants.MaxShaderStages); + } + + public unsafe Auto CreateComputePipeline(Vk api, Device device, ShaderCollection program, PipelineCache cache) + { + if (program.TryGetComputePipeline(out var pipeline)) + { + return pipeline; + } + + var pipelineCreateInfo = new ComputePipelineCreateInfo() + { + SType = StructureType.ComputePipelineCreateInfo, + Stage = Stages[0], + BasePipelineIndex = -1, + Layout = PipelineLayout + }; + + Pipeline pipelineHandle = default; + + api.CreateComputePipelines(device, cache, 1, &pipelineCreateInfo, null, &pipelineHandle).ThrowOnError(); + + pipeline = new Auto(new DisposablePipeline(api, device, pipelineHandle)); + + program.AddComputePipeline(pipeline); + + return pipeline; + } + + public unsafe void DestroyComputePipeline(ShaderCollection program) + { + program.RemoveComputePipeline(); + } + + public unsafe Auto CreateGraphicsPipeline( + Vk api, + Device device, + ShaderCollection program, + PipelineCache cache, + RenderPass renderPass) + { + if (program.TryGetGraphicsPipeline(ref Internal, out var pipeline)) + { + return pipeline; + } + + Pipeline pipelineHandle = default; + + fixed (VertexInputAttributeDescription* pVertexAttributeDescriptions = &Internal.VertexAttributeDescriptions[0]) + fixed (VertexInputBindingDescription* pVertexBindingDescriptions = &Internal.VertexBindingDescriptions[0]) + fixed (Viewport* pViewports = &Internal.Viewports[0]) + fixed (Rect2D* pScissors = &Internal.Scissors[0]) + fixed (PipelineColorBlendAttachmentState* pColorBlendAttachmentState = &Internal.ColorBlendAttachmentState[0]) + { + var vertexInputState = new PipelineVertexInputStateCreateInfo + { + SType = StructureType.PipelineVertexInputStateCreateInfo, + VertexAttributeDescriptionCount = VertexAttributeDescriptionsCount, + PVertexAttributeDescriptions = pVertexAttributeDescriptions, + VertexBindingDescriptionCount = VertexBindingDescriptionsCount, + PVertexBindingDescriptions = pVertexBindingDescriptions + }; + + bool primitiveRestartEnable = PrimitiveRestartEnable; + + primitiveRestartEnable &= Topology == PrimitiveTopology.LineStrip || + Topology == PrimitiveTopology.TriangleStrip || + Topology == PrimitiveTopology.TriangleFan || + Topology == PrimitiveTopology.LineStripWithAdjacency || + Topology == PrimitiveTopology.TriangleStripWithAdjacency; + + var inputAssemblyState = new PipelineInputAssemblyStateCreateInfo() + { + SType = StructureType.PipelineInputAssemblyStateCreateInfo, + PrimitiveRestartEnable = primitiveRestartEnable, + Topology = Topology + }; + + var tessellationState = new PipelineTessellationStateCreateInfo() + { + SType = StructureType.PipelineTessellationStateCreateInfo, + PatchControlPoints = PatchControlPoints + }; + + var depthClipState = new PipelineRasterizationDepthClipStateCreateInfoEXT() + { + SType = StructureType.PipelineRasterizationDepthClipStateCreateInfoExt, + DepthClipEnable = false + }; + + var rasterizationState = new PipelineRasterizationStateCreateInfo() + { + SType = StructureType.PipelineRasterizationStateCreateInfo, + DepthClampEnable = DepthClampEnable, + RasterizerDiscardEnable = RasterizerDiscardEnable, + PolygonMode = PolygonMode, + LineWidth = LineWidth, + CullMode = CullMode, + FrontFace = FrontFace, + DepthBiasEnable = DepthBiasEnable, + DepthBiasClamp = DepthBiasClamp, + DepthBiasConstantFactor = DepthBiasConstantFactor, + DepthBiasSlopeFactor = DepthBiasSlopeFactor, + PNext = &depthClipState + }; + + var viewportState = new PipelineViewportStateCreateInfo() + { + SType = StructureType.PipelineViewportStateCreateInfo, + ViewportCount = ViewportsCount, + PViewports = pViewports, + ScissorCount = ScissorsCount, + PScissors = pScissors + }; + + var multisampleState = new PipelineMultisampleStateCreateInfo + { + SType = StructureType.PipelineMultisampleStateCreateInfo, + SampleShadingEnable = false, + RasterizationSamples = SampleCountFlags.SampleCount1Bit, + MinSampleShading = 1 + }; + + var stencilFront = new StencilOpState( + StencilFrontFailOp, + StencilFrontPassOp, + StencilFrontDepthFailOp, + StencilFrontCompareOp, + StencilFrontCompareMask, + StencilFrontWriteMask, + StencilFrontReference); + + var stencilBack = new StencilOpState( + StencilBackFailOp, + StencilBackPassOp, + StencilBackDepthFailOp, + StencilBackCompareOp, + StencilBackCompareMask, + StencilBackWriteMask, + StencilBackReference); + + var depthStencilState = new PipelineDepthStencilStateCreateInfo() + { + SType = StructureType.PipelineDepthStencilStateCreateInfo, + DepthTestEnable = DepthTestEnable, + DepthWriteEnable = DepthWriteEnable, + DepthCompareOp = DepthCompareOp, + DepthBoundsTestEnable = DepthBoundsTestEnable, + StencilTestEnable = StencilTestEnable, + Front = stencilFront, + Back = stencilBack, + MinDepthBounds = MinDepthBounds, + MaxDepthBounds = MaxDepthBounds + }; + + var colorBlendState = new PipelineColorBlendStateCreateInfo() + { + SType = StructureType.PipelineColorBlendStateCreateInfo, + LogicOpEnable = LogicOpEnable, + LogicOp = LogicOp, + AttachmentCount = ColorBlendAttachmentStateCount, + PAttachments = pColorBlendAttachmentState + }; + + colorBlendState.BlendConstants[0] = BlendConstantR; + colorBlendState.BlendConstants[1] = BlendConstantG; + colorBlendState.BlendConstants[2] = BlendConstantB; + colorBlendState.BlendConstants[3] = BlendConstantA; + + PipelineDynamicStateCreateInfo* pDynamicState = null; + + if (VulkanConfiguration.UseDynamicState) + { + const int DynamicStates = 7; + + DynamicState* dynamicStates = stackalloc DynamicState[DynamicStates]; + + dynamicStates[0] = DynamicState.Viewport; + dynamicStates[1] = DynamicState.Scissor; + dynamicStates[2] = DynamicState.DepthBias; + dynamicStates[3] = DynamicState.DepthBounds; + dynamicStates[4] = DynamicState.StencilCompareMask; + dynamicStates[5] = DynamicState.StencilWriteMask; + dynamicStates[6] = DynamicState.StencilReference; + + var pipelineDynamicStateCreateInfo = new PipelineDynamicStateCreateInfo() + { + SType = StructureType.PipelineDynamicStateCreateInfo, + DynamicStateCount = DynamicStates, + PDynamicStates = dynamicStates + }; + + pDynamicState = &pipelineDynamicStateCreateInfo; + } + + var pipelineCreateInfo = new GraphicsPipelineCreateInfo() + { + SType = StructureType.GraphicsPipelineCreateInfo, + StageCount = StagesCount, + PStages = Stages.Pointer, + PVertexInputState = &vertexInputState, + PInputAssemblyState = &inputAssemblyState, + PTessellationState = &tessellationState, + PViewportState = &viewportState, + PRasterizationState = &rasterizationState, + PMultisampleState = &multisampleState, + PDepthStencilState = &depthStencilState, + PColorBlendState = &colorBlendState, + PDynamicState = pDynamicState, + Layout = PipelineLayout, + RenderPass = renderPass, + BasePipelineIndex = -1 + }; + + api.CreateGraphicsPipelines(device, cache, 1, &pipelineCreateInfo, null, &pipelineHandle).ThrowOnError(); + } + + pipeline = new Auto(new DisposablePipeline(api, device, pipelineHandle)); + + program.AddGraphicsPipeline(ref Internal, pipeline); + + return pipeline; + } + + public void Dispose() + { + Stages.Dispose(); + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/PipelineUid.cs b/Ryujinx.Graphics.Vulkan/PipelineUid.cs new file mode 100644 index 000000000..da5d1a4d7 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/PipelineUid.cs @@ -0,0 +1,145 @@ +using Ryujinx.Common.Memory; +using Silk.NET.Vulkan; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; + +namespace Ryujinx.Graphics.Vulkan +{ + struct PipelineUid : IRefEquatable + { + public ulong Id0; + public ulong Id1; + public ulong Id2; + public ulong Id3; + + public ulong Id4; + public ulong Id5; + public ulong Id6; + public ulong Id7; + + public ulong Id8; + public ulong Id9; + public ulong Id10; + public ulong Padding; + + private uint VertexAttributeDescriptionsCount => (byte)((Id8 >> 38) & 0xFF); + private uint VertexBindingDescriptionsCount => (byte)((Id8 >> 46) & 0xFF); + private uint ViewportsCount => (byte)((Id8 >> 54) & 0xFF); + private uint ScissorsCount => (byte)((Id9 >> 0) & 0xFF); + private uint ColorBlendAttachmentStateCount => (byte)((Id9 >> 8) & 0xFF); + private bool HasDepthStencil => ((Id9 >> 63) & 0x1) != 0UL; + + public Array32 VertexAttributeDescriptions; + public Array33 VertexBindingDescriptions; + public Array16 Viewports; + public Array16 Scissors; + public Array8 ColorBlendAttachmentState; + public Array9 AttachmentFormats; + + public override bool Equals(object obj) + { + return obj is PipelineUid other && Equals(other); + } + + public bool Equals(ref PipelineUid other) + { + if (!Unsafe.As>(ref Id0).Equals(Unsafe.As>(ref other.Id0)) || + !Unsafe.As>(ref Id4).Equals(Unsafe.As>(ref other.Id4)) || + !Unsafe.As>(ref Id8).Equals(Unsafe.As>(ref other.Id8))) + { + return false; + } + + if (!SequenceEqual(VertexAttributeDescriptions.ToSpan(), other.VertexAttributeDescriptions.ToSpan(), VertexAttributeDescriptionsCount)) + { + return false; + } + + if (!SequenceEqual(VertexBindingDescriptions.ToSpan(), other.VertexBindingDescriptions.ToSpan(), VertexBindingDescriptionsCount)) + { + return false; + } + + if (!VulkanConfiguration.UseDynamicState) + { + if (!SequenceEqual(Viewports.ToSpan(), other.Viewports.ToSpan(), ViewportsCount)) + { + return false; + } + + if (!SequenceEqual(Scissors.ToSpan(), other.Scissors.ToSpan(), ScissorsCount)) + { + return false; + } + } + + if (!SequenceEqual(ColorBlendAttachmentState.ToSpan(), other.ColorBlendAttachmentState.ToSpan(), ColorBlendAttachmentStateCount)) + { + return false; + } + + if (!SequenceEqual(AttachmentFormats.ToSpan(), other.AttachmentFormats.ToSpan(), ColorBlendAttachmentStateCount + (HasDepthStencil ? 1u : 0u))) + { + return false; + } + + return true; + } + + private static bool SequenceEqual(ReadOnlySpan x, ReadOnlySpan y, uint count) where T : unmanaged + { + return MemoryMarshal.Cast(x.Slice(0, (int)count)).SequenceEqual(MemoryMarshal.Cast(y.Slice(0, (int)count))); + } + + public override int GetHashCode() + { + ulong hash64 = Id0 * 23 ^ + Id1 * 23 ^ + Id2 * 23 ^ + Id3 * 23 ^ + Id4 * 23 ^ + Id5 * 23 ^ + Id6 * 23 ^ + Id7 * 23 ^ + Id8 * 23 ^ + Id9 * 23 ^ + Id10 * 23; + + for (int i = 0; i < (int)VertexAttributeDescriptionsCount; i++) + { + hash64 ^= VertexAttributeDescriptions[i].Binding * 23; + hash64 ^= (uint)VertexAttributeDescriptions[i].Format * 23; + hash64 ^= VertexAttributeDescriptions[i].Location * 23; + hash64 ^= VertexAttributeDescriptions[i].Offset * 23; + } + + for (int i = 0; i < (int)VertexBindingDescriptionsCount; i++) + { + hash64 ^= VertexBindingDescriptions[i].Binding * 23; + hash64 ^= (uint)VertexBindingDescriptions[i].InputRate * 23; + hash64 ^= VertexBindingDescriptions[i].Stride * 23; + } + + for (int i = 0; i < (int)ColorBlendAttachmentStateCount; i++) + { + hash64 ^= ColorBlendAttachmentState[i].BlendEnable * 23; + hash64 ^= (uint)ColorBlendAttachmentState[i].SrcColorBlendFactor * 23; + hash64 ^= (uint)ColorBlendAttachmentState[i].DstColorBlendFactor * 23; + hash64 ^= (uint)ColorBlendAttachmentState[i].ColorBlendOp * 23; + hash64 ^= (uint)ColorBlendAttachmentState[i].SrcAlphaBlendFactor * 23; + hash64 ^= (uint)ColorBlendAttachmentState[i].DstAlphaBlendFactor * 23; + hash64 ^= (uint)ColorBlendAttachmentState[i].AlphaBlendOp * 23; + hash64 ^= (uint)ColorBlendAttachmentState[i].ColorWriteMask * 23; + } + + for (int i = 0; i < (int)ColorBlendAttachmentStateCount; i++) + { + hash64 ^= (uint)AttachmentFormats[i] * 23; + } + + return (int)hash64 ^ ((int)(hash64 >> 32) * 17); + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/Queries/BufferedQuery.cs b/Ryujinx.Graphics.Vulkan/Queries/BufferedQuery.cs new file mode 100644 index 000000000..9efdf2675 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/Queries/BufferedQuery.cs @@ -0,0 +1,132 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Ryujinx.Graphics.Vulkan.Queries +{ + class BufferedQuery : IDisposable + { + private const int MaxQueryRetries = 5000; + private const long DefaultValue = -1; + + private readonly Vk _api; + private readonly Device _device; + private readonly PipelineFull _pipeline; + + private QueryPool _queryPool; + + private readonly BufferHolder _buffer; + private readonly IntPtr _bufferMap; + private readonly CounterType _type; + + public unsafe BufferedQuery(VulkanGraphicsDevice gd, Device device, PipelineFull pipeline, CounterType type) + { + _api = gd.Api; + _device = device; + _pipeline = pipeline; + + var queryPoolCreateInfo = new QueryPoolCreateInfo() + { + SType = StructureType.QueryPoolCreateInfo, + QueryCount = 1, + QueryType = GetQueryType(type) + }; + + gd.Api.CreateQueryPool(device, queryPoolCreateInfo, null, out _queryPool).ThrowOnError(); + + var buffer = gd.BufferManager.Create(gd, sizeof(long), forConditionalRendering: true); + + _bufferMap = buffer.Map(0, sizeof(long)); + Marshal.WriteInt64(_bufferMap, -1L); + _buffer = buffer; + } + + private static QueryType GetQueryType(CounterType type) + { + return type switch + { + CounterType.SamplesPassed => QueryType.Occlusion, + CounterType.PrimitivesGenerated => QueryType.PipelineStatistics, + CounterType.TransformFeedbackPrimitivesWritten => QueryType.TransformFeedbackStreamExt, + _ => QueryType.Occlusion + }; + } + + public Auto GetBuffer() + { + return _buffer.GetBuffer(); + } + + public void Reset() + { + End(false); + Begin(); + } + + public void Begin() + { + _pipeline.BeginQuery(_queryPool); + } + + public unsafe void End(bool withResult) + { + _pipeline.EndQuery(_queryPool); + + if (withResult) + { + Marshal.WriteInt64(_bufferMap, -1L); + _pipeline.CopyQueryResults(_queryPool, _buffer); + // _pipeline.FlushCommandsImpl(); + } + } + + public bool TryGetResult(out long result) + { + result = Marshal.ReadInt64(_bufferMap); + + return result != DefaultValue; + } + + public long AwaitResult(AutoResetEvent wakeSignal = null) + { + long data = DefaultValue; + + if (wakeSignal == null) + { + while (data == DefaultValue) + { + data = Marshal.ReadInt64(_bufferMap); + } + } + else + { + int iterations = 0; + while (data == DefaultValue && iterations++ < MaxQueryRetries) + { + data = Marshal.ReadInt64(_bufferMap); + if (data == DefaultValue) + { + wakeSignal.WaitOne(1); + } + } + + if (iterations >= MaxQueryRetries) + { + Logger.Error?.Print(LogClass.Gpu, $"Error: Query result timed out. Took more than {MaxQueryRetries} tries."); + } + } + + return data; + } + + public unsafe void Dispose() + { + _buffer.Dispose(); + _api.DestroyQueryPool(_device, _queryPool, null); + _queryPool = default; + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/Queries/CounterQueue.cs b/Ryujinx.Graphics.Vulkan/Queries/CounterQueue.cs new file mode 100644 index 000000000..939bcd006 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/Queries/CounterQueue.cs @@ -0,0 +1,232 @@ +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.Graphics.Vulkan.Queries +{ + class CounterQueue : IDisposable + { + private const int QueryPoolInitialSize = 100; + + private readonly VulkanGraphicsDevice _gd; + private readonly Device _device; + private readonly PipelineFull _pipeline; + + public CounterType Type { get; } + public bool Disposed { get; private set; } + + private Queue _events = new Queue(); + private CounterQueueEvent _current; + + private ulong _accumulatedCounter; + + private object _lock = new object(); + + private Queue _queryPool; + private AutoResetEvent _queuedEvent = new AutoResetEvent(false); + private AutoResetEvent _wakeSignal = new AutoResetEvent(false); + + private Thread _consumerThread; + + internal CounterQueue(VulkanGraphicsDevice gd, Device device, PipelineFull pipeline, CounterType type) + { + _gd = gd; + _device = device; + _pipeline = pipeline; + + Type = type; + + _queryPool = new Queue(QueryPoolInitialSize); + for (int i = 0; i < QueryPoolInitialSize; i++) + { + _queryPool.Enqueue(new BufferedQuery(_gd, _device, _pipeline, type)); + } + + _current = new CounterQueueEvent(this, type, 0); + + _consumerThread = new Thread(EventConsumer); + _consumerThread.Start(); + } + + private void EventConsumer() + { + while (!Disposed) + { + CounterQueueEvent evt = null; + lock (_lock) + { + if (_events.Count > 0) + { + evt = _events.Dequeue(); + } + } + + if (evt == null) + { + _queuedEvent.WaitOne(); // No more events to go through, wait for more. + } + else + { + evt.TryConsume(ref _accumulatedCounter, true, _wakeSignal); + } + } + } + + internal BufferedQuery GetQueryObject() + { + // Creating/disposing query objects on a context we're sharing with will cause issues. + // So instead, make a lot of query objects on the main thread and reuse them. + + lock (_lock) + { + if (_queryPool.Count > 0) + { + BufferedQuery result = _queryPool.Dequeue(); + return result; + } + else + { + return new BufferedQuery(_gd, _device, _pipeline, Type); + } + } + } + + internal void ReturnQueryObject(BufferedQuery query) + { + lock (_lock) + { + _queryPool.Enqueue(query); + } + } + + public CounterQueueEvent QueueReport(EventHandler resultHandler, ulong lastDrawIndex, bool hostReserved) + { + CounterQueueEvent result; + ulong draws = lastDrawIndex - _current.DrawIndex; + + lock (_lock) + { + // A query's result only matters if more than one draw was performed during it. + // Otherwise, dummy it out and return 0 immediately. + + if (hostReserved) + { + // This counter event is guaranteed to be available for host conditional rendering. + _current.ReserveForHostAccess(); + } + + if (draws > 0) + { + _current.Complete(true); + _events.Enqueue(_current); + + _current.OnResult += resultHandler; + } + else + { + _current.Complete(false); + _current.Dispose(); + resultHandler(_current, 0); + } + + result = _current; + + _current = new CounterQueueEvent(this, Type, lastDrawIndex); + } + + _queuedEvent.Set(); + + return result; + } + + public void QueueReset() + { + lock (_lock) + { + _current.Clear(); + } + } + + public void Flush(bool blocking) + { + if (!blocking) + { + // Just wake the consumer thread - it will update the queries. + _wakeSignal.Set(); + return; + } + + lock (_lock) + { + // Tell the queue to process all events. + while (_events.Count > 0) + { + CounterQueueEvent flush = _events.Peek(); + if (!flush.TryConsume(ref _accumulatedCounter, true)) + { + return; // If not blocking, then return when we encounter an event that is not ready yet. + } + _events.Dequeue(); + } + } + } + + public void FlushTo(CounterQueueEvent evt) + { + lock (_lock) + { + if (evt.Disposed) + { + return; + } + + // Tell the queue to process all events up to this one. + while (_events.Count > 0) + { + CounterQueueEvent flush = _events.Peek(); + + if (flush.DrawIndex > evt.DrawIndex) + { + return; + } + + _events.Dequeue(); + flush.TryConsume(ref _accumulatedCounter, true); + + if (flush == evt) + { + return; + } + } + } + } + + public void Dispose() + { + lock (_lock) + { + while (_events.Count > 0) + { + CounterQueueEvent evt = _events.Dequeue(); + + evt.Dispose(); + } + + Disposed = true; + } + + _queuedEvent.Set(); + + _consumerThread.Join(); + + _current?.Dispose(); + + foreach (BufferedQuery query in _queryPool) + { + query.Dispose(); + } + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/Queries/CounterQueueEvent.cs b/Ryujinx.Graphics.Vulkan/Queries/CounterQueueEvent.cs new file mode 100644 index 000000000..9f8612939 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/Queries/CounterQueueEvent.cs @@ -0,0 +1,163 @@ +using Ryujinx.Graphics.GAL; +using System; +using System.Threading; + +namespace Ryujinx.Graphics.Vulkan.Queries +{ + class CounterQueueEvent : ICounterEvent + { + public event EventHandler OnResult; + + public CounterType Type { get; } + public bool ClearCounter { get; private set; } + + public bool Disposed { get; private set; } + public bool Invalid { get; set; } + + public ulong DrawIndex { get; } + + private CounterQueue _queue; + private BufferedQuery _counter; + + private bool _hostAccessReserved = false; + private int _refCount = 1; // Starts with a reference from the counter queue. + + private object _lock = new object(); + private ulong _result = ulong.MaxValue; + + public CounterQueueEvent(CounterQueue queue, CounterType type, ulong drawIndex) + { + _queue = queue; + + _counter = queue.GetQueryObject(); + Type = type; + + DrawIndex = drawIndex; + + _counter.Begin(); + } + + public Auto GetBuffer() + { + return _counter.GetBuffer(); + } + + internal void Clear() + { + _counter.Reset(); + ClearCounter = true; + } + + internal void Complete(bool withResult) + { + _counter.End(withResult); + } + + internal bool TryConsume(ref ulong result, bool block, AutoResetEvent wakeSignal = null) + { + lock (_lock) + { + if (Disposed) + { + return true; + } + + if (ClearCounter) + { + result = 0; + } + + long queryResult; + + if (block) + { + queryResult = _counter.AwaitResult(wakeSignal); + } + else + { + if (!_counter.TryGetResult(out queryResult)) + { + return false; + } + } + + result += (ulong)queryResult; + + _result = result; + + OnResult?.Invoke(this, result); + + Dispose(); // Return the our resources to the pool. + + return true; + } + } + + public void Flush() + { + if (Disposed) + { + return; + } + + // Tell the queue to process all events up to this one. + _queue.FlushTo(this); + } + + public void DecrementRefCount() + { + if (Interlocked.Decrement(ref _refCount) == 0) + { + DisposeInternal(); + } + } + + public bool ReserveForHostAccess() + { + if (_hostAccessReserved) + { + return true; + } + + if (IsValueAvailable()) + { + return false; + } + + if (Interlocked.Increment(ref _refCount) == 1) + { + Interlocked.Decrement(ref _refCount); + + return false; + } + + _hostAccessReserved = true; + + return true; + } + + public void ReleaseHostAccess() + { + _hostAccessReserved = false; + + DecrementRefCount(); + } + + private void DisposeInternal() + { + _queue.ReturnQueryObject(_counter); + } + + private bool IsValueAvailable() + { + return _result != ulong.MaxValue || _counter.TryGetResult(out _); + } + + public void Dispose() + { + Disposed = true; + + DecrementRefCount(); + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/Queries/Counters.cs b/Ryujinx.Graphics.Vulkan/Queries/Counters.cs new file mode 100644 index 000000000..79b56381e --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/Queries/Counters.cs @@ -0,0 +1,58 @@ +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; + +namespace Ryujinx.Graphics.Vulkan.Queries +{ + class Counters : IDisposable + { + private readonly CounterQueue[] _counterQueues; + private readonly PipelineFull _pipeline; + + public Counters(VulkanGraphicsDevice gd, Device device, PipelineFull pipeline) + { + _pipeline = pipeline; + + int count = Enum.GetNames(typeof(CounterType)).Length; + + _counterQueues = new CounterQueue[count]; + + for (int index = 0; index < count; index++) + { + CounterType type = (CounterType)index; + _counterQueues[index] = new CounterQueue(gd, device, pipeline, type); + } + } + + public CounterQueueEvent QueueReport(CounterType type, EventHandler resultHandler, bool hostReserved) + { + return _counterQueues[(int)type].QueueReport(resultHandler, _pipeline.DrawCount, hostReserved); + } + + public void QueueReset(CounterType type) + { + _counterQueues[(int)type].QueueReset(); + } + + public void Update() + { + foreach (var queue in _counterQueues) + { + queue.Flush(false); + } + } + + public void Flush(CounterType type) + { + _counterQueues[(int)type].Flush(true); + } + + public void Dispose() + { + foreach (var queue in _counterQueues) + { + queue.Dispose(); + } + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj b/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj new file mode 100644 index 000000000..8592362ee --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj @@ -0,0 +1,38 @@ + + + + net6.0 + + + + true + + + + true + + + + + + + + + + + + + + + + + + + + + PreserveNewest + shaderc_shared.dll + + + + diff --git a/Ryujinx.Graphics.Vulkan/SamplerHolder.cs b/Ryujinx.Graphics.Vulkan/SamplerHolder.cs new file mode 100644 index 000000000..6d78eee68 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/SamplerHolder.cs @@ -0,0 +1,108 @@ +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; + +namespace Ryujinx.Graphics.Vulkan +{ + class SamplerHolder : ISampler + { + private readonly VulkanGraphicsDevice _gd; + private readonly Auto _sampler; + + public unsafe SamplerHolder(VulkanGraphicsDevice gd, Device device, GAL.SamplerCreateInfo info) + { + _gd = gd; + + gd.Samplers.Add(this); + + (Filter minFilter, SamplerMipmapMode mipFilter) = EnumConversion.Convert(info.MinFilter); + + var borderColor = GetConstrainedBorderColor(info.BorderColor, out var cantConstrain); + + var samplerCreateInfo = new Silk.NET.Vulkan.SamplerCreateInfo() + { + SType = StructureType.SamplerCreateInfo, + MagFilter = info.MagFilter.Convert(), + MinFilter = minFilter, + MipmapMode = mipFilter, + AddressModeU = info.AddressU.Convert(), + AddressModeV = info.AddressV.Convert(), + AddressModeW = info.AddressP.Convert(), + MipLodBias = info.MipLodBias, + AnisotropyEnable = info.MaxAnisotropy != 1f, + MaxAnisotropy = info.MaxAnisotropy, + CompareEnable = info.CompareMode == CompareMode.CompareRToTexture, + CompareOp = info.CompareOp.Convert(), + MinLod = info.MinLod, + MaxLod = info.MaxLod, + BorderColor = borderColor, + UnnormalizedCoordinates = false // TODO: Use unnormalized coordinates. + }; + + SamplerCustomBorderColorCreateInfoEXT customBorderColor; + + if (cantConstrain && gd.SupportsCustomBorderColor) + { + var color = new ClearColorValue( + info.BorderColor.Red, + info.BorderColor.Green, + info.BorderColor.Blue, + info.BorderColor.Alpha); + + customBorderColor = new SamplerCustomBorderColorCreateInfoEXT() + { + SType = StructureType.SamplerCustomBorderColorCreateInfoExt, + CustomBorderColor = color + }; + + samplerCreateInfo.PNext = &customBorderColor; + } + + gd.Api.CreateSampler(device, samplerCreateInfo, null, out var sampler).ThrowOnError(); + + _sampler = new Auto(new DisposableSampler(gd.Api, device, sampler)); + } + + private static BorderColor GetConstrainedBorderColor(ColorF arbitraryBorderColor, out bool cantConstrain) + { + float r = arbitraryBorderColor.Red; + float g = arbitraryBorderColor.Green; + float b = arbitraryBorderColor.Blue; + float a = arbitraryBorderColor.Alpha; + + if (r == 0f && g == 0f && b == 0f) + { + if (a == 1f) + { + cantConstrain = false; + return BorderColor.FloatOpaqueBlack; + } + else if (a == 0f) + { + cantConstrain = false; + return BorderColor.FloatTransparentBlack; + } + } + else if (r == 1f && g == 1f && b == 1f && a == 1f) + { + cantConstrain = false; + return BorderColor.FloatOpaqueWhite; + } + + cantConstrain = true; + return BorderColor.FloatOpaqueBlack; + } + + public Auto GetSampler() + { + return _sampler; + } + + public void Dispose() + { + if (_gd.Samplers.Remove(this)) + { + _sampler.Dispose(); + } + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/SemaphoreHolder.cs b/Ryujinx.Graphics.Vulkan/SemaphoreHolder.cs new file mode 100644 index 000000000..aa1b0eafe --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/SemaphoreHolder.cs @@ -0,0 +1,60 @@ +using Silk.NET.Vulkan; +using System; +using System.Threading; +using VkSemaphore = Silk.NET.Vulkan.Semaphore; + +namespace Ryujinx.Graphics.Vulkan +{ + class SemaphoreHolder : IDisposable + { + private readonly Vk _api; + private readonly Device _device; + private VkSemaphore _semaphore; + private int _referenceCount; + public bool _disposed; + + public unsafe SemaphoreHolder(Vk api, Device device) + { + _api = api; + _device = device; + + var semaphoreCreateInfo = new SemaphoreCreateInfo() + { + SType = StructureType.SemaphoreCreateInfo + }; + + api.CreateSemaphore(device, in semaphoreCreateInfo, null, out _semaphore).ThrowOnError(); + + _referenceCount = 1; + } + + public VkSemaphore GetUnsafe() + { + return _semaphore; + } + + public VkSemaphore Get() + { + Interlocked.Increment(ref _referenceCount); + return _semaphore; + } + + public unsafe void Put() + { + if (Interlocked.Decrement(ref _referenceCount) == 0) + { + _api.DestroySemaphore(_device, _semaphore, null); + _semaphore = default; + } + } + + public void Dispose() + { + if (!_disposed) + { + Put(); + _disposed = true; + } + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/Shader.cs b/Ryujinx.Graphics.Vulkan/Shader.cs new file mode 100644 index 000000000..93264b987 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/Shader.cs @@ -0,0 +1,157 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; +using shaderc; +using Silk.NET.Vulkan; +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Vulkan +{ + class Shader : IShader + { + private readonly Vk _api; + private readonly Device _device; + private readonly ShaderModule _module; + private readonly ShaderStageFlags _stage; + + private IntPtr _entryPointName; + + public ShaderStageFlags StageFlags => _stage; + + public ShaderBindings Bindings { get; } + + public bool Valid { get; } + + public unsafe Shader(Vk api, Device device, ShaderStage stage, ShaderBindings bindings, string glsl) + { + _api = api; + _device = device; + Bindings = bindings; + + glsl = glsl.Replace("gl_VertexID", "(gl_VertexIndex - gl_BaseVertex)"); + glsl = glsl.Replace("gl_InstanceID", "(gl_InstanceIndex - gl_BaseInstance)"); + + // System.Console.WriteLine(glsl); + + Options options = new Options(false) + { + SourceLanguage = SourceLanguage.Glsl, + TargetSpirVVersion = new SpirVVersion(1, 5) + }; + options.SetTargetEnvironment(TargetEnvironment.Vulkan, EnvironmentVersion.Vulkan_1_2); + Compiler compiler = new Compiler(options); + var scr = compiler.Compile(glsl, "Ryu", GetShaderCShaderStage(stage)); + + if (scr.Status != Status.Success) + { + Logger.Error?.Print(LogClass.Gpu, $"Shader compilation error: {scr.Status} {scr.ErrorMessage}"); + return; + } + + Valid = true; + + var spirvBytes = new Span((void*)scr.CodePointer, (int)scr.CodeLength); + + uint[] code = new uint[(scr.CodeLength + 3) / 4]; + + spirvBytes.CopyTo(MemoryMarshal.Cast(new Span(code)).Slice(0, (int)scr.CodeLength)); + + fixed (uint* pCode = code) + { + var shaderModuleCreateInfo = new ShaderModuleCreateInfo() + { + SType = StructureType.ShaderModuleCreateInfo, + CodeSize = scr.CodeLength, + PCode = pCode + }; + + api.CreateShaderModule(device, shaderModuleCreateInfo, null, out _module).ThrowOnError(); + } + + _stage = stage.Convert(); + _entryPointName = Marshal.StringToHGlobalAnsi("main"); + } + + public unsafe Shader(Vk api, Device device, ShaderStage stage, ShaderBindings bindings, byte[] spirv) + { + _api = api; + _device = device; + Bindings = bindings; + + Valid = true; + + fixed (byte* pCode = spirv) + { + var shaderModuleCreateInfo = new ShaderModuleCreateInfo() + { + SType = StructureType.ShaderModuleCreateInfo, + CodeSize = (uint)spirv.Length, + PCode = (uint*)pCode + }; + + api.CreateShaderModule(device, shaderModuleCreateInfo, null, out _module).ThrowOnError(); + } + + _stage = stage.Convert(); + _entryPointName = Marshal.StringToHGlobalAnsi("main"); + } + + private static uint[] LoadShaderData(string filePath, out int codeSize) + { + var fileBytes = File.ReadAllBytes(filePath); + var shaderData = new uint[(int)Math.Ceiling(fileBytes.Length / 4f)]; + + System.Buffer.BlockCopy(fileBytes, 0, shaderData, 0, fileBytes.Length); + + codeSize = fileBytes.Length; + + return shaderData; + } + + private static ShaderKind GetShaderCShaderStage(ShaderStage stage) + { + switch (stage) + { + case ShaderStage.Vertex: + return ShaderKind.GlslVertexShader; + case ShaderStage.Geometry: + return ShaderKind.GlslGeometryShader; + case ShaderStage.TessellationControl: + return ShaderKind.GlslTessControlShader; + case ShaderStage.TessellationEvaluation: + return ShaderKind.GlslTessEvaluationShader; + case ShaderStage.Fragment: + return ShaderKind.GlslFragmentShader; + case ShaderStage.Compute: + return ShaderKind.GlslComputeShader; + }; + + Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(ShaderStage)} enum value: {stage}."); + + return ShaderKind.GlslVertexShader; + } + + public unsafe PipelineShaderStageCreateInfo GetInfo() + { + return new PipelineShaderStageCreateInfo() + { + SType = StructureType.PipelineShaderStageCreateInfo, + Stage = _stage, + Module = _module, + PName = (byte*)_entryPointName + }; + } + + public unsafe void Dispose() + { + if (_entryPointName != IntPtr.Zero) + { + _api.DestroyShaderModule(_device, _module, null); + Marshal.FreeHGlobal(_entryPointName); + _entryPointName = IntPtr.Zero; + } + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/ShaderCollection.cs b/Ryujinx.Graphics.Vulkan/ShaderCollection.cs new file mode 100644 index 000000000..7af1c2c26 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/ShaderCollection.cs @@ -0,0 +1,185 @@ +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.Graphics.Vulkan +{ + class ShaderCollection : IProgram + { + private readonly PipelineShaderStageCreateInfo[] _infos; + private readonly IShader[] _shaders; + + private readonly PipelineLayoutCacheEntry _plce; + + public PipelineLayout PipelineLayout => _plce.PipelineLayout; + + public uint Stages { get; } + + public int[][][] Bindings { get; } + + public ProgramLinkStatus LinkStatus { get; } + + private HashTableSlim> _graphicsPipelineCache; + private Auto _computePipeline; + + private VulkanGraphicsDevice _gd; + private Device _device; + + public ShaderCollection(VulkanGraphicsDevice gd, Device device, IShader[] shaders) + { + _gd = gd; + _device = device; + _shaders = shaders; + + gd.Shaders.Add(this); + + var internalShaders = new Shader[shaders.Length]; + + _infos = new PipelineShaderStageCreateInfo[shaders.Length]; + + LinkStatus = ProgramLinkStatus.Success; + + uint stages = 0; + + for (int i = 0; i < shaders.Length; i++) + { + var shader = (Shader)shaders[i]; + if (!shader.Valid) + { + LinkStatus = ProgramLinkStatus.Failure; + } + + stages |= 1u << shader.StageFlags switch + { + ShaderStageFlags.ShaderStageFragmentBit => 1, + ShaderStageFlags.ShaderStageGeometryBit => 2, + ShaderStageFlags.ShaderStageTessellationControlBit => 3, + ShaderStageFlags.ShaderStageTessellationEvaluationBit => 4, + _ => 0 + }; + + internalShaders[i] = shader; + + _infos[i] = internalShaders[i].GetInfo(); + } + + _plce = gd.PipelineLayoutCache.GetOrCreate(gd, device, stages); + + Stages = stages; + + int[][] GrabAll(Func> selector) + { + bool hasAny = false; + int[][] bindings = new int[internalShaders.Length][]; + + for (int i = 0; i < internalShaders.Length; i++) + { + var collection = selector(internalShaders[i].Bindings); + hasAny |= collection.Count != 0; + bindings[i] = collection.ToArray(); + } + + return hasAny ? bindings : Array.Empty(); + } + + Bindings = new[] + { + GrabAll(x => x.UniformBufferBindings), + GrabAll(x => x.StorageBufferBindings), + GrabAll(x => x.TextureBindings), + GrabAll(x => x.ImageBindings), + GrabAll(x => x.BufferTextureBindings), + GrabAll(x => x.BufferImageBindings) + }; + } + + public PipelineShaderStageCreateInfo[] GetInfos() + { + return _infos; + } + + public ProgramLinkStatus CheckProgramLink(bool blocking) + { + return LinkStatus; + } + + public byte[] GetBinary() + { + throw new System.NotImplementedException(); + } + + public void AddComputePipeline(Auto pipeline) + { + _computePipeline = pipeline; + } + + public void RemoveComputePipeline() + { + _computePipeline = null; + } + + public void AddGraphicsPipeline(ref PipelineUid key, Auto pipeline) + { + (_graphicsPipelineCache ??= new()).Add(ref key, pipeline); + } + + public bool TryGetComputePipeline(out Auto pipeline) + { + pipeline = _computePipeline; + return pipeline != null; + } + + public bool TryGetGraphicsPipeline(ref PipelineUid key, out Auto pipeline) + { + if (_graphicsPipelineCache == null) + { + pipeline = default; + return false; + } + + return _graphicsPipelineCache.TryGetValue(ref key, out pipeline); + } + + public Auto GetNewDescriptorSetCollection( + VulkanGraphicsDevice gd, + int commandBufferIndex, + int setIndex, + out bool isNew) + { + return _plce.GetNewDescriptorSetCollection(gd, commandBufferIndex, setIndex, out isNew); + } + + protected virtual unsafe void Dispose(bool disposing) + { + if (disposing) + { + if (!_gd.Shaders.Remove(this)) + { + return; + } + + for (int i = 0; i < _shaders.Length; i++) + { + _shaders[i].Dispose(); + } + + if (_graphicsPipelineCache != null) + { + foreach (Auto pipeline in _graphicsPipelineCache.Values) + { + pipeline.Dispose(); + } + } + + _computePipeline?.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/StagingBuffer.cs b/Ryujinx.Graphics.Vulkan/StagingBuffer.cs new file mode 100644 index 000000000..0942b381a --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/StagingBuffer.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Ryujinx.Graphics.Vulkan +{ + class StagingBuffer : IDisposable + { + private const int BufferSize = 16 * 1024 * 1024; + + private int _freeOffset; + private int _freeSize; + + private readonly VulkanGraphicsDevice _gd; + private readonly BufferHolder _buffer; + + private struct PendingCopy + { + public FenceHolder Fence { get; } + public int Size { get; } + + public PendingCopy(FenceHolder fence, int size) + { + Fence = fence; + Size = size; + fence.Get(); + } + } + + private readonly Queue _pendingCopies; + + public StagingBuffer(VulkanGraphicsDevice gd, BufferManager bufferManager) + { + _gd = gd; + _buffer = bufferManager.Create(gd, BufferSize); + _pendingCopies = new Queue(); + _freeSize = BufferSize; + } + + public unsafe bool TryPushData(CommandBufferScoped cbs, Action endRenderPass, BufferHolder dst, int dstOffset, ReadOnlySpan data) + { + if (data.Length > BufferSize) + { + return false; + } + + if (_freeSize < data.Length) + { + FreeCompleted(); + + if (_freeSize < data.Length) + { + return false; + } + } + + endRenderPass(); + + var srcBuffer = _buffer.GetBuffer(); + var dstBuffer = dst.GetBuffer(); + + int offset = _freeOffset; + int capacity = BufferSize - offset; + if (capacity < data.Length) + { + _buffer.SetDataUnchecked(offset, data.Slice(0, capacity)); + _buffer.SetDataUnchecked(0, data.Slice(capacity)); + + BufferHolder.Copy(_gd, cbs, srcBuffer, dstBuffer, offset, dstOffset, capacity); + BufferHolder.Copy(_gd, cbs, srcBuffer, dstBuffer, 0, dstOffset + capacity, data.Length - capacity); + } + else + { + _buffer.SetDataUnchecked(offset, data); + + BufferHolder.Copy(_gd, cbs, srcBuffer, dstBuffer, offset, dstOffset, data.Length); + } + + _freeOffset = (offset + data.Length) & (BufferSize - 1); + _freeSize -= data.Length; + Debug.Assert(_freeSize >= 0); + + _pendingCopies.Enqueue(new PendingCopy(cbs.GetFence(), data.Length)); + + return true; + } + + private void FreeCompleted() + { + while (_pendingCopies.TryPeek(out var pc) && pc.Fence.IsSignaled()) + { + var dequeued = _pendingCopies.Dequeue(); + Debug.Assert(dequeued.Fence == pc.Fence); + _freeSize += pc.Size; + pc.Fence.Put(); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _buffer.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/SyncManager.cs b/Ryujinx.Graphics.Vulkan/SyncManager.cs new file mode 100644 index 000000000..a0b2e7df7 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/SyncManager.cs @@ -0,0 +1,122 @@ +using Ryujinx.Common.Logging; +using Silk.NET.Vulkan; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.Graphics.Vulkan +{ + class SyncManager + { + private class SyncHandle + { + public ulong ID; + public MultiFenceHolder Waitable; + } + + private ulong _firstHandle = 0; + + private readonly VulkanGraphicsDevice _gd; + private readonly Device _device; + private List _handles; + + public SyncManager(VulkanGraphicsDevice gd, Device device) + { + _gd = gd; + _device = device; + _handles = new List(); + } + + public void Create(ulong id) + { + MultiFenceHolder waitable = new MultiFenceHolder(); + + _gd.FlushAllCommands(); + _gd.CommandBufferPool.AddWaitable(waitable); + + SyncHandle handle = new SyncHandle + { + ID = id, + Waitable = waitable + }; + + lock (_handles) + { + _handles.Add(handle); + } + } + + public void Wait(ulong id) + { + SyncHandle result = null; + + lock (_handles) + { + if ((long)(_firstHandle - id) > 0) + { + return; // The handle has already been signalled or deleted. + } + + foreach (SyncHandle handle in _handles) + { + if (handle.ID == id) + { + result = handle; + break; + } + } + } + + if (result != null) + { + lock (result) + { + if (result.Waitable == null) + { + return; + } + + bool signaled = result.Waitable.WaitForFences(_gd.Api, _device, 1000000000); + if (!signaled) + { + Logger.Error?.PrintMsg(LogClass.Gpu, $"GL Sync Object {result.ID} failed to signal within 1000ms. Continuing..."); + } + } + } + } + + public void Cleanup() + { + // Iterate through handles and remove any that have already been signalled. + + while (true) + { + SyncHandle first = null; + lock (_handles) + { + first = _handles.FirstOrDefault(); + } + + if (first == null) break; + + bool signaled = first.Waitable.WaitForFences(_gd.Api, _device, 0); + if (signaled) + { + // Delete the sync object. + lock (_handles) + { + lock (first) + { + _firstHandle = first.ID + 1; + _handles.RemoveAt(0); + first.Waitable = null; + } + } + } else + { + // This sync handle and any following have not been reached yet. + break; + } + } + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/TextureBuffer.cs b/Ryujinx.Graphics.Vulkan/TextureBuffer.cs new file mode 100644 index 000000000..cc1cc3f1f --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/TextureBuffer.cs @@ -0,0 +1,115 @@ +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using VkFormat = Silk.NET.Vulkan.Format; + +namespace Ryujinx.Graphics.Vulkan +{ + class TextureBuffer : ITexture + { + private readonly VulkanGraphicsDevice _gd; + + private BufferHandle _bufferHandle; + private int _offset; + private int _size; + private Auto _bufferView; + + public int Width { get; } + public int Height { get; } + + public VkFormat VkFormat { get; } + + public float ScaleFactor { get; } + + public TextureBuffer(VulkanGraphicsDevice gd, TextureCreateInfo info, float scale) + { + _gd = gd; + Width = info.Width; + Height = info.Height; + VkFormat = FormatTable.GetFormat(info.Format); + ScaleFactor = scale; + + gd.Textures.Add(this); + } + + public void CopyTo(ITexture destination, int firstLayer, int firstLevel) + { + throw new NotSupportedException(); + } + + public void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel) + { + throw new NotSupportedException(); + } + + public void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter) + { + throw new NotSupportedException(); + } + + public ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel) + { + throw new NotSupportedException(); + } + + public ReadOnlySpan GetData() + { + return _gd.GetBufferData(_bufferHandle, _offset, _size); + } + + public ReadOnlySpan GetData(int layer, int level) + { + return GetData(); + } + + public void Release() + { + if (_gd.Textures.Remove(this)) + { + ReleaseImpl(); + } + } + + private void ReleaseImpl() + { + _bufferView?.Dispose(); + _bufferView = null; + } + + public void SetData(ReadOnlySpan data) + { + _gd.SetBufferData(_bufferHandle, _offset, data); + } + + public void SetData(ReadOnlySpan data, int layer, int level) + { + throw new NotSupportedException(); + } + + public void SetStorage(BufferRange buffer) + { + if (_bufferHandle == buffer.Handle && + _offset == buffer.Offset && + _size == buffer.Size) + { + return; + } + + _bufferHandle = buffer.Handle; + _offset = buffer.Offset; + _size = buffer.Size; + + ReleaseImpl();; + } + + public BufferView GetBufferView(CommandBufferScoped cbs) + { + if (_bufferView == null) + { + _bufferView = _gd.BufferManager.CreateView(_bufferHandle, VkFormat, _offset, _size); + } + + return _bufferView?.Get(cbs, _offset, _size).Value ?? default; + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/TextureCopy.cs b/Ryujinx.Graphics.Vulkan/TextureCopy.cs new file mode 100644 index 000000000..fb7d82c33 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/TextureCopy.cs @@ -0,0 +1,275 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Numerics; + +namespace Ryujinx.Graphics.Vulkan +{ + static class TextureCopy + { + public static void Blit( + Vk api, + CommandBuffer commandBuffer, + Image srcImage, + Image dstImage, + TextureCreateInfo srcInfo, + TextureCreateInfo dstInfo, + Extents2D srcRegion, + Extents2D dstRegion, + int srcLayer, + int dstLayer, + int srcLevel, + int dstLevel, + bool linearFilter, + ImageAspectFlags srcAspectFlags = 0, + ImageAspectFlags dstAspectFlags = 0) + { + static (Offset3D, Offset3D) ExtentsToOffset3D(Extents2D extents, int width, int height) + { + static int Clamp(int value, int max) + { + return Math.Clamp(value, 0, max); + } + + var xy1 = new Offset3D(Clamp(extents.X1, width), Clamp(extents.Y1, height), 0); + var xy2 = new Offset3D(Clamp(extents.X2, width), Clamp(extents.Y2, height), 1); + + return (xy1, xy2); + } + + if (srcAspectFlags == 0) + { + srcAspectFlags = srcInfo.Format.ConvertAspectFlags(); + } + + if (dstAspectFlags == 0) + { + dstAspectFlags = dstInfo.Format.ConvertAspectFlags(); + } + + var srcSl = new ImageSubresourceLayers(srcAspectFlags, (uint)srcLevel, (uint)srcLayer, 1); + var dstSl = new ImageSubresourceLayers(dstAspectFlags, (uint)dstLevel, (uint)dstLayer, 1); + + var srcOffsets = new ImageBlit.SrcOffsetsBuffer(); + var dstOffsets = new ImageBlit.DstOffsetsBuffer(); + + (srcOffsets.Element0, srcOffsets.Element1) = ExtentsToOffset3D(srcRegion, srcInfo.Width, srcInfo.Height); + (dstOffsets.Element0, dstOffsets.Element1) = ExtentsToOffset3D(dstRegion, dstInfo.Width, dstInfo.Height); + + var region = new ImageBlit() + { + SrcSubresource = srcSl, + SrcOffsets = srcOffsets, + DstSubresource = dstSl, + DstOffsets = dstOffsets + }; + + var filter = linearFilter && !dstInfo.Format.IsDepthOrStencil() ? Filter.Linear : Filter.Nearest; + + api.CmdBlitImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, region, filter); + } + + public static void Copy( + Vk api, + CommandBuffer commandBuffer, + Image srcImage, + Image dstImage, + TextureCreateInfo srcInfo, + TextureCreateInfo dstInfo, + int srcViewLayer, + int dstViewLayer, + int srcViewLevel, + int dstViewLevel, + int srcLayer, + int dstLayer, + int srcLevel, + int dstLevel) + { + int srcDepth = srcInfo.GetDepthOrLayers(); + int srcLevels = srcInfo.Levels; + + int dstDepth = dstInfo.GetDepthOrLayers(); + int dstLevels = dstInfo.Levels; + + if (dstInfo.Target == Target.Texture3D) + { + dstDepth = Math.Max(1, dstDepth >> dstLevel); + } + + int depth = Math.Min(srcDepth, dstDepth); + int levels = Math.Min(srcLevels, dstLevels); + + Copy( + api, + commandBuffer, + srcImage, + dstImage, + srcInfo, + dstInfo, + srcViewLayer, + dstViewLayer, + srcViewLevel, + dstViewLevel, + srcLayer, + dstLayer, + srcLevel, + dstLevel, + depth, + levels); + } + + private static int ClampLevels(TextureCreateInfo info, int levels) + { + int width = info.Width; + int height = info.Height; + int depth = info.Target == Target.Texture3D ? info.Depth : 1; + + int maxLevels = 1 + BitOperations.Log2((uint)Math.Max(Math.Max(width, height), depth)); + + if (levels > maxLevels) + { + levels = maxLevels; + } + + return levels; + } + + public static void Copy( + Vk api, + CommandBuffer commandBuffer, + Image srcImage, + Image dstImage, + TextureCreateInfo srcInfo, + TextureCreateInfo dstInfo, + int srcViewLayer, + int dstViewLayer, + int srcViewLevel, + int dstViewLevel, + int srcDepthOrLayer, + int dstDepthOrLayer, + int srcLevel, + int dstLevel, + int depthOrLayers, + int levels) + { + int srcZ; + int srcLayer; + int srcDepth; + int srcLayers; + + if (srcInfo.Target == Target.Texture3D) + { + srcZ = srcDepthOrLayer; + srcLayer = 0; + srcDepth = depthOrLayers; + srcLayers = 1; + } + else + { + srcZ = 0; + srcLayer = srcDepthOrLayer; + srcDepth = 1; + srcLayers = depthOrLayers; + } + + int dstZ; + int dstLayer; + int dstDepth; + int dstLayers; + + if (dstInfo.Target == Target.Texture3D) + { + dstZ = dstDepthOrLayer; + dstLayer = 0; + dstDepth = depthOrLayers; + dstLayers = 1; + } + else + { + dstZ = 0; + dstLayer = dstDepthOrLayer; + dstDepth = 1; + dstLayers = depthOrLayers; + } + + int srcWidth = srcInfo.Width; + int srcHeight = srcInfo.Height; + + int dstWidth = dstInfo.Width; + int dstHeight = dstInfo.Height; + + srcWidth = Math.Max(1, srcWidth >> srcLevel); + srcHeight = Math.Max(1, srcHeight >> srcLevel); + + dstWidth = Math.Max(1, dstWidth >> dstLevel); + dstHeight = Math.Max(1, dstHeight >> dstLevel); + + int blockWidth = 1; + int blockHeight = 1; + bool sizeInBlocks = false; + + // When copying from a compressed to a non-compressed format, + // the non-compressed texture will have the size of the texture + // in blocks (not in texels), so we must adjust that size to + // match the size in texels of the compressed texture. + if (!srcInfo.IsCompressed && dstInfo.IsCompressed) + { + srcWidth *= dstInfo.BlockWidth; + srcHeight *= dstInfo.BlockHeight; + blockWidth = dstInfo.BlockWidth; + blockHeight = dstInfo.BlockHeight; + + sizeInBlocks = true; + } + else if (srcInfo.IsCompressed && !dstInfo.IsCompressed) + { + dstWidth *= srcInfo.BlockWidth; + dstHeight *= srcInfo.BlockHeight; + blockWidth = srcInfo.BlockWidth; + blockHeight = srcInfo.BlockHeight; + } + + int width = Math.Min(srcWidth, dstWidth); + int height = Math.Min(srcHeight, dstHeight); + + for (int level = 0; level < levels; level++) + { + // Stop copy if we are already out of the levels range. + if (level >= srcInfo.Levels || dstLevel + level >= dstInfo.Levels) + { + break; + } + + var srcSl = new ImageSubresourceLayers( + srcInfo.Format.ConvertAspectFlags(), + (uint)(srcViewLevel + srcLevel + level), + (uint)(srcViewLayer + srcLayer), + (uint)srcLayers); + + var dstSl = new ImageSubresourceLayers( + dstInfo.Format.ConvertAspectFlags(), + (uint)(dstViewLevel + dstLevel + level), + (uint)(dstViewLayer + dstLayer), + (uint)dstLayers); + + int copyWidth = sizeInBlocks ? BitUtils.DivRoundUp(width, blockWidth) : width; + int copyHeight = sizeInBlocks ? BitUtils.DivRoundUp(height, blockHeight) : height; + + var extent = new Extent3D((uint)copyWidth, (uint)copyHeight, (uint)srcDepth); + + var region = new ImageCopy(srcSl, new Offset3D(0, 0, srcZ), dstSl, new Offset3D(0, 0, dstZ), extent); + + api.CmdCopyImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, region); + + width = Math.Max(1, width >> 1); + height = Math.Max(1, height >> 1); + + if (srcInfo.Target == Target.Texture3D) + { + srcDepth = Math.Max(1, srcDepth >> 1); + } + } + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/TextureStorage.cs b/Ryujinx.Graphics.Vulkan/TextureStorage.cs new file mode 100644 index 000000000..7d1e749b2 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/TextureStorage.cs @@ -0,0 +1,282 @@ +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; +using System.Numerics; +using VkFormat = Silk.NET.Vulkan.Format; + +namespace Ryujinx.Graphics.Vulkan +{ + class TextureStorage : IDisposable + { + private const ImageUsageFlags DefaultUsageFlags = + ImageUsageFlags.ImageUsageSampledBit | + ImageUsageFlags.ImageUsageTransferSrcBit | + ImageUsageFlags.ImageUsageTransferDstBit; + + public const AccessFlags DefaultAccessMask = + AccessFlags.AccessShaderReadBit | + AccessFlags.AccessShaderWriteBit | + AccessFlags.AccessColorAttachmentReadBit | + AccessFlags.AccessColorAttachmentWriteBit | + AccessFlags.AccessDepthStencilAttachmentReadBit | + AccessFlags.AccessDepthStencilAttachmentWriteBit | + AccessFlags.AccessTransferReadBit | + AccessFlags.AccessTransferWriteBit; + + private readonly VulkanGraphicsDevice _gd; + + private readonly Device _device; + + private readonly TextureCreateInfo _info; + + private readonly Image _image; + private readonly Auto _imageAuto; + private readonly Auto _allocationAuto; + + private Dictionary _aliasedStorages; + + public VkFormat VkFormat { get; } + public float ScaleFactor { get; } + + public unsafe TextureStorage( + VulkanGraphicsDevice gd, + PhysicalDevice physicalDevice, + Device device, + TextureCreateInfo info, + float scaleFactor, + Auto foreignAllocation = null) + { + _gd = gd; + _device = device; + _info = info; + ScaleFactor = scaleFactor; + + var format = _gd.FormatCapabilities.ConvertToVkFormat(info.Format); + var levels = (uint)info.Levels; + var layers = (uint)info.GetLayers(); + var depth = (uint)(info.Target == Target.Texture3D ? info.Depth : 1); + + VkFormat = format; + + var type = info.Target.Convert(); + + var extent = new Extent3D((uint)info.Width, (uint)info.Height, depth); + + var sampleCountFlags = ConvertToSampleCountFlags((uint)info.Samples); + + var usage = DefaultUsageFlags; + + if (info.Format.IsDepthOrStencil()) + { + usage |= ImageUsageFlags.ImageUsageDepthStencilAttachmentBit; + } + else if (info.Format.IsRtColorCompatible()) + { + usage |= ImageUsageFlags.ImageUsageColorAttachmentBit; + } + + if (info.Format.IsImageCompatible()) + { + usage |= ImageUsageFlags.ImageUsageStorageBit; + } + + var flags = ImageCreateFlags.ImageCreateMutableFormatBit; + + if (info.BlockWidth != 1 || info.BlockHeight != 1) + { + flags |= ImageCreateFlags.ImageCreateBlockTexelViewCompatibleBit; + } + + bool cubeCompatible = info.Width == info.Height && layers >= 6; + + if (type == ImageType.ImageType2D && cubeCompatible) + { + flags |= ImageCreateFlags.ImageCreateCubeCompatibleBit; + } + + if (type == ImageType.ImageType3D) + { + flags |= ImageCreateFlags.ImageCreate2DArrayCompatibleBit; + } + + // System.Console.WriteLine("create image " + type + " " + format + " " + levels + " " + layers + " " + usage + " " + flags); + + var imageCreateInfo = new ImageCreateInfo() + { + SType = StructureType.ImageCreateInfo, + ImageType = type, + Format = format, + Extent = extent, + MipLevels = levels, + ArrayLayers = layers, + Samples = sampleCountFlags, + Tiling = ImageTiling.Optimal, + Usage = usage, + SharingMode = SharingMode.Exclusive, + InitialLayout = ImageLayout.Undefined, + Flags = flags + }; + + gd.Api.CreateImage(device, imageCreateInfo, null, out _image).ThrowOnError(); + + if (foreignAllocation == null) + { + gd.Api.GetImageMemoryRequirements(device, _image, out var requirements); + var allocation = gd.MemoryAllocator.AllocateDeviceMemory(physicalDevice, requirements); + + if (allocation.Memory.Handle == 0UL) + { + gd.Api.DestroyImage(device, _image, null); + throw new Exception("Image initialization failed."); + } + + gd.Api.BindImageMemory(device, _image, allocation.Memory, allocation.Offset).ThrowOnError(); + + _allocationAuto = new Auto(allocation); + _imageAuto = new Auto(new DisposableImage(_gd.Api, device, _image), null, _allocationAuto); + + InitialTransition(ImageLayout.Undefined, ImageLayout.General); + } + else + { + var allocation = foreignAllocation.GetUnsafe(); + + gd.Api.BindImageMemory(device, _image, allocation.Memory, allocation.Offset).ThrowOnError(); + + _imageAuto = new Auto(new DisposableImage(_gd.Api, device, _image)); + + InitialTransition(ImageLayout.Preinitialized, ImageLayout.General); + } + } + + public TextureStorage CreateAliasedColorForDepthStorageUnsafe(GAL.Format format) + { + var colorFormat = format switch + { + GAL.Format.S8Uint => GAL.Format.R8Unorm, + GAL.Format.D16Unorm => GAL.Format.R16Unorm, + GAL.Format.S8UintD24Unorm => GAL.Format.R8G8B8A8Unorm, + GAL.Format.D32Float => GAL.Format.R32Float, + GAL.Format.D24UnormS8Uint => GAL.Format.R8G8B8A8Unorm, + GAL.Format.D32FloatS8Uint => GAL.Format.R32G32Float, + _ => throw new ArgumentException($"\"{format}\" is not a supported depth or stencil format.") + }; + + return CreateAliasedStorageUnsafe(colorFormat); + } + + public TextureStorage CreateAliasedStorageUnsafe(GAL.Format format) + { + if (_aliasedStorages == null || !_aliasedStorages.TryGetValue(format, out var storage)) + { + _aliasedStorages ??= new Dictionary(); + + var info = NewCreateInfoWith(_info, format, _info.BytesPerPixel); + + storage = new TextureStorage(_gd, default, _device, info, ScaleFactor, _allocationAuto); + + _aliasedStorages.Add(format, storage); + } + + return storage; + } + + public static TextureCreateInfo NewCreateInfoWith(TextureCreateInfo info, GAL.Format format, int bytesPerPixel) + { + return new TextureCreateInfo( + info.Width, + info.Height, + info.Depth, + info.Levels, + info.Samples, + info.BlockWidth, + info.BlockHeight, + bytesPerPixel, + format, + info.DepthStencilMode, + info.Target, + info.SwizzleR, + info.SwizzleG, + info.SwizzleB, + info.SwizzleA); + } + + public Auto GetImage() + { + return _imageAuto; + } + + public Image GetImageForViewCreation() + { + return _image; + } + + private unsafe void InitialTransition(ImageLayout srcLayout, ImageLayout dstLayout) + { + using var cbs = _gd.CommandBufferPool.Rent(); + + var aspectFlags = _info.Format.ConvertAspectFlags(); + + var subresourceRange = new ImageSubresourceRange(aspectFlags, 0, (uint)_info.Levels, 0, (uint)_info.GetLayers()); + + var barrier = new ImageMemoryBarrier() + { + SType = StructureType.ImageMemoryBarrier, + SrcAccessMask = 0, + DstAccessMask = DefaultAccessMask, + OldLayout = srcLayout, + NewLayout = dstLayout, + SrcQueueFamilyIndex = Vk.QueueFamilyIgnored, + DstQueueFamilyIndex = Vk.QueueFamilyIgnored, + Image = _imageAuto.Get(cbs).Value, + SubresourceRange = subresourceRange + }; + + _gd.Api.CmdPipelineBarrier( + cbs.CommandBuffer, + PipelineStageFlags.PipelineStageTopOfPipeBit, + PipelineStageFlags.PipelineStageAllCommandsBit, + 0, + 0, + null, + 0, + null, + 1, + barrier); + } + + private static SampleCountFlags ConvertToSampleCountFlags(uint samples) + { + return SampleCountFlags.SampleCount1Bit; + if (samples == 0 || samples > (uint)SampleCountFlags.SampleCount64Bit) + { + return SampleCountFlags.SampleCount1Bit; + } + + // Round up to the nearest power of two. + return (SampleCountFlags)(1u << (31 - BitOperations.LeadingZeroCount(samples))); + } + + public TextureView CreateView(TextureCreateInfo info, int firstLayer, int firstLevel) + { + return new TextureView(_gd, _device, info, this, firstLayer, firstLevel); + } + + public void Dispose() + { + if (_aliasedStorages != null) + { + foreach (var storage in _aliasedStorages.Values) + { + storage.Dispose(); + } + + _aliasedStorages.Clear(); + } + + _imageAuto.Dispose(); + _allocationAuto?.Dispose(); + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/TextureView.cs b/Ryujinx.Graphics.Vulkan/TextureView.cs new file mode 100644 index 000000000..c17bc8e11 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/TextureView.cs @@ -0,0 +1,747 @@ +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Threading; +using VkBuffer = Silk.NET.Vulkan.Buffer; +using VkFormat = Silk.NET.Vulkan.Format; + +namespace Ryujinx.Graphics.Vulkan +{ + class TextureView : ITexture + { + private readonly VulkanGraphicsDevice _gd; + + private readonly Device _device; + + private readonly Auto _imageView; + private readonly Auto _imageViewIdentity; + private readonly Auto _imageView2dArray; + private BufferHolder _flushStorage; + + public TextureCreateInfo Info { get; } + + public TextureStorage Storage { get; } + + public int Width => Info.Width; + public int Height => Info.Height; + public int Layers => Info.GetDepthOrLayers(); + public int FirstLayer { get; } + public int FirstLevel { get; } + public float ScaleFactor => Storage.ScaleFactor; + public VkFormat VkFormat { get; } + public bool Valid { get; private set; } + + public TextureView( + VulkanGraphicsDevice gd, + Device device, + TextureCreateInfo info, + TextureStorage storage, + int firstLayer, + int firstLevel) + { + _gd = gd; + _device = device; + Info = info; + Storage = storage; + FirstLayer = firstLayer; + FirstLevel = firstLevel; + + gd.Textures.Add(this); + + var format = _gd.FormatCapabilities.ConvertToVkFormat(info.Format); + var levels = (uint)info.Levels; + var layers = (uint)info.GetLayers(); + + VkFormat = format; + + var type = info.Target.ConvertView(); + + var swizzleR = info.SwizzleR.Convert(); + var swizzleG = info.SwizzleG.Convert(); + var swizzleB = info.SwizzleB.Convert(); + var swizzleA = info.SwizzleA.Convert(); + + if (info.Format == GAL.Format.R5G5B5A1Unorm || + info.Format == GAL.Format.R5G5B5X1Unorm || + info.Format == GAL.Format.R5G6B5Unorm) + { + var temp = swizzleR; + + swizzleR = swizzleB; + swizzleB = temp; + } + else if (info.Format == GAL.Format.R4G4B4A4Unorm) + { + var tempR = swizzleR; + var tempG = swizzleG; + + swizzleR = swizzleA; + swizzleG = swizzleB; + swizzleB = tempG; + swizzleA = tempR; + } + + var componentMapping = new ComponentMapping(swizzleR, swizzleG, swizzleB, swizzleA); + + var aspectFlags = info.Format.ConvertAspectFlags(info.DepthStencilMode); + var aspectFlagsDepth = info.Format.ConvertAspectFlags(DepthStencilMode.Depth); + + var subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, layers); + var subresourceRangeDepth = new ImageSubresourceRange(aspectFlagsDepth, (uint)firstLevel, levels, (uint)firstLayer, layers); + + unsafe Auto CreateImageView(ComponentMapping cm, ImageSubresourceRange sr, ImageViewType viewType) + { + var imageCreateInfo = new ImageViewCreateInfo() + { + SType = StructureType.ImageViewCreateInfo, + Image = storage.GetImageForViewCreation(), + ViewType = viewType, + Format = format, + Components = cm, + SubresourceRange = sr + }; + + gd.Api.CreateImageView(device, imageCreateInfo, null, out var imageView).ThrowOnError(); + return new Auto(new DisposableImageView(gd.Api, device, imageView), null, storage.GetImage()); + } + + _imageView = CreateImageView(componentMapping, subresourceRange, type); + + // Framebuffer attachments and storage images requires a identity component mapping. + var identityComponentMapping = new ComponentMapping( + ComponentSwizzle.R, + ComponentSwizzle.G, + ComponentSwizzle.B, + ComponentSwizzle.A); + + _imageViewIdentity = CreateImageView(identityComponentMapping, subresourceRangeDepth, type); + + // Framebuffer attachments also requires 3D textures to be bound as 2D array. + if (info.Target == Target.Texture3D) + { + subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, (uint)info.Depth); + + _imageView2dArray = CreateImageView(identityComponentMapping, subresourceRange, ImageViewType.ImageViewType2DArray); + } + + Valid = true; + } + + public Auto GetImage() + { + return Storage.GetImage(); + } + + public Auto GetImageView() + { + return _imageView; + } + + public Auto GetIdentityImageView() + { + return _imageViewIdentity; + } + + public Auto GetImageViewForAttachment() + { + return _imageView2dArray ?? _imageViewIdentity; + } + + public void CopyTo(ITexture destination, int firstLayer, int firstLevel) + { + _gd.FlushAllCommands(); + + var src = this; + var dst = (TextureView)destination; + + if (!Valid || !dst.Valid) + { + return; + } + + using var cbs = _gd.CommandBufferPool.Rent(); + + var srcImage = src.GetImage().Get(cbs).Value; + var dstImage = dst.GetImage().Get(cbs).Value; + + TextureCopy.Copy( + _gd.Api, + cbs.CommandBuffer, + srcImage, + dstImage, + src.Info, + dst.Info, + src.FirstLayer, + dst.FirstLayer, + src.FirstLevel, + dst.FirstLevel, + 0, + firstLayer, + 0, + firstLevel); + } + + public void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel) + { + _gd.FlushAllCommands(); + + var src = this; + var dst = (TextureView)destination; + + if (!Valid || !dst.Valid) + { + return; + } + + using var cbs = _gd.CommandBufferPool.Rent(); + + var srcImage = src.GetImage().Get(cbs).Value; + var dstImage = dst.GetImage().Get(cbs).Value; + + TextureCopy.Copy( + _gd.Api, + cbs.CommandBuffer, + srcImage, + dstImage, + src.Info, + dst.Info, + src.FirstLayer, + dst.FirstLayer, + src.FirstLevel, + dst.FirstLevel, + srcLayer, + dstLayer, + srcLevel, + dstLevel, + 1, + 1); + } + + public void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter) + { + var dst = (TextureView)destination; + + if (_gd.CommandBufferPool.OwnedByCurrentThread) + { + _gd.PipelineInternal.EndRenderPass(); + + var cbs = _gd.PipelineInternal.CurrentCommandBuffer; + + CopyToImpl(cbs, dst, srcRegion, dstRegion, linearFilter); + } + else if (_gd.BackgroundQueue.Handle != 0) + { + lock (_gd.BackgroundQueueLock) + { + using var cbp = new CommandBufferPool( + _gd.Api, + _device, + _gd.BackgroundQueue, + _gd.QueueFamilyIndex, + isLight: true); + + using var cbs = cbp.Rent(); + + CopyToImpl(cbs, dst, srcRegion, dstRegion, linearFilter); + } + } + else + { + // TODO... + } + } + + private void CopyToImpl(CommandBufferScoped cbs, TextureView dst, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter) + { + var src = this; + + var srcFormat = GetCompatibleGalFormat(src.Info.Format); + var dstFormat = GetCompatibleGalFormat(dst.Info.Format); + + bool srcUsesStorageFormat = src.VkFormat == src.Storage.VkFormat; + bool dstUsesStorageFormat = dst.VkFormat == dst.Storage.VkFormat; + + if (srcUsesStorageFormat && dstUsesStorageFormat) + { + if ((srcRegion.X1 | dstRegion.X1) == 0 && + (srcRegion.Y1 | dstRegion.Y1) == 0 && + srcRegion.X2 == src.Width && + srcRegion.Y2 == src.Height && + dstRegion.X2 == dst.Width && + dstRegion.Y2 == dst.Height && + src.Width == dst.Width && + src.Height == dst.Height && + src.VkFormat == dst.VkFormat) + { + TextureCopy.Copy( + _gd.Api, + cbs.CommandBuffer, + src.GetImage().Get(cbs).Value, + dst.GetImage().Get(cbs).Value, + src.Info, + dst.Info, + src.FirstLayer, + dst.FirstLayer, + src.FirstLevel, + dst.FirstLevel, + 0, + 0, + 0, + 0, + 1, + 1); + + return; + } + else if (_gd.FormatCapabilities.FormatSupports(srcFormat, FormatFeatureFlags.FormatFeatureBlitSrcBit) && + _gd.FormatCapabilities.FormatSupports(dstFormat, FormatFeatureFlags.FormatFeatureBlitDstBit)) + { + TextureCopy.Blit( + _gd.Api, + cbs.CommandBuffer, + src.GetImage().Get(cbs).Value, + dst.GetImage().Get(cbs).Value, + src.Info, + dst.Info, + srcRegion, + dstRegion, + src.FirstLayer, + dst.FirstLayer, + src.FirstLevel, + dst.FirstLevel, + linearFilter); + + return; + } + else if (srcFormat == GAL.Format.D32FloatS8Uint && srcFormat == dstFormat && SupportsBlitFromD32FS8ToD32FAndS8()) + { + var d32StorageInfo = TextureStorage.NewCreateInfoWith(src.Info, GAL.Format.D32Float, 4); + var s8StorageInfo = TextureStorage.NewCreateInfoWith(dst.Info, GAL.Format.S8Uint, 1); + + using var d32Storage = _gd.CreateTextureStorage(d32StorageInfo, dst.Storage.ScaleFactor); + using var s8Storage = _gd.CreateTextureStorage(s8StorageInfo, dst.Storage.ScaleFactor); + + void BlitAndCopy(ref TextureCreateInfo info, TextureStorage storage, ImageAspectFlags aspectFlags) + { + TextureCopy.Blit( + _gd.Api, + cbs.CommandBuffer, + src.GetImage().Get(cbs).Value, + storage.GetImage().Get(cbs).Value, + src.Info, + info, + srcRegion, + dstRegion, + src.FirstLayer, + 0, + src.FirstLevel, + 0, + false, + aspectFlags, + aspectFlags); + + TextureCopy.Copy( + _gd.Api, + cbs.CommandBuffer, + storage.GetImage().Get(cbs).Value, + dst.GetImage().Get(cbs).Value, + info, + dst.Info, + 0, + dst.FirstLayer, + 0, + dst.FirstLevel, + 0, + 0, + 0, + 0, + 1, + 1); + } + + BlitAndCopy(ref d32StorageInfo, d32Storage, ImageAspectFlags.ImageAspectDepthBit); + BlitAndCopy(ref s8StorageInfo, s8Storage, ImageAspectFlags.ImageAspectStencilBit); + + return; + } + } + + if (VulkanConfiguration.UseSlowSafeBlitOnAmd && + _gd.Vendor == Vendor.Amd && + src.Info.Target == Target.Texture2D && + dst.Info.Target == Target.Texture2D && + !dst.Info.Format.IsDepthOrStencil()) + { + _gd.HelperShader.Blit( + _gd, + src, + dst.GetIdentityImageView(), + dst.Width, + dst.Height, + dst.VkFormat, + srcRegion, + dstRegion, + linearFilter); + + return; + } + + Auto srcImage; + Auto dstImage; + + if (dst.Info.Format.IsDepthOrStencil()) + { + srcImage = src.Storage.CreateAliasedColorForDepthStorageUnsafe(srcFormat).GetImage(); + dstImage = dst.Storage.CreateAliasedColorForDepthStorageUnsafe(dstFormat).GetImage(); + } + else + { + srcImage = src.Storage.CreateAliasedStorageUnsafe(srcFormat).GetImage(); + dstImage = dst.Storage.CreateAliasedStorageUnsafe(dstFormat).GetImage(); + } + + TextureCopy.Blit( + _gd.Api, + cbs.CommandBuffer, + srcImage.Get(cbs).Value, + dstImage.Get(cbs).Value, + src.Info, + dst.Info, + srcRegion, + dstRegion, + src.FirstLevel, + dst.FirstLevel, + src.FirstLayer, + dst.FirstLayer, + linearFilter, + ImageAspectFlags.ImageAspectColorBit, + ImageAspectFlags.ImageAspectColorBit); + } + + private bool SupportsBlitFromD32FS8ToD32FAndS8() + { + var formatFeatureFlags = FormatFeatureFlags.FormatFeatureBlitSrcBit | FormatFeatureFlags.FormatFeatureBlitDstBit; + return _gd.FormatCapabilities.FormatSupports(GAL.Format.D32Float, formatFeatureFlags) && + _gd.FormatCapabilities.FormatSupports(GAL.Format.S8Uint, formatFeatureFlags); + } + + public ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel) + { + return new TextureView(_gd, _device, info, Storage, FirstLayer + firstLayer, FirstLevel + firstLevel); + } + + public byte[] GetData(int x, int y, int width, int height) + { + int size = width * height * Info.BytesPerPixel; + using var bufferHolder = _gd.BufferManager.Create(_gd, size); + + using (var cbs = _gd.CommandBufferPool.Rent()) + { + var buffer = bufferHolder.GetBuffer(cbs.CommandBuffer).Get(cbs).Value; + var image = GetImage().Get(cbs).Value; + + CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, size, true, x, y, width, height); + } + + bufferHolder.WaitForFences(); + byte[] bitmap = new byte[size]; + GetDataFromBuffer(bufferHolder.GetDataStorage(0, size)).CopyTo(bitmap); + return bitmap; + } + + public ReadOnlySpan GetData() + { + if (_gd.CommandBufferPool.OwnedByCurrentThread) + { + return GetData(_gd.CommandBufferPool); + } + else if (_gd.BackgroundQueue.Handle != 0) + { + lock (_gd.BackgroundQueueLock) + { + using var cbp = new CommandBufferPool( + _gd.Api, + _device, + _gd.BackgroundQueue, + _gd.QueueFamilyIndex, + isLight: true); + + return GetData(cbp); + } + } + else + { + // TODO: Flush when the device only supports one queue. + int size = 0; + + for (int level = 0; level < Info.Levels; level++) + { + size += Info.GetMipSize(level); + } + + return new byte[size]; + } + } + + public ReadOnlySpan GetData(int layer, int level) + { + throw new NotImplementedException(); + } + + private ReadOnlySpan GetData(CommandBufferPool cbp) + { + int size; + var bufferHolder = _flushStorage; + + if (bufferHolder == null) + { + size = 0; + + for (int level = 0; level < Info.Levels; level++) + { + size += Info.GetMipSize(level); + } + + size = GetBufferDataLength(size); + + bufferHolder = _gd.BufferManager.Create(_gd, size); + + var existingStorage = Interlocked.CompareExchange(ref _flushStorage, bufferHolder, null); + if (existingStorage != null) + { + bufferHolder.Dispose(); + bufferHolder = existingStorage; + } + } + else + { + size = bufferHolder.Size; + } + + using (var cbs = cbp.Rent()) + { + var buffer = bufferHolder.GetBuffer(cbs.CommandBuffer).Get(cbs).Value; + var image = GetImage().Get(cbs).Value; + + CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, size, true, 0, 0, Info.GetLayers(), Info.Levels, singleSlice: false); + } + + bufferHolder.WaitForFences(); + return GetDataFromBuffer(bufferHolder.GetDataStorage(0, size)); + } + + public void SetData(ReadOnlySpan data) + { + SetData(data, 0, 0, Info.GetLayers(), Info.Levels, singleSlice: false); + } + + public void SetData(ReadOnlySpan data, int layer, int level) + { + SetData(data, layer, level, 1, 1, singleSlice: true); + } + + private void SetData(ReadOnlySpan data, int layer, int level, int layers, int levels, bool singleSlice) + { + int bufferDataLength = GetBufferDataLength(data.Length); + + using var bufferHolder = _gd.BufferManager.Create(_gd, bufferDataLength); + + using var cbs = _gd.CommandBufferPool.Rent(); + + CopyDataToBuffer(bufferHolder.GetDataStorage(0, bufferDataLength), data); + + var buffer = bufferHolder.GetBuffer(cbs.CommandBuffer).Get(cbs).Value; + var image = GetImage().Get(cbs).Value; + + CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, bufferDataLength, false, layer, level, layers, levels, singleSlice); + } + + private int GetBufferDataLength(int length) + { + if (NeedsD24S8Conversion()) + { + return length * 2; + } + + return length; + } + + private GAL.Format GetCompatibleGalFormat(GAL.Format format) + { + if (NeedsD24S8Conversion()) + { + return GAL.Format.D32FloatS8Uint; + } + + return format; + } + + private void CopyDataToBuffer(Span storage, ReadOnlySpan input) + { + if (NeedsD24S8Conversion()) + { + FormatConverter.ConvertD24S8ToD32FS8(storage, input); + return; + } + + input.CopyTo(storage); + } + + private ReadOnlySpan GetDataFromBuffer(ReadOnlySpan storage) + { + if (NeedsD24S8Conversion()) + { + byte[] output = new byte[GetBufferDataLength(storage.Length)]; + FormatConverter.ConvertD32FS8ToD24S8(output, storage); + return output; + } + + return storage; + } + + private bool NeedsD24S8Conversion() + { + return Info.Format == GAL.Format.D24UnormS8Uint && VkFormat == VkFormat.D32SfloatS8Uint; + } + + private void CopyFromOrToBuffer( + CommandBuffer commandBuffer, + VkBuffer buffer, + Image image, + int size, + bool to, + int dstLayer, + int dstLevel, + int dstLayers, + int dstLevels, + bool singleSlice) + { + bool is3D = Info.Target == Target.Texture3D; + int width = Info.Width; + int height = Info.Height; + int depth = is3D && !singleSlice ? Info.Depth : 1; + int layer = is3D ? 0 : dstLayer; + int layers = dstLayers; + int levels = dstLevels; + + int offset = 0; + + for (int level = 0; level < levels; level++) + { + int mipSize = GetBufferDataLength(Info.GetMipSize(level)); + + int endOffset = offset + mipSize; + + if ((uint)endOffset > (uint)size) + { + break; + } + + int rowLength = (Info.GetMipStride(level) / Info.BytesPerPixel) * Info.BlockWidth; + + var aspectFlags = Info.Format.ConvertAspectFlags(); + + if (aspectFlags == (ImageAspectFlags.ImageAspectDepthBit | ImageAspectFlags.ImageAspectStencilBit)) + { + aspectFlags = ImageAspectFlags.ImageAspectDepthBit; + } + + var sl = new ImageSubresourceLayers( + aspectFlags, + (uint)(FirstLevel + dstLevel + level), + (uint)(FirstLayer + layer), + (uint)layers); + + var extent = new Extent3D((uint)width, (uint)height, (uint)depth); + + int z = is3D ? dstLayer : 0; + + var region = new BufferImageCopy((ulong)offset, (uint)rowLength, (uint)height, sl, new Offset3D(0, 0, z), extent); + + if (to) + { + _gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, region); + } + else + { + _gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, region); + } + + offset += mipSize; + + width = Math.Max(1, width >> 1); + height = Math.Max(1, height >> 1); + + if (Info.Target == Target.Texture3D) + { + depth = Math.Max(1, depth >> 1); + } + } + } + + private void CopyFromOrToBuffer( + CommandBuffer commandBuffer, + VkBuffer buffer, + Image image, + int size, + bool to, + int x, + int y, + int width, + int height) + { + var aspectFlags = Info.Format.ConvertAspectFlags(); + + if (aspectFlags == (ImageAspectFlags.ImageAspectDepthBit | ImageAspectFlags.ImageAspectStencilBit)) + { + aspectFlags = ImageAspectFlags.ImageAspectDepthBit; + } + + var sl = new ImageSubresourceLayers(aspectFlags, (uint)FirstLevel, (uint)FirstLayer, 1); + + var extent = new Extent3D((uint)width, (uint)height, 1); + + var region = new BufferImageCopy(0, (uint)width, (uint)height, sl, new Offset3D(x, y, 0), extent); + + if (to) + { + _gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, region); + } + else + { + _gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, region); + } + } + + public void SetStorage(BufferRange buffer) + { + throw new NotImplementedException(); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + Valid = false; + + if (_gd.Textures.Remove(this)) + { + _imageView.Dispose(); + _imageViewIdentity.Dispose(); + _imageView2dArray?.Dispose(); + _flushStorage?.Dispose(); + } + } + } + + public void Dispose() + { + Dispose(true); + } + + public void Release() + { + Dispose(); + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/Vendor.cs b/Ryujinx.Graphics.Vulkan/Vendor.cs new file mode 100644 index 000000000..b9d9849ec --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/Vendor.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Vulkan +{ + enum Vendor + { + Amd, + Intel, + Nvidia, + Unknown + } +} diff --git a/Ryujinx.Graphics.Vulkan/VulkanConfiguration.cs b/Ryujinx.Graphics.Vulkan/VulkanConfiguration.cs new file mode 100644 index 000000000..6ca671b71 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/VulkanConfiguration.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Vulkan +{ + static class VulkanConfiguration + { + public const bool UseDynamicState = true; + + public const bool UseFastBufferUpdates = true; + public const bool UseGranularBufferTracking = false; + public const bool UseSlowSafeBlitOnAmd = true; + } +} diff --git a/Ryujinx.Graphics.Vulkan/VulkanException.cs b/Ryujinx.Graphics.Vulkan/VulkanException.cs new file mode 100644 index 000000000..ca3ac6b63 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/VulkanException.cs @@ -0,0 +1,40 @@ +using Silk.NET.Vulkan; +using System; +using System.Runtime.Serialization; + +namespace Ryujinx.Graphics.Vulkan +{ + static class ResultExtensions + { + public static void ThrowOnError(this Result result) + { + if (result != Result.Success) + { + throw new VulkanException(result); + } + } + } + + class VulkanException : Exception + { + public VulkanException() + { + } + + public VulkanException(Result result) : base($"Unexpected API error \"{result}\".") + { + } + + public VulkanException(string message) : base(message) + { + } + + public VulkanException(string message, Exception innerException) : base(message, innerException) + { + } + + protected VulkanException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/VulkanGraphicsDevice.cs b/Ryujinx.Graphics.Vulkan/VulkanGraphicsDevice.cs new file mode 100644 index 000000000..81c0269b8 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/VulkanGraphicsDevice.cs @@ -0,0 +1,454 @@ +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; +using Ryujinx.Graphics.Vulkan.Queries; +using Silk.NET.Vulkan; +using Silk.NET.Vulkan.Extensions.EXT; +using Silk.NET.Vulkan.Extensions.KHR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Vulkan +{ + public sealed class VulkanGraphicsDevice : IRenderer + { + private Instance _instance; + private SurfaceKHR _surface; + private PhysicalDevice _physicalDevice; + private Device _device; + private Window _window; + + internal FormatCapabilities FormatCapabilities { get; private set; } + internal HardwareCapabilities Capabilities { get; private set; } + + internal Vk Api { get; private set; } + internal KhrSurface SurfaceApi { get; private set; } + internal KhrSwapchain SwapchainApi { get; private set; } + internal ExtConditionalRendering ConditionalRenderingApi { get; private set; } + internal ExtExtendedDynamicState ExtendedDynamicStateApi { get; private set; } + internal ExtTransformFeedback TransformFeedbackApi { get; private set; } + internal KhrDrawIndirectCount DrawIndirectCountApi { get; private set; } + internal ExtDebugReport DebugReportApi { get; private set; } + + internal bool SupportsIndexTypeUint8 { get; private set; } + internal bool SupportsCustomBorderColor { get; private set; } + internal bool SupportsIndirectParameters { get; private set; } + internal bool SupportsFragmentShaderInterlock { get; private set; } + + internal uint QueueFamilyIndex { get; private set; } + + internal Queue Queue { get; private set; } + internal Queue BackgroundQueue { get; private set; } + internal object BackgroundQueueLock { get; private set; } + + internal MemoryAllocator MemoryAllocator { get; private set; } + internal CommandBufferPool CommandBufferPool { get; private set; } + internal DescriptorSetManager DescriptorSetManager { get; private set; } + internal PipelineLayoutCache PipelineLayoutCache { get; private set; } + + internal BufferManager BufferManager { get; private set; } + + internal HashSet Shaders { get; } + internal HashSet Textures { get; } + internal HashSet Samplers { get; } + + private Counters _counters; + private SyncManager _syncManager; + + private PipelineFull _pipeline; + private DebugReportCallbackEXT _debugReportCallback; + + internal HelperShader HelperShader { get; private set; } + internal PipelineFull PipelineInternal => _pipeline; + + public IPipeline Pipeline => _pipeline; + + public IWindow Window => _window; + + private Func GetSurface; + private Func GetRequiredExtensions; + + internal Vendor Vendor { get; private set; } + public string GpuVendor { get; private set; } + public string GpuRenderer { get; private set; } + public string GpuVersion { get; private set; } + + public bool PreferThreading => true; + + public event EventHandler ScreenCaptured; + + public VulkanGraphicsDevice(Func surfaceFunc, Func requiredExtensionsFunc) + { + GetSurface = surfaceFunc; + GetRequiredExtensions = requiredExtensionsFunc; + Shaders = new HashSet(); + Textures = new HashSet(); + Samplers = new HashSet(); + } + + private void SetupContext(GraphicsDebugLevel logLevel) + { + var api = Vk.GetApi(); + + Api = api; + + _instance = VulkanInitialization.CreateInstance(api, logLevel, GetRequiredExtensions(), out ExtDebugReport debugReport, out _debugReportCallback); + + DebugReportApi = debugReport; + + if (api.TryGetInstanceExtension(_instance, out KhrSurface surfaceApi)) + { + SurfaceApi = surfaceApi; + } + + _surface = GetSurface(_instance, api); + _physicalDevice = VulkanInitialization.FindSuitablePhysicalDevice(api, _instance, _surface); + + FormatCapabilities = new FormatCapabilities(api, _physicalDevice); + + var queueFamilyIndex = VulkanInitialization.FindSuitableQueueFamily(api, _physicalDevice, _surface, out uint maxQueueCount); + var supportedExtensions = VulkanInitialization.GetSupportedExtensions(api, _physicalDevice); + + _device = VulkanInitialization.CreateDevice(api, _physicalDevice, queueFamilyIndex, supportedExtensions, maxQueueCount); + + Capabilities = new HardwareCapabilities( + supportedExtensions.Contains(ExtConditionalRendering.ExtensionName), + supportedExtensions.Contains(ExtExtendedDynamicState.ExtensionName)); + + SupportsIndexTypeUint8 = supportedExtensions.Contains("VK_EXT_index_type_uint8"); + SupportsCustomBorderColor = supportedExtensions.Contains("VK_EXT_custom_border_color"); + SupportsIndirectParameters = supportedExtensions.Contains(KhrDrawIndirectCount.ExtensionName); + SupportsFragmentShaderInterlock = supportedExtensions.Contains("VK_EXT_fragment_shader_interlock"); + + if (api.TryGetDeviceExtension(_instance, _device, out KhrSwapchain swapchainApi)) + { + SwapchainApi = swapchainApi; + } + + if (api.TryGetDeviceExtension(_instance, _device, out ExtConditionalRendering conditionalRenderingApi)) + { + ConditionalRenderingApi = conditionalRenderingApi; + } + + if (api.TryGetDeviceExtension(_instance, _device, out ExtExtendedDynamicState extendedDynamicStateApi)) + { + ExtendedDynamicStateApi = extendedDynamicStateApi; + } + + if (api.TryGetDeviceExtension(_instance, _device, out ExtTransformFeedback transformFeedbackApi)) + { + TransformFeedbackApi = transformFeedbackApi; + } + + if (api.TryGetDeviceExtension(_instance, _device, out KhrDrawIndirectCount drawIndirectCountApi)) + { + DrawIndirectCountApi = drawIndirectCountApi; + } + + api.GetDeviceQueue(_device, queueFamilyIndex, 0, out var queue); + Queue = queue; + + if (maxQueueCount >= 2) + { + api.GetDeviceQueue(_device, queueFamilyIndex, 1, out var backgroundQueue); + BackgroundQueue = backgroundQueue; + BackgroundQueueLock = new object(); + } + + Api.GetPhysicalDeviceProperties(_physicalDevice, out var properties); + + MemoryAllocator = new MemoryAllocator(api, _device, properties.Limits.MaxMemoryAllocationCount); + + CommandBufferPool = VulkanInitialization.CreateCommandBufferPool(api, _device, queue, queueFamilyIndex); + + DescriptorSetManager = new DescriptorSetManager(_device); + + PipelineLayoutCache = new PipelineLayoutCache(); + + BufferManager = new BufferManager(this, _physicalDevice, _device); + + _syncManager = new SyncManager(this, _device); + _pipeline = new PipelineFull(this, _device); + + HelperShader = new HelperShader(this, _device); + + _counters = new Counters(this, _device, _pipeline); + + _window = new Window(this, _surface, _physicalDevice, _device); + } + + public IShader CompileShader(ShaderStage stage, ShaderBindings bindings, string code) + { + return new Shader(Api, _device, stage, bindings, code); + } + + public IShader CompileShader(ShaderStage stage, ShaderBindings bindings, byte[] code) + { + return new Shader(Api, _device, stage, bindings, code); + } + + public BufferHandle CreateBuffer(int size) + { + return BufferManager.CreateWithHandle(this, size); + } + + public IProgram CreateProgram(IShader[] shaders, ShaderInfo info) + { + return new ShaderCollection(this, _device, shaders); + } + + public IProgram CreateProgram(ShaderSource[] sources, ShaderInfo info) + { + IShader[] shaders = new IShader[sources.Length]; + + for (int index = 0; index < sources.Length; index++) + { + var source = sources[index]; + var shader = source.BinaryCode != null + ? CompileShader(source.Stage, source.Bindings, source.BinaryCode) + : CompileShader(source.Stage, source.Bindings, source.Code); + + shaders[index] = shader; + } + + return CreateProgram(shaders, info); + } + + public ISampler CreateSampler(GAL.SamplerCreateInfo info) + { + return new SamplerHolder(this, _device, info); + } + + public ITexture CreateTexture(TextureCreateInfo info, float scale) + { + if (info.Target == Target.TextureBuffer) + { + return new TextureBuffer(this, info, scale); + } + + // This should be disposed when all views are destroyed. + using var storage = CreateTextureStorage(info, scale); + return storage.CreateView(info, 0, 0); + } + + internal TextureStorage CreateTextureStorage(TextureCreateInfo info, float scale) + { + return new TextureStorage(this, _physicalDevice, _device, info, scale); + } + + public void DeleteBuffer(BufferHandle buffer) + { + BufferManager.Delete(buffer); + } + + internal void FlushAllCommands() + { + // System.Console.WriteLine("flush commands " + caller); + _pipeline?.FlushCommandsImpl(); + } + + public ReadOnlySpan GetBufferData(BufferHandle buffer, int offset, int size) + { + return BufferManager.GetData(buffer, offset, size); + } + + public Capabilities GetCapabilities() + { + Api.GetPhysicalDeviceFeatures(_physicalDevice, out var features); + Api.GetPhysicalDeviceProperties(_physicalDevice, out var properties); + + var limits = properties.Limits; + + return new Capabilities( + api: TargetApi.Vulkan, + GpuVendor, + hasFrontFacingBug: Vendor == Vendor.Intel && RuntimeInformation.IsOSPlatform(OSPlatform.Windows), + hasVectorIndexingBug: false, + supportsAstcCompression: features.TextureCompressionAstcLdr, + supports3DTextureCompression: true, + supportsBgraFormat: true, + supportsR4G4Format: false, + supportsFragmentShaderInterlock: SupportsFragmentShaderInterlock, + supportsFragmentShaderOrderingIntel: false, + supportsImageLoadFormatted: features.ShaderStorageImageReadWithoutFormat, + supportsMismatchingViewFormat: true, + supportsNonConstantTextureOffset: false, + supportsShaderBallot: false, + supportsTextureShadowLod: false, + supportsViewportSwizzle: false, + supportsIndirectParameters: SupportsIndirectParameters, + maximumComputeSharedMemorySize: (int)limits.MaxComputeSharedMemorySize, + maximumSupportedAnisotropy: (int)limits.MaxSamplerAnisotropy, + storageBufferOffsetAlignment: (int)limits.MinStorageBufferOffsetAlignment); + } + + public HardwareInfo GetHardwareInfo() + { + return new HardwareInfo(GpuVendor, GpuRenderer); + } + + private static string ParseStandardVulkanVersion(uint version) + { + return $"{version >> 22}.{(version >> 12) & 0x3FF}.{version & 0xFFF}"; + } + + private static string ParseDriverVersion(ref PhysicalDeviceProperties properties) + { + uint driverVersionRaw = properties.DriverVersion; + + // NVIDIA differ from the standard here and use a different format. + if (properties.VendorID == 0x10DE) + { + return $"{(driverVersionRaw >> 22) & 0x3FF}.{(driverVersionRaw >> 14) & 0xFF}.{(driverVersionRaw >> 6) & 0xFF}.{driverVersionRaw & 0x3F}"; + } + else + { + return ParseStandardVulkanVersion(driverVersionRaw); + } + } + + private unsafe void PrintGpuInformation() + { + Api.GetPhysicalDeviceProperties(_physicalDevice, out var properties); + + string vendorName = properties.VendorID switch + { + 0x1002 => "AMD", + 0x1010 => "ImgTec", + 0x10DE => "NVIDIA", + 0x13B5 => "ARM", + 0x1AE0 => "Google", + 0x5143 => "Qualcomm", + 0x8086 => "Intel", + 0x10001 => "Vivante", + 0x10002 => "VeriSilicon", + 0x10003 => "Kazan", + 0x10004 => "Codeplay Software Ltd.", + 0x10005 => "Mesa", + 0x10006 => "PoCL", + _ => $"0x{properties.VendorID:X}" + }; + + Vendor = properties.VendorID switch + { + 0x1002 => Vendor.Amd, + 0x10DE => Vendor.Nvidia, + 0x8086 => Vendor.Intel, + _ => Vendor.Unknown + }; + + GpuVendor = vendorName; + GpuRenderer = Marshal.PtrToStringAnsi((IntPtr)properties.DeviceName); + GpuVersion = $"Vulkan v{ParseStandardVulkanVersion(properties.ApiVersion)}, Driver v{ParseDriverVersion(ref properties)}"; + + Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})"); + } + + public void Initialize(GraphicsDebugLevel logLevel) + { + SetupContext(logLevel); + + PrintGpuInformation(); + } + + public void PreFrame() + { + _syncManager.Cleanup(); + } + + public ICounterEvent ReportCounter(CounterType type, EventHandler resultHandler, bool hostReserved) + { + return _counters.QueueReport(type, resultHandler, hostReserved); + } + + public void ResetCounter(CounterType type) + { + _counters.QueueReset(type); + } + + public void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan data) + { + BufferManager.SetData(buffer, offset, data, _pipeline.CurrentCommandBuffer, _pipeline.EndRenderPass); + } + + public void UpdateCounters() + { + _counters.Update(); + } + + public unsafe void Dispose() + { + CommandBufferPool.Dispose(); + _counters.Dispose(); + _window.Dispose(); + HelperShader.Dispose(); + _pipeline.Dispose(); + BufferManager.Dispose(); + DescriptorSetManager.Dispose(); + PipelineLayoutCache.Dispose(); + + SurfaceApi.DestroySurface(_instance, _surface, null); + + MemoryAllocator.Dispose(); + + if (_debugReportCallback.Handle != 0) + { + DebugReportApi.DestroyDebugReportCallback(_instance, _debugReportCallback, null); + } + + foreach (var shader in Shaders) + { + shader.Dispose(); + } + + foreach (var texture in Textures) + { + texture.Release(); + } + + foreach (var sampler in Samplers) + { + sampler.Dispose(); + } + + Api.DestroyDevice(_device, null); + + // Last step destroy the instance + Api.DestroyInstance(_instance, null); + } + + public void BackgroundContextAction(Action action, bool alwaysBackground = false) + { + action(); + } + + public void CreateSync(ulong id) + { + _syncManager.Create(id); + } + + public IProgram LoadProgramBinary(byte[] programBinary, bool isFragment, ShaderInfo info) + { + throw new NotImplementedException(); + } + + public void WaitSync(ulong id) + { + _syncManager.Wait(id); + } + + public void Screenshot() + { + _window.ScreenCaptureRequested = true; + } + + public void OnScreenCaptured(ScreenCaptureImageInfo bitmap) + { + ScreenCaptured?.Invoke(this, bitmap); + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs b/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs new file mode 100644 index 000000000..9bb01515d --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs @@ -0,0 +1,425 @@ +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Silk.NET.Vulkan; +using Silk.NET.Vulkan.Extensions.EXT; +using Silk.NET.Vulkan.Extensions.KHR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Vulkan +{ + unsafe static class VulkanInitialization + { + private const uint InvalidIndex = uint.MaxValue; + private const string AppName = "Ryujinx.Graphics.Vulkan"; + private const int QueuesCount = 2; + + private static readonly string[] RequiredExtensions = new string[] + { + KhrSwapchain.ExtensionName, + "VK_EXT_shader_subgroup_vote", + ExtTransformFeedback.ExtensionName + }; + + private static readonly string[] DesirableExtensions = new string[] + { + ExtConditionalRendering.ExtensionName, + ExtExtendedDynamicState.ExtensionName, + KhrDrawIndirectCount.ExtensionName, + "VK_EXT_index_type_uint8", + "VK_EXT_custom_border_color", + "VK_EXT_robustness2" + }; + + public static Instance CreateInstance(Vk api, GraphicsDebugLevel logLevel, string[] requiredExtensions, out ExtDebugReport debugReport, out DebugReportCallbackEXT debugReportCallback) + { + var enabledLayers = new List(); + + void AddAvailableLayer(string layerName) + { + uint layerPropertiesCount; + + api.EnumerateInstanceLayerProperties(&layerPropertiesCount, null).ThrowOnError(); + + LayerProperties[] layerProperties = new LayerProperties[layerPropertiesCount]; + + fixed (LayerProperties* pLayerProperties = layerProperties) + { + api.EnumerateInstanceLayerProperties(&layerPropertiesCount, layerProperties).ThrowOnError(); + + for (int i = 0; i < layerPropertiesCount; i++) + { + string currentLayerName = Marshal.PtrToStringAnsi((IntPtr)pLayerProperties[i].LayerName); + + if (currentLayerName == layerName) + { + enabledLayers.Add(layerName); + return; + } + } + } + + Logger.Warning?.Print(LogClass.Gpu, $"Missing layer {layerName}"); + } + + if (logLevel == GraphicsDebugLevel.Slowdowns || logLevel == GraphicsDebugLevel.All) + { + AddAvailableLayer("VK_LAYER_KHRONOS_validation"); + } + + var enabledExtensions = requiredExtensions.Append(ExtDebugReport.ExtensionName).ToArray(); + + var appName = Marshal.StringToHGlobalAnsi(AppName); + + var applicationInfo = new ApplicationInfo + { + PApplicationName = (byte*)appName, + ApplicationVersion = 1, + PEngineName = (byte*)appName, + EngineVersion = 1, + ApiVersion = Vk.Version12.Value + }; + + IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length]; + IntPtr* ppEnabledLayers = stackalloc IntPtr[enabledLayers.Count]; + + for (int i = 0; i < enabledExtensions.Length; i++) + { + ppEnabledExtensions[i] = Marshal.StringToHGlobalAnsi(enabledExtensions[i]); + } + + for (int i = 0; i < enabledLayers.Count; i++) + { + ppEnabledLayers[i] = Marshal.StringToHGlobalAnsi(enabledLayers[i]); + } + + var instanceCreateInfo = new InstanceCreateInfo + { + SType = StructureType.InstanceCreateInfo, + PApplicationInfo = &applicationInfo, + PpEnabledExtensionNames = (byte**)ppEnabledExtensions, + PpEnabledLayerNames = (byte**)ppEnabledLayers, + EnabledExtensionCount = (uint)enabledExtensions.Length, + EnabledLayerCount = (uint)enabledLayers.Count + }; + + api.CreateInstance(in instanceCreateInfo, null, out var instance).ThrowOnError(); + + Marshal.FreeHGlobal(appName); + + for (int i = 0; i < enabledExtensions.Length; i++) + { + Marshal.FreeHGlobal(ppEnabledExtensions[i]); + } + + for (int i = 0; i < enabledLayers.Count; i++) + { + Marshal.FreeHGlobal(ppEnabledLayers[i]); + } + + if (!api.TryGetInstanceExtension(instance, out debugReport)) + { + throw new Exception(); + // TODO: Exception. + } + + if (logLevel != GraphicsDebugLevel.None) + { + var flags = logLevel switch + { + GraphicsDebugLevel.Error => DebugReportFlagsEXT.DebugReportErrorBitExt, + GraphicsDebugLevel.Slowdowns => DebugReportFlagsEXT.DebugReportErrorBitExt | DebugReportFlagsEXT.DebugReportPerformanceWarningBitExt, + GraphicsDebugLevel.All => DebugReportFlagsEXT.DebugReportInformationBitExt | + DebugReportFlagsEXT.DebugReportWarningBitExt | + DebugReportFlagsEXT.DebugReportPerformanceWarningBitExt | + DebugReportFlagsEXT.DebugReportErrorBitExt | + DebugReportFlagsEXT.DebugReportDebugBitExt, + _ => throw new NotSupportedException() + }; + var debugReportCallbackCreateInfo = new DebugReportCallbackCreateInfoEXT() + { + SType = StructureType.DebugReportCallbackCreateInfoExt, + Flags = flags, + PfnCallback = new PfnDebugReportCallbackEXT(DebugReport) + }; + + debugReport.CreateDebugReportCallback(instance, in debugReportCallbackCreateInfo, null, out debugReportCallback).ThrowOnError(); + } + else + { + debugReportCallback = default; + } + + return instance; + } + + private static string[] ExcludedMessages = new string[] + { + // NOTE: Done on purpuse right now. + "UNASSIGNED-CoreValidation-Shader-OutputNotConsumed", + // TODO: Figure out if fixable + "VUID-vkCmdDrawIndexed-None-04584", + // TODO: might be worth looking into making this happy to possibly optimize copies. + "UNASSIGNED-CoreValidation-DrawState-InvalidImageLayout" + }; + + private unsafe static uint DebugReport( + uint flags, + DebugReportObjectTypeEXT objectType, + ulong @object, + nuint location, + int messageCode, + byte* layerPrefix, + byte* message, + void* userData) + { + var msg = Marshal.PtrToStringAnsi((IntPtr)message); + + foreach (string excludedMessagePart in ExcludedMessages) + { + if (msg.Contains(excludedMessagePart)) + { + return 0; + } + } + + DebugReportFlagsEXT debugFlags = (DebugReportFlagsEXT)flags; + + if (debugFlags.HasFlag(DebugReportFlagsEXT.DebugReportErrorBitExt)) + { + Logger.Error?.Print(LogClass.Gpu, msg); + //throw new Exception(msg); + } + else if (debugFlags.HasFlag(DebugReportFlagsEXT.DebugReportWarningBitExt)) + { + Logger.Warning?.Print(LogClass.Gpu, msg); + } + else if (debugFlags.HasFlag(DebugReportFlagsEXT.DebugReportInformationBitExt)) + { + Logger.Info?.Print(LogClass.Gpu, msg); + } + else if (debugFlags.HasFlag(DebugReportFlagsEXT.DebugReportPerformanceWarningBitExt)) + { + Logger.Warning?.Print(LogClass.Gpu, msg); + } + else + { + Logger.Debug?.Print(LogClass.Gpu, msg); + } + + return 0; + } + + public static PhysicalDevice FindSuitablePhysicalDevice(Vk api, Instance instance, SurfaceKHR surface) + { + uint physicalDeviceCount; + + api.EnumeratePhysicalDevices(instance, &physicalDeviceCount, null).ThrowOnError(); + + PhysicalDevice[] physicalDevices = new PhysicalDevice[physicalDeviceCount]; + + fixed (PhysicalDevice* pPhysicalDevices = physicalDevices) + { + api.EnumeratePhysicalDevices(instance, &physicalDeviceCount, pPhysicalDevices).ThrowOnError(); + } + + if (physicalDevices.Length > 1) + { + return physicalDevices[0]; + } + + for (int i = 0; i < physicalDevices.Length; i++) + { + if (IsSuitableDevice(api, physicalDevices[i], surface)) + { + return physicalDevices[i]; + } + } + + throw new VulkanException("Initialization failed, none of the available GPUs meets the minimum requirements."); + } + + private static bool IsSuitableDevice(Vk api, PhysicalDevice physicalDevice, SurfaceKHR surface) + { + int extensionMatches = 0; + uint propertiesCount; + + api.EnumerateDeviceExtensionProperties(physicalDevice, (byte*)null, &propertiesCount, null).ThrowOnError(); + + ExtensionProperties[] extensionProperties = new ExtensionProperties[propertiesCount]; + + fixed (ExtensionProperties* pExtensionProperties = extensionProperties) + { + api.EnumerateDeviceExtensionProperties(physicalDevice, (byte*)null, &propertiesCount, pExtensionProperties).ThrowOnError(); + + for (int i = 0; i < propertiesCount; i++) + { + string extensionName = Marshal.PtrToStringAnsi((IntPtr)pExtensionProperties[i].ExtensionName); + + if (RequiredExtensions.Contains(extensionName)) + { + extensionMatches++; + } + } + } + + return extensionMatches == RequiredExtensions.Length && FindSuitableQueueFamily(api, physicalDevice, surface, out _) != InvalidIndex; + } + + public static uint FindSuitableQueueFamily(Vk api, PhysicalDevice physicalDevice, SurfaceKHR surface, out uint queueCount) + { + const QueueFlags RequiredFlags = QueueFlags.QueueGraphicsBit | QueueFlags.QueueComputeBit; + + var khrSurface = new KhrSurface(api.Context); + + uint propertiesCount; + + api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, &propertiesCount, null); + + QueueFamilyProperties[] properties = new QueueFamilyProperties[propertiesCount]; + + fixed (QueueFamilyProperties* pProperties = properties) + { + api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, &propertiesCount, pProperties); + } + + for (uint index = 0; index < propertiesCount; index++) + { + var queueFlags = properties[index].QueueFlags; + + khrSurface.GetPhysicalDeviceSurfaceSupport(physicalDevice, index, surface, out var surfaceSupported).ThrowOnError(); + + if (queueFlags.HasFlag(RequiredFlags) && surfaceSupported) + { + queueCount = properties[index].QueueCount; + return index; + } + } + + queueCount = 0; + return InvalidIndex; + } + + public static Device CreateDevice(Vk api, PhysicalDevice physicalDevice, uint queueFamilyIndex, string[] supportedExtensions, uint queueCount) + { + if (queueCount > QueuesCount) + { + queueCount = QueuesCount; + } + + float* queuePriorities = stackalloc float[(int)queueCount]; + + for (int i = 0; i < queueCount; i++) + { + queuePriorities[i] = 1f; + } + + var queueCreateInfo = new DeviceQueueCreateInfo() + { + SType = StructureType.DeviceQueueCreateInfo, + QueueFamilyIndex = queueFamilyIndex, + QueueCount = queueCount, + PQueuePriorities = queuePriorities + }; + + var features = new PhysicalDeviceFeatures() + { + DepthBiasClamp = true, + DepthClamp = true, + DualSrcBlend = true, + FragmentStoresAndAtomics = true, + GeometryShader = true, + ImageCubeArray = true, + IndependentBlend = true, + LogicOp = true, + MultiViewport = true, + PipelineStatisticsQuery = true, + SamplerAnisotropy = true, + ShaderClipDistance = true, + ShaderImageGatherExtended = true, + // ShaderStorageImageReadWithoutFormat = true, + // ShaderStorageImageWriteWithoutFormat = true, + VertexPipelineStoresAndAtomics = true + }; + + var featuresIndexU8 = new PhysicalDeviceIndexTypeUint8FeaturesEXT() + { + SType = StructureType.PhysicalDeviceIndexTypeUint8FeaturesExt, + IndexTypeUint8 = supportedExtensions.Contains("VK_EXT_index_type_uint8") + }; + + var featuresTransformFeedback = new PhysicalDeviceTransformFeedbackFeaturesEXT() + { + SType = StructureType.PhysicalDeviceTransformFeedbackFeaturesExt, + PNext = &featuresIndexU8, + TransformFeedback = true + }; + + var featuresRobustness2 = new PhysicalDeviceRobustness2FeaturesEXT() + { + SType = StructureType.PhysicalDeviceRobustness2FeaturesExt, + PNext = &featuresTransformFeedback, + NullDescriptor = true + }; + + var featuresVk12 = new PhysicalDeviceVulkan12Features() + { + SType = StructureType.PhysicalDeviceVulkan12Features, + PNext = &featuresRobustness2, + DrawIndirectCount = supportedExtensions.Contains(KhrDrawIndirectCount.ExtensionName) + }; + + var enabledExtensions = RequiredExtensions.Union(DesirableExtensions.Intersect(supportedExtensions)).ToArray(); + + IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length]; + + for (int i = 0; i < enabledExtensions.Length; i++) + { + ppEnabledExtensions[i] = Marshal.StringToHGlobalAnsi(enabledExtensions[i]); + } + + var deviceCreateInfo = new DeviceCreateInfo() + { + SType = StructureType.DeviceCreateInfo, + PNext = &featuresVk12, + QueueCreateInfoCount = 1, + PQueueCreateInfos = &queueCreateInfo, + PpEnabledExtensionNames = (byte**)ppEnabledExtensions, + EnabledExtensionCount = (uint)enabledExtensions.Length, + PEnabledFeatures = &features + }; + + api.CreateDevice(physicalDevice, in deviceCreateInfo, null, out var device).ThrowOnError(); + + for (int i = 0; i < enabledExtensions.Length; i++) + { + Marshal.FreeHGlobal(ppEnabledExtensions[i]); + } + + return device; + } + + public static string[] GetSupportedExtensions(Vk api, PhysicalDevice physicalDevice) + { + uint propertiesCount; + + api.EnumerateDeviceExtensionProperties(physicalDevice, (byte*)null, &propertiesCount, null).ThrowOnError(); + + ExtensionProperties[] extensionProperties = new ExtensionProperties[propertiesCount]; + + fixed (ExtensionProperties* pExtensionProperties = extensionProperties) + { + api.EnumerateDeviceExtensionProperties(physicalDevice, (byte*)null, &propertiesCount, pExtensionProperties).ThrowOnError(); + } + + return extensionProperties.Select(x => Marshal.PtrToStringAnsi((IntPtr)x.ExtensionName)).ToArray(); + } + + public static CommandBufferPool CreateCommandBufferPool(Vk api, Device device, Queue queue, uint queueFamilyIndex) + { + return new CommandBufferPool(api, device, queue, queueFamilyIndex); + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/Window.cs b/Ryujinx.Graphics.Vulkan/Window.cs new file mode 100644 index 000000000..668d2adad --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/Window.cs @@ -0,0 +1,431 @@ +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Linq; +using VkFormat = Silk.NET.Vulkan.Format; + +namespace Ryujinx.Graphics.Vulkan +{ + class Window : IWindow, IDisposable + { + private const int SurfaceWidth = 1280; + private const int SurfaceHeight = 720; + + private readonly VulkanGraphicsDevice _gd; + private readonly SurfaceKHR _surface; + private readonly PhysicalDevice _physicalDevice; + private readonly Device _device; + private SwapchainKHR _swapchain; + + private Image[] _swapchainImages; + private Auto[] _swapchainImageViews; + + private Semaphore _imageAvailableSemaphore; + private Semaphore _renderFinishedSemaphore; + + private int _width; + private int _height; + private VkFormat _format; + + internal bool ScreenCaptureRequested { get; set; } + + public unsafe Window(VulkanGraphicsDevice gd, SurfaceKHR surface, PhysicalDevice physicalDevice, Device device) + { + _gd = gd; + _physicalDevice = physicalDevice; + _device = device; + _surface = surface; + + CreateSwapchain(); + + var semaphoreCreateInfo = new SemaphoreCreateInfo() + { + SType = StructureType.SemaphoreCreateInfo + }; + + gd.Api.CreateSemaphore(device, semaphoreCreateInfo, null, out _imageAvailableSemaphore).ThrowOnError(); + gd.Api.CreateSemaphore(device, semaphoreCreateInfo, null, out _renderFinishedSemaphore).ThrowOnError(); + } + + private void RecreateSwapchain() + { + for (int i = 0; i < _swapchainImageViews.Length; i++) + { + _swapchainImageViews[i].Dispose(); + } + + CreateSwapchain(); + } + + private unsafe void CreateSwapchain() + { + _gd.SurfaceApi.GetPhysicalDeviceSurfaceCapabilities(_physicalDevice, _surface, out var capabilities); + + uint surfaceFormatsCount; + + _gd.SurfaceApi.GetPhysicalDeviceSurfaceFormats(_physicalDevice, _surface, &surfaceFormatsCount, null); + + var surfaceFormats = new SurfaceFormatKHR[surfaceFormatsCount]; + + fixed (SurfaceFormatKHR* pSurfaceFormats = surfaceFormats) + { + _gd.SurfaceApi.GetPhysicalDeviceSurfaceFormats(_physicalDevice, _surface, &surfaceFormatsCount, pSurfaceFormats); + } + + uint presentModesCount; + + _gd.SurfaceApi.GetPhysicalDeviceSurfacePresentModes(_physicalDevice, _surface, &presentModesCount, null); + + var presentModes = new PresentModeKHR[presentModesCount]; + + fixed (PresentModeKHR* pPresentModes = presentModes) + { + _gd.SurfaceApi.GetPhysicalDeviceSurfacePresentModes(_physicalDevice, _surface, &presentModesCount, pPresentModes); + } + + uint imageCount = capabilities.MinImageCount + 1; + if (capabilities.MaxImageCount > 0 && imageCount > capabilities.MaxImageCount) + { + imageCount = capabilities.MaxImageCount; + } + + var surfaceFormat = ChooseSwapSurfaceFormat(surfaceFormats); + + var extent = ChooseSwapExtent(capabilities); + + _width = (int)extent.Width; + _height = (int)extent.Height; + _format = surfaceFormat.Format; + + var oldSwapchain = _swapchain; + + var swapchainCreateInfo = new SwapchainCreateInfoKHR() + { + SType = StructureType.SwapchainCreateInfoKhr, + Surface = _surface, + MinImageCount = imageCount, + ImageFormat = surfaceFormat.Format, + ImageColorSpace = surfaceFormat.ColorSpace, + ImageExtent = extent, + ImageUsage = ImageUsageFlags.ImageUsageColorAttachmentBit | ImageUsageFlags.ImageUsageTransferDstBit, + ImageSharingMode = SharingMode.Exclusive, + ImageArrayLayers = 1, + PreTransform = capabilities.CurrentTransform, + CompositeAlpha = CompositeAlphaFlagsKHR.CompositeAlphaOpaqueBitKhr, + PresentMode = ChooseSwapPresentMode(presentModes), + Clipped = true, + OldSwapchain = oldSwapchain + }; + + _gd.SwapchainApi.CreateSwapchain(_device, swapchainCreateInfo, null, out _swapchain).ThrowOnError(); + + _gd.SwapchainApi.GetSwapchainImages(_device, _swapchain, &imageCount, null); + + _swapchainImages = new Image[imageCount]; + + fixed (Image* pSwapchainImages = _swapchainImages) + { + _gd.SwapchainApi.GetSwapchainImages(_device, _swapchain, &imageCount, pSwapchainImages); + } + + _swapchainImageViews = new Auto[imageCount]; + + for (int i = 0; i < imageCount; i++) + { + _swapchainImageViews[i] = CreateSwapchainImageView(_swapchainImages[i], surfaceFormat.Format); + } + } + + private unsafe Auto CreateSwapchainImageView(Image swapchainImage, VkFormat format) + { + var componentMapping = new ComponentMapping( + ComponentSwizzle.R, + ComponentSwizzle.G, + ComponentSwizzle.B, + ComponentSwizzle.A); + + var aspectFlags = ImageAspectFlags.ImageAspectColorBit; + + var subresourceRange = new ImageSubresourceRange(aspectFlags, 0, 1, 0, 1); + + var imageCreateInfo = new ImageViewCreateInfo() + { + SType = StructureType.ImageViewCreateInfo, + Image = swapchainImage, + ViewType = ImageViewType.ImageViewType2D, + Format = format, + Components = componentMapping, + SubresourceRange = subresourceRange + }; + + _gd.Api.CreateImageView(_device, imageCreateInfo, null, out var imageView).ThrowOnError(); + return new Auto(new DisposableImageView(_gd.Api, _device, imageView)); + } + + private static SurfaceFormatKHR ChooseSwapSurfaceFormat(SurfaceFormatKHR[] availableFormats) + { + if (availableFormats.Length == 1 && availableFormats[0].Format == VkFormat.Undefined) + { + return new SurfaceFormatKHR(VkFormat.B8G8R8A8Unorm, ColorSpaceKHR.ColorspaceSrgbNonlinearKhr); + } + + foreach (var format in availableFormats) + { + if (format.Format == VkFormat.B8G8R8A8Unorm && format.ColorSpace == ColorSpaceKHR.ColorspaceSrgbNonlinearKhr) + { + return format; + } + } + + return availableFormats[0]; + } + + private static PresentModeKHR ChooseSwapPresentMode(PresentModeKHR[] availablePresentModes) + { + if (availablePresentModes.Contains(PresentModeKHR.PresentModeImmediateKhr)) + { + return PresentModeKHR.PresentModeImmediateKhr; + } + else if (availablePresentModes.Contains(PresentModeKHR.PresentModeMailboxKhr)) + { + return PresentModeKHR.PresentModeMailboxKhr; + } + else + { + return PresentModeKHR.PresentModeFifoKhr; + } + } + + public static Extent2D ChooseSwapExtent(SurfaceCapabilitiesKHR capabilities) + { + if (capabilities.CurrentExtent.Width != uint.MaxValue) + { + return capabilities.CurrentExtent; + } + else + { + uint width = Math.Max(capabilities.MinImageExtent.Width, Math.Min(capabilities.MaxImageExtent.Width, SurfaceWidth)); + uint height = Math.Max(capabilities.MinImageExtent.Height, Math.Min(capabilities.MaxImageExtent.Height, SurfaceHeight)); + + return new Extent2D(width, height); + } + } + + public unsafe void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback) + { + uint nextImage = 0; + + while (true) + { + var acquireResult = _gd.SwapchainApi.AcquireNextImage( + _device, + _swapchain, + ulong.MaxValue, + _imageAvailableSemaphore, + new Fence(), + ref nextImage); + + if (acquireResult == Result.ErrorOutOfDateKhr || + acquireResult == Result.SuboptimalKhr) + { + RecreateSwapchain(); + } + else + { + acquireResult.ThrowOnError(); + break; + } + } + + var swapchainImage = _swapchainImages[nextImage]; + + _gd.FlushAllCommands(); + + var cbs = _gd.CommandBufferPool.Rent(); + + Transition( + cbs.CommandBuffer, + swapchainImage, + 0, + AccessFlags.AccessTransferWriteBit, + ImageLayout.Undefined, + ImageLayout.General); + + var view = (TextureView)texture; + + int srcX0, srcX1, srcY0, srcY1; + float scale = view.ScaleFactor; + + if (crop.Left == 0 && crop.Right == 0) + { + srcX0 = 0; + srcX1 = (int)(view.Width / scale); + } + else + { + srcX0 = crop.Left; + srcX1 = crop.Right; + } + + if (crop.Top == 0 && crop.Bottom == 0) + { + srcY0 = 0; + srcY1 = (int)(view.Height / scale); + } + else + { + srcY0 = crop.Top; + srcY1 = crop.Bottom; + } + + if (scale != 1f) + { + srcX0 = (int)(srcX0 * scale); + srcY0 = (int)(srcY0 * scale); + srcX1 = (int)Math.Ceiling(srcX1 * scale); + srcY1 = (int)Math.Ceiling(srcY1 * scale); + } + + if (ScreenCaptureRequested) + { + CaptureFrame(view, srcX0, srcY0, srcX1 - srcX0, srcY1 - srcY0, view.Info.Format.IsBgr(), crop.FlipX, crop.FlipY); + + ScreenCaptureRequested = false; + } + + float ratioX = MathF.Min(1f, (_height * (float)SurfaceWidth) / ((float)SurfaceHeight * _width)); + float ratioY = MathF.Min(1f, (_width * (float)SurfaceHeight) / ((float)SurfaceWidth * _height)); + + int dstWidth = (int)(_width * ratioX); + int dstHeight = (int)(_height * ratioY); + + int dstPaddingX = (_width - dstWidth) / 2; + int dstPaddingY = (_height - dstHeight) / 2; + + int dstX0 = crop.FlipX ? _width - dstPaddingX : dstPaddingX; + int dstX1 = crop.FlipX ? dstPaddingX : _width - dstPaddingX; + + int dstY0 = crop.FlipY ? dstPaddingY : _height - dstPaddingY; + int dstY1 = crop.FlipY ? _height - dstPaddingY : dstPaddingY; + + _gd.HelperShader.Blit( + _gd, + cbs, + view, + _swapchainImageViews[nextImage], + _width, + _height, + _format, + new Extents2D(srcX0, srcY0, srcX1, srcY1), + new Extents2D(dstX0, dstY1, dstX1, dstY0), + true, + true); + + Transition( + cbs.CommandBuffer, + swapchainImage, + 0, + 0, + ImageLayout.General, + ImageLayout.PresentSrcKhr); + + _gd.CommandBufferPool.Return( + cbs, + new[] { _imageAvailableSemaphore }, + new[] { PipelineStageFlags.PipelineStageColorAttachmentOutputBit }, + new[] { _renderFinishedSemaphore }); + + // TODO: Present queue. + var semaphore = _renderFinishedSemaphore; + var swapchain = _swapchain; + + Result result; + + var presentInfo = new PresentInfoKHR() + { + SType = StructureType.PresentInfoKhr, + WaitSemaphoreCount = 1, + PWaitSemaphores = &semaphore, + SwapchainCount = 1, + PSwapchains = &swapchain, + PImageIndices = &nextImage, + PResults = &result + }; + + _gd.SwapchainApi.QueuePresent(_gd.Queue, presentInfo); + } + + private unsafe void Transition( + CommandBuffer commandBuffer, + Image image, + AccessFlags srcAccess, + AccessFlags dstAccess, + ImageLayout srcLayout, + ImageLayout dstLayout) + { + var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ImageAspectColorBit, 0, 1, 0, 1); + + var barrier = new ImageMemoryBarrier() + { + SType = StructureType.ImageMemoryBarrier, + SrcAccessMask = srcAccess, + DstAccessMask = dstAccess, + OldLayout = srcLayout, + NewLayout = dstLayout, + SrcQueueFamilyIndex = Vk.QueueFamilyIgnored, + DstQueueFamilyIndex = Vk.QueueFamilyIgnored, + Image = image, + SubresourceRange = subresourceRange + }; + + _gd.Api.CmdPipelineBarrier( + commandBuffer, + PipelineStageFlags.PipelineStageTopOfPipeBit, + PipelineStageFlags.PipelineStageAllCommandsBit, + 0, + 0, + null, + 0, + null, + 1, + barrier); + } + + private void CaptureFrame(TextureView texture, int x, int y, int width, int height, bool isBgra, bool flipX, bool flipY) + { + byte[] bitmap = texture.GetData(x, y, width, height); + + _gd.OnScreenCaptured(new ScreenCaptureImageInfo(width, height, isBgra, bitmap, flipX, flipY)); + } + + public void SetSize(int width, int height) + { + // Not needed as we can get the size from the surface. + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + unsafe + { + _gd.Api.DestroySemaphore(_device, _renderFinishedSemaphore, null); + _gd.Api.DestroySemaphore(_device, _imageAvailableSemaphore, null); + + for (int i = 0; i < _swapchainImageViews.Length; i++) + { + _swapchainImageViews[i].Dispose(); + } + + _gd.SwapchainApi.DestroySwapchain(_device, _swapchain, null); + + } + } + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs b/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs index 664610a43..e57e5e082 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs @@ -32,7 +32,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.Types { if (IsValid()) { - return gpuContext.Synchronization.WaitOnSyncpoint(Id, Value, timeout); + // return gpuContext.Synchronization.WaitOnSyncpoint(Id, Value, timeout); } return false; diff --git a/Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs b/Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs index a69951433..88dcbccfc 100644 --- a/Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs +++ b/Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs @@ -108,14 +108,9 @@ namespace Ryujinx.Headless.SDL2.OpenGL _glLogLevel = glLogLevel; } - protected override string GetGpuVendorName() - { - return ((Renderer)Renderer).GpuVendor; - } - public override SDL_WindowFlags GetWindowFlags() => SDL_WindowFlags.SDL_WINDOW_OPENGL; - protected override void InitializeRenderer() + protected override void InitializeWindowRenderer() { // Ensure to not share this context with other contexts before this point. SetupOpenGLAttributes(false, _glLogLevel); @@ -147,7 +142,9 @@ namespace Ryujinx.Headless.SDL2.OpenGL MouseDriver.SetClientSize(DefaultWidth, DefaultHeight); } - protected override void FinalizeRenderer() + protected override void InitializeRenderer() { } + + protected override void FinalizeWindowRenderer() { // Try to bind the OpenGL context before calling the gpu disposal. _openGLContext.MakeCurrent(); diff --git a/Ryujinx.Headless.SDL2/Options.cs b/Ryujinx.Headless.SDL2/Options.cs index 4c9b83c4d..806fcd71e 100644 --- a/Ryujinx.Headless.SDL2/Options.cs +++ b/Ryujinx.Headless.SDL2/Options.cs @@ -164,6 +164,9 @@ namespace Ryujinx.Headless.SDL2 [Option("graphics-shaders-dump-path", Required = false, HelpText = "Dumps shaders in this local directory. (Developer only)")] public string GraphicsShadersDumpPath { get; set; } + [Option("graphics-backend", Required = false, Default = GraphicsBackend.OpenGl, HelpText = "Change Graphics Backend to use.")] + public GraphicsBackend GraphicsBackend { get; set; } + // Hacks [Option("expand-ram", Required = false, Default = false, HelpText = "Expands the RAM amount on the emulated system from 4GB to 6GB.")] diff --git a/Ryujinx.Headless.SDL2/Program.cs b/Ryujinx.Headless.SDL2/Program.cs index 1d64a8c6c..05eac2e2e 100644 --- a/Ryujinx.Headless.SDL2/Program.cs +++ b/Ryujinx.Headless.SDL2/Program.cs @@ -17,7 +17,9 @@ using Ryujinx.Graphics.GAL.Multithreading; using Ryujinx.Graphics.Gpu; using Ryujinx.Graphics.Gpu.Shader; using Ryujinx.Graphics.OpenGL; +using Ryujinx.Graphics.Vulkan; using Ryujinx.Headless.SDL2.OpenGL; +using Ryujinx.Headless.SDL2.Vulkan; using Ryujinx.HLE; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS; @@ -25,6 +27,7 @@ using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.Input; using Ryujinx.Input.HLE; using Ryujinx.Input.SDL2; +using Silk.NET.Vulkan; using System; using System.Collections.Generic; using System.IO; @@ -403,7 +406,7 @@ namespace Ryujinx.Headless.SDL2 } // Setup graphics configuration - GraphicsConfig.EnableShaderCache = (bool)option.EnableShaderCache; + GraphicsConfig.EnableShaderCache = (bool)option.EnableShaderCache && option.GraphicsBackend != GraphicsBackend.Vulkan; GraphicsConfig.ResScale = option.ResScale; GraphicsConfig.MaxAnisotropy = option.MaxAnisotropy; GraphicsConfig.ShadersDumpPath = option.GraphicsShadersDumpPath; @@ -449,10 +452,29 @@ namespace Ryujinx.Headless.SDL2 Logger.Info?.Print(LogClass.Application, label); } - private static Switch InitializeEmulationContext(WindowBase window, Options options) + private static (WindowBase, IRenderer) CreateRenderer(Options options) { - IRenderer renderer = new Renderer(); + WindowBase window; + IRenderer renderer; + if (options.GraphicsBackend == GraphicsBackend.Vulkan) + { + VulkanWindow vulkanWindow = new VulkanWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, (bool)options.EnableMouse); + window = vulkanWindow; + renderer = new VulkanGraphicsDevice((instance, vk) => new SurfaceKHR((ulong)(vulkanWindow.CreateWindowSurface(instance.Handle))), + vulkanWindow.GetRequiredInstanceExtensions); + } + else + { + window = new OpenGLWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, (bool)options.EnableMouse); + renderer = new Renderer(); + } + + return (window, renderer); + } + + private static Switch InitializeEmulationContext(WindowBase window, IRenderer renderer, Options options) + { BackendThreading threadingMode = options.BackendThreading; bool threadedGAL = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading); @@ -521,8 +543,11 @@ namespace Ryujinx.Headless.SDL2 Logger.RestartTime(); - _window = new OpenGLWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, (bool)options.EnableMouse); - _emulationContext = InitializeEmulationContext(_window, options); + (WindowBase window, IRenderer renderer) = CreateRenderer(options); + + _window = window; + + _emulationContext = InitializeEmulationContext(window, renderer, options); SetupProgressHandler(); diff --git a/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj b/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj index 89e7e81a9..27faf9770 100644 --- a/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj +++ b/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj @@ -16,6 +16,7 @@ + diff --git a/Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs b/Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs new file mode 100644 index 000000000..9a8c91b66 --- /dev/null +++ b/Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs @@ -0,0 +1,91 @@ +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Input.HLE; +using System; +using System.Runtime.InteropServices; +using System.Text; +using static SDL2.SDL; + +namespace Ryujinx.Headless.SDL2.Vulkan +{ + class VulkanWindow : WindowBase + { + private GraphicsDebugLevel _glLogLevel; + + public VulkanWindow(InputManager inputManager, GraphicsDebugLevel glLogLevel, AspectRatio aspectRatio, bool enableMouse) : base(inputManager, glLogLevel, aspectRatio, enableMouse) + { + _glLogLevel = glLogLevel; + } + + public override SDL_WindowFlags GetWindowFlags() => SDL_WindowFlags.SDL_WINDOW_VULKAN; + + protected override void InitializeWindowRenderer() { } + + protected override void InitializeRenderer() + { + Renderer?.Window.SetSize(DefaultWidth, DefaultHeight); + MouseDriver.SetClientSize(DefaultWidth, DefaultHeight); + } + + public unsafe IntPtr CreateWindowSurface(IntPtr instance) + { + if (SDL_Vulkan_CreateSurface(WindowHandle, instance, out ulong surfaceHandle) == SDL_bool.SDL_FALSE) + { + string errorMessage = $"SDL_Vulkan_CreateSurface failed with error \"{SDL_GetError()}\""; + + Logger.Error?.Print(LogClass.Application, errorMessage); + + throw new Exception(errorMessage); + } + + return (IntPtr)surfaceHandle; + } + + private static unsafe string GetStringFromUtf8Byte(byte* start) + { + byte* end = start; + while (*end != 0) end++; + + return Encoding.UTF8.GetString(start, (int)(end - start)); + } + + // TODO: Fix this in SDL2-CS. + [DllImport("SDL2", EntryPoint = "SDL_Vulkan_GetInstanceExtensions", CallingConvention = CallingConvention.Cdecl)] + public static extern SDL_bool SDL_Vulkan_GetInstanceExtensions_Workaround(IntPtr window, out uint count, IntPtr names); + + public unsafe string[] GetRequiredInstanceExtensions() + { + if (SDL_Vulkan_GetInstanceExtensions_Workaround(WindowHandle, out uint extensionsCount, IntPtr.Zero) == SDL_bool.SDL_TRUE) + { + IntPtr[] rawExtensions = new IntPtr[(int)extensionsCount]; + string[] extensions = new string[(int)extensionsCount]; + + fixed (IntPtr* rawExtensionsPtr = rawExtensions) + { + if (SDL_Vulkan_GetInstanceExtensions_Workaround(WindowHandle, out extensionsCount, (IntPtr)rawExtensionsPtr) == SDL_bool.SDL_TRUE) + { + for (int i = 0; i < extensions.Length; i++) + { + extensions[i] = GetStringFromUtf8Byte((byte*)rawExtensions[i]); + } + + return extensions; + } + } + } + + string errorMessage = $"SDL_Vulkan_GetInstanceExtensions failed with error \"{SDL_GetError()}\""; + + Logger.Error?.Print(LogClass.Application, errorMessage); + + throw new Exception(errorMessage); + } + + protected override void FinalizeWindowRenderer() + { + Device.DisposeGpu(); + } + + protected override void SwapBuffers() { } + } +} diff --git a/Ryujinx.Headless.SDL2/WindowBase.cs b/Ryujinx.Headless.SDL2/WindowBase.cs index 58aa8d070..2f25a5850 100644 --- a/Ryujinx.Headless.SDL2/WindowBase.cs +++ b/Ryujinx.Headless.SDL2/WindowBase.cs @@ -151,22 +151,29 @@ namespace Ryujinx.Headless.SDL2 } } + protected abstract void InitializeWindowRenderer(); + protected abstract void InitializeRenderer(); - protected abstract void FinalizeRenderer(); + protected abstract void FinalizeWindowRenderer(); protected abstract void SwapBuffers(object image); - protected abstract string GetGpuVendorName(); - public abstract SDL_WindowFlags GetWindowFlags(); + private string GetGpuVendorName() + { + return Renderer.GetHardwareInfo().GpuVendor; + } + public void Render() { - InitializeRenderer(); + InitializeWindowRenderer(); Device.Gpu.Renderer.Initialize(_glLogLevel); + InitializeRenderer(); + _gpuVendorName = GetGpuVendorName(); Device.Gpu.Renderer.RunLoop(() => @@ -220,7 +227,7 @@ namespace Ryujinx.Headless.SDL2 } }); - FinalizeRenderer(); + FinalizeWindowRenderer(); } public void Exit() diff --git a/Ryujinx.Ui.Common/Configuration/ConfigurationFileFormat.cs b/Ryujinx.Ui.Common/Configuration/ConfigurationFileFormat.cs index 28a2956a6..918a52d95 100644 --- a/Ryujinx.Ui.Common/Configuration/ConfigurationFileFormat.cs +++ b/Ryujinx.Ui.Common/Configuration/ConfigurationFileFormat.cs @@ -310,6 +310,11 @@ namespace Ryujinx.Ui.Common.Configuration /// public List InputConfig { get; set; } + /// + /// Graphics backend + /// + public GraphicsBackend GraphicsBackend { get; set; } + /// /// Loads a configuration file from disk /// diff --git a/Ryujinx.Ui.Common/Configuration/ConfigurationState.cs b/Ryujinx.Ui.Common/Configuration/ConfigurationState.cs index c739b2949..9f011cc63 100644 --- a/Ryujinx.Ui.Common/Configuration/ConfigurationState.cs +++ b/Ryujinx.Ui.Common/Configuration/ConfigurationState.cs @@ -411,6 +411,11 @@ namespace Ryujinx.Ui.Common.Configuration /// public ReactiveObject EnableShaderCache { get; private set; } + /// + /// Graphics backend + /// + public ReactiveObject GraphicsBackend { get; private set; } + public GraphicsSection() { BackendThreading = new ReactiveObject(); @@ -428,6 +433,8 @@ namespace Ryujinx.Ui.Common.Configuration EnableVsync.Event += static (sender, e) => LogValueChange(sender, e, nameof(EnableVsync)); EnableShaderCache = new ReactiveObject(); EnableShaderCache.Event += static (sender, e) => LogValueChange(sender, e, nameof(EnableShaderCache)); + GraphicsBackend = new ReactiveObject(); + GraphicsBackend.Event += static (sender, e) => LogValueChange(sender, e, nameof(GraphicsBackend)); } } @@ -572,6 +579,7 @@ namespace Ryujinx.Ui.Common.Configuration KeyboardConfig = new List(), ControllerConfig = new List(), InputConfig = Hid.InputConfig, + GraphicsBackend = Graphics.GraphicsBackend, }; return configurationFile; @@ -585,6 +593,7 @@ namespace Ryujinx.Ui.Common.Configuration Graphics.ResScaleCustom.Value = 1.0f; Graphics.MaxAnisotropy.Value = -1.0f; Graphics.AspectRatio.Value = AspectRatio.Fixed16x9; + Graphics.GraphicsBackend.Value = GraphicsBackend.OpenGl; Graphics.ShadersDumpPath.Value = ""; Logger.EnableDebug.Value = false; Logger.EnableStub.Value = true; @@ -1103,6 +1112,7 @@ namespace Ryujinx.Ui.Common.Configuration Graphics.AspectRatio.Value = configurationFileFormat.AspectRatio; Graphics.ShadersDumpPath.Value = configurationFileFormat.GraphicsShadersDumpPath; Graphics.BackendThreading.Value = configurationFileFormat.BackendThreading; + Graphics.GraphicsBackend.Value = configurationFileFormat.GraphicsBackend; Logger.EnableDebug.Value = configurationFileFormat.LoggingEnableDebug; Logger.EnableStub.Value = configurationFileFormat.LoggingEnableStub; Logger.EnableInfo.Value = configurationFileFormat.LoggingEnableInfo; diff --git a/Ryujinx.sln b/Ryujinx.sln index 92d4e5b4f..4553bd737 100644 --- a/Ryujinx.sln +++ b/Ryujinx.sln @@ -74,6 +74,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Ui.Common", "Ryujin EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Horizon.Generators", "Ryujinx.Horizon.Generators\Ryujinx.Horizon.Generators.csproj", "{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Vulkan", "Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj", "{D4D09B08-D580-4D69-B886-C35D2853F6C8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spv.Generator", "SpvGen\Spv.Generator.csproj", "{D0720AC9-8AE7-417C-AB39-82CE984045C4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -212,6 +216,14 @@ Global {6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Debug|Any CPU.Build.0 = Debug|Any CPU {6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Release|Any CPU.ActiveCfg = Release|Any CPU {6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Release|Any CPU.Build.0 = Release|Any CPU + {D4D09B08-D580-4D69-B886-C35D2853F6C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D4D09B08-D580-4D69-B886-C35D2853F6C8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D4D09B08-D580-4D69-B886-C35D2853F6C8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D4D09B08-D580-4D69-B886-C35D2853F6C8}.Release|Any CPU.Build.0 = Release|Any CPU + {D0720AC9-8AE7-417C-AB39-82CE984045C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D0720AC9-8AE7-417C-AB39-82CE984045C4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D0720AC9-8AE7-417C-AB39-82CE984045C4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D0720AC9-8AE7-417C-AB39-82CE984045C4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Ryujinx/Ryujinx.csproj b/Ryujinx/Ryujinx.csproj index fc8e33fee..0ecd12a40 100644 --- a/Ryujinx/Ryujinx.csproj +++ b/Ryujinx/Ryujinx.csproj @@ -39,6 +39,7 @@ + diff --git a/Ryujinx/Ui/GLRenderer.cs b/Ryujinx/Ui/GLRenderer.cs index 45eb53cb8..f41ce38c1 100644 --- a/Ryujinx/Ui/GLRenderer.cs +++ b/Ryujinx/Ui/GLRenderer.cs @@ -127,9 +127,9 @@ namespace Ryujinx.Ui _nativeWindow.SwapBuffers(); } - public override string GetGpuVendorName() + protected override string GetGpuBackendName() { - return ((Renderer)Renderer).GpuVendor; + return "OpenGL"; } protected override void Dispose(bool disposing) diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs index afd37d3f0..c779fc19e 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/Ryujinx/Ui/MainWindow.cs @@ -19,6 +19,7 @@ using Ryujinx.Common.System; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL.Multithreading; using Ryujinx.Graphics.OpenGL; +using Ryujinx.Graphics.Vulkan; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS.Services.Account.Acc; @@ -36,6 +37,8 @@ using Ryujinx.Ui.Common.Helper; using Ryujinx.Ui.Helper; using Ryujinx.Ui.Widgets; using Ryujinx.Ui.Windows; +using Silk.NET.Vulkan; +using SPB.Graphics.Vulkan; using System; using System.Diagnostics; using System.IO; @@ -81,8 +84,6 @@ namespace Ryujinx.Ui public bool IsFocused; - private static bool UseVulkan = false; - #pragma warning disable CS0169, CS0649, IDE0044 [GUI] public MenuItem ExitMenuItem; @@ -120,6 +121,7 @@ namespace Ryujinx.Ui [GUI] CheckMenuItem _fileExtToggle; [GUI] CheckMenuItem _pathToggle; [GUI] CheckMenuItem _fileSizeToggle; + [GUI] Label _gpuBackend; [GUI] Label _dockedMode; [GUI] Label _aspectRatio; [GUI] Label _gameStatus; @@ -406,9 +408,10 @@ namespace Ryujinx.Ui IRenderer renderer; - if (UseVulkan) + if (ConfigurationState.Instance.Graphics.GraphicsBackend == GraphicsBackend.Vulkan) { - throw new NotImplementedException(); + renderer = new VulkanGraphicsDevice((instance, vk) => new SurfaceKHR((ulong)((VKRenderer)RendererWidget).CreateWindowSurface(instance.Handle)), + VulkanHelper.GetRequiredInstanceExtensions); } else { @@ -875,7 +878,7 @@ namespace Ryujinx.Ui private RendererWidgetBase CreateRendererWidget() { - if (UseVulkan) + if (ConfigurationState.Instance.Graphics.GraphicsBackend == GraphicsBackend.Vulkan) { return new VKRenderer(InputManager, ConfigurationState.Instance.Logger.GraphicsDebugLevel); } @@ -1034,7 +1037,7 @@ namespace Ryujinx.Ui Graphics.Gpu.GraphicsConfig.ResScale = (resScale == -1) ? resScaleCustom : resScale; Graphics.Gpu.GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy; Graphics.Gpu.GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath; - Graphics.Gpu.GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache; + Graphics.Gpu.GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache && ConfigurationState.Instance.Graphics.GraphicsBackend != GraphicsBackend.Vulkan; } public void SaveConfig() @@ -1125,6 +1128,7 @@ namespace Ryujinx.Ui _gpuName.Text = args.GpuName; _dockedMode.Text = args.DockedMode; _aspectRatio.Text = args.AspectRatio; + _gpuBackend.Text = args.GpuBackend; _volumeStatus.Text = GetVolumeLabelText(args.Volume); if (args.VSyncEnabled) diff --git a/Ryujinx/Ui/MainWindow.glade b/Ryujinx/Ui/MainWindow.glade index b7d93e9bd..d3d748783 100644 --- a/Ryujinx/Ui/MainWindow.glade +++ b/Ryujinx/Ui/MainWindow.glade @@ -774,6 +774,31 @@ 11 + + + True + False + start + 5 + 5 + + + False + True + 10 + + + + + True + False + + + False + True + 11 + + True diff --git a/Ryujinx/Ui/RendererWidgetBase.cs b/Ryujinx/Ui/RendererWidgetBase.cs index 0d82a5c57..1e6099b4f 100644 --- a/Ryujinx/Ui/RendererWidgetBase.cs +++ b/Ryujinx/Ui/RendererWidgetBase.cs @@ -72,6 +72,7 @@ namespace Ryujinx.Ui private InputManager _inputManager; private IKeyboard _keyboardInterface; private GraphicsDebugLevel _glLogLevel; + private string _gpuBackendName; private string _gpuVendorName; private bool _isMouseInClient; @@ -118,7 +119,12 @@ namespace Ryujinx.Ui public abstract void SwapBuffers(object image); - public abstract string GetGpuVendorName(); + protected abstract string GetGpuBackendName(); + + private string GetGpuVendorName() + { + return Renderer.GetHardwareInfo().GpuVendor; + } private void HideCursorStateChanged(object sender, ReactiveEventArgs state) { @@ -225,7 +231,7 @@ namespace Ryujinx.Ui WindowWidth = evnt.Width * monitor.ScaleFactor; WindowHeight = evnt.Height * monitor.ScaleFactor; - Renderer?.Window.SetSize(WindowWidth, WindowHeight); + Renderer?.Window?.SetSize(WindowWidth, WindowHeight); return result; } @@ -306,7 +312,7 @@ namespace Ryujinx.Ui } Renderer = renderer; - Renderer?.Window.SetSize(WindowWidth, WindowHeight); + Renderer?.Window?.SetSize(WindowWidth, WindowHeight); if (Renderer != null) { @@ -385,6 +391,7 @@ namespace Ryujinx.Ui Device.Gpu.Renderer.Initialize(_glLogLevel); + _gpuBackendName = GetGpuBackendName(); _gpuVendorName = GetGpuVendorName(); Device.Gpu.Renderer.RunLoop(() => @@ -430,6 +437,7 @@ namespace Ryujinx.Ui StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs( Device.EnableDeviceVsync, Device.GetVolume(), + _gpuBackendName, dockedMode, ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(), $"Game: {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)", diff --git a/Ryujinx/Ui/StatusUpdatedEventArgs.cs b/Ryujinx/Ui/StatusUpdatedEventArgs.cs index df83efa41..046597b07 100644 --- a/Ryujinx/Ui/StatusUpdatedEventArgs.cs +++ b/Ryujinx/Ui/StatusUpdatedEventArgs.cs @@ -11,11 +11,13 @@ namespace Ryujinx.Ui public string GameStatus; public string FifoStatus; public string GpuName; + public string GpuBackend; - public StatusUpdatedEventArgs(bool vSyncEnabled, float volume, string dockedMode, string aspectRatio, string gameStatus, string fifoStatus, string gpuName) + public StatusUpdatedEventArgs(bool vSyncEnabled, float volume, string gpuBackend, string dockedMode, string aspectRatio, string gameStatus, string fifoStatus, string gpuName) { VSyncEnabled = vSyncEnabled; Volume = volume; + GpuBackend = gpuBackend; DockedMode = dockedMode; AspectRatio = aspectRatio; GameStatus = gameStatus; diff --git a/Ryujinx/Ui/VKRenderer.cs b/Ryujinx/Ui/VKRenderer.cs index f6dd9ec4f..d2f60de84 100644 --- a/Ryujinx/Ui/VKRenderer.cs +++ b/Ryujinx/Ui/VKRenderer.cs @@ -66,9 +66,9 @@ namespace Ryujinx.Ui public override void SwapBuffers(object image) { } - public override string GetGpuVendorName() + protected override string GetGpuBackendName() { - return "Vulkan (Unknown)"; + return "Vulkan"; } protected override void Dispose(bool disposing) diff --git a/Ryujinx/Ui/Windows/SettingsWindow.cs b/Ryujinx/Ui/Windows/SettingsWindow.cs index fef1164b3..5ab9b61a3 100644 --- a/Ryujinx/Ui/Windows/SettingsWindow.cs +++ b/Ryujinx/Ui/Windows/SettingsWindow.cs @@ -91,6 +91,7 @@ namespace Ryujinx.Ui.Windows [GUI] Entry _graphicsShadersDumpPath; [GUI] ComboBoxText _anisotropy; [GUI] ComboBoxText _aspectRatio; + [GUI] ComboBoxText _graphicsBackend; [GUI] ComboBoxText _resScaleCombo; [GUI] Entry _resScaleText; [GUI] ToggleButton _configureController1; @@ -321,6 +322,7 @@ namespace Ryujinx.Ui.Windows _resScaleCombo.SetActiveId(ConfigurationState.Instance.Graphics.ResScale.Value.ToString()); _anisotropy.SetActiveId(ConfigurationState.Instance.Graphics.MaxAnisotropy.Value.ToString()); _aspectRatio.SetActiveId(((int)ConfigurationState.Instance.Graphics.AspectRatio.Value).ToString()); + _graphicsBackend.SetActiveId(((int)ConfigurationState.Instance.Graphics.GraphicsBackend.Value).ToString()); _custThemePath.Buffer.Text = ConfigurationState.Instance.Ui.CustomThemePath; _resScaleText.Buffer.Text = ConfigurationState.Instance.Graphics.ResScaleCustom.Value.ToString(); @@ -528,6 +530,7 @@ namespace Ryujinx.Ui.Windows ConfigurationState.Instance.Graphics.MaxAnisotropy.Value = float.Parse(_anisotropy.ActiveId, CultureInfo.InvariantCulture); ConfigurationState.Instance.Graphics.AspectRatio.Value = Enum.Parse(_aspectRatio.ActiveId); ConfigurationState.Instance.Graphics.BackendThreading.Value = backendThreading; + ConfigurationState.Instance.Graphics.GraphicsBackend.Value = Enum.Parse(_graphicsBackend.ActiveId); ConfigurationState.Instance.Graphics.ResScale.Value = int.Parse(_resScaleCombo.ActiveId); ConfigurationState.Instance.Graphics.ResScaleCustom.Value = resScaleCustom; ConfigurationState.Instance.System.AudioVolume.Value = (float)_audioVolumeSlider.Value / 100.0f; diff --git a/Ryujinx/Ui/Windows/SettingsWindow.glade b/Ryujinx/Ui/Windows/SettingsWindow.glade index c9d19a27f..f573ac709 100644 --- a/Ryujinx/Ui/Windows/SettingsWindow.glade +++ b/Ryujinx/Ui/Windows/SettingsWindow.glade @@ -1900,6 +1900,51 @@ 1 + + + True + False + 5 + 5 + + + True + False + Graphics Backend to use + Graphics Backend: + + + False + True + 5 + 0 + + + + + True + False + Graphics Backend to use + -1 + + Vulkan + OpenGL + + + + False + True + 1 + + + + + False + True + 5 + 1 + + False @@ -2139,7 +2184,7 @@ False True 5 - 0 + 2 @@ -2151,7 +2196,7 @@ False True 5 - 1 + 3 @@ -2525,7 +2570,7 @@ True False Requires appropriate log levels enabled. - OpenGL Log Level + Graphics Backend Log Level False diff --git a/SpvGen/Autogenerated/CoreGrammar.cs b/SpvGen/Autogenerated/CoreGrammar.cs new file mode 100644 index 000000000..5c0284f6a --- /dev/null +++ b/SpvGen/Autogenerated/CoreGrammar.cs @@ -0,0 +1,5075 @@ +// AUTOGENERATED: DO NOT EDIT +// Last update date: 2020-05-20 22:58:10.704809 +#region Grammar License +// Copyright (c) 2014-2020 The Khronos Group Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and/or associated documentation files (the "Materials"), +// to deal in the Materials without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Materials, and to permit persons to whom the +// Materials are furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Materials. +// +// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS +// STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND +// HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ +// +// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS +// IN THE MATERIALS. +#endregion + +using static Spv.Specification; + +namespace Spv.Generator +{ + public partial class Module + { + // Miscellaneous + + public Instruction Nop() + { + Instruction result = new Instruction(Op.OpNop); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Undef(Instruction resultType) + { + Instruction result = new Instruction(Op.OpUndef, GetNewId(), resultType); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SizeOf(Instruction resultType, Instruction pointer) + { + Instruction result = new Instruction(Op.OpSizeOf, GetNewId(), resultType); + + result.AddOperand(pointer); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ExtInst(Instruction resultType, Instruction set, LiteralInteger instruction, params Instruction[] parameters) + { + Instruction result = new Instruction(Op.OpExtInst, GetNewId(), resultType); + + result.AddOperand(set); + result.AddOperand(instruction); + result.AddOperand(parameters); + AddToFunctionDefinitions(result); + + return result; + } + + // Debug + + public Instruction SourceContinued(string continuedSource) + { + Instruction result = new Instruction(Op.OpSourceContinued); + + result.AddOperand(continuedSource); + AddDebug(result); + + return result; + } + + public Instruction Source(SourceLanguage sourceLanguage, LiteralInteger version, Instruction file = null, string source = null) + { + Instruction result = new Instruction(Op.OpSource); + + result.AddOperand(sourceLanguage); + result.AddOperand(version); + if (file != null) + { + result.AddOperand(file); + } + if (source != null) + { + result.AddOperand(source); + } + AddDebug(result); + + return result; + } + + public Instruction SourceExtension(string extension) + { + Instruction result = new Instruction(Op.OpSourceExtension); + + result.AddOperand(extension); + AddDebug(result); + + return result; + } + + public Instruction Name(Instruction target, string name) + { + Instruction result = new Instruction(Op.OpName); + + result.AddOperand(target); + result.AddOperand(name); + AddDebug(result); + + return result; + } + + public Instruction MemberName(Instruction type, LiteralInteger member, string name) + { + Instruction result = new Instruction(Op.OpMemberName); + + result.AddOperand(type); + result.AddOperand(member); + result.AddOperand(name); + AddDebug(result); + + return result; + } + + public Instruction String(string str) + { + Instruction result = new Instruction(Op.OpString, GetNewId()); + + result.AddOperand(str); + AddDebug(result); + + return result; + } + + public Instruction Line(Instruction file, LiteralInteger line, LiteralInteger column) + { + Instruction result = new Instruction(Op.OpLine); + + result.AddOperand(file); + result.AddOperand(line); + result.AddOperand(column); + AddDebug(result); + + return result; + } + + public Instruction NoLine() + { + Instruction result = new Instruction(Op.OpNoLine); + + AddDebug(result); + + return result; + } + + public Instruction ModuleProcessed(string process) + { + Instruction result = new Instruction(Op.OpModuleProcessed); + + result.AddOperand(process); + AddDebug(result); + + return result; + } + + // Annotation + + public Instruction Decorate(Instruction target, Decoration decoration, params Operand[] parameters) + { + Instruction result = new Instruction(Op.OpDecorate); + + result.AddOperand(target); + result.AddOperand(decoration); + result.AddOperand(parameters); + AddAnnotation(result); + + return result; + } + + public Instruction MemberDecorate(Instruction structureType, LiteralInteger member, Decoration decoration, params Operand[] parameters) + { + Instruction result = new Instruction(Op.OpMemberDecorate); + + result.AddOperand(structureType); + result.AddOperand(member); + result.AddOperand(decoration); + result.AddOperand(parameters); + AddAnnotation(result); + + return result; + } + + public Instruction DecorationGroup() + { + Instruction result = new Instruction(Op.OpDecorationGroup, GetNewId()); + + AddAnnotation(result); + + return result; + } + + public Instruction GroupDecorate(Instruction decorationGroup, params Instruction[] targets) + { + Instruction result = new Instruction(Op.OpGroupDecorate); + + result.AddOperand(decorationGroup); + result.AddOperand(targets); + AddAnnotation(result); + + return result; + } + + public Instruction GroupMemberDecorate(Instruction decorationGroup, params Operand[] targets) + { + Instruction result = new Instruction(Op.OpGroupMemberDecorate); + + result.AddOperand(decorationGroup); + result.AddOperand(targets); + AddAnnotation(result); + + return result; + } + + public Instruction DecorateId(Instruction target, Decoration decoration, params Operand[] parameters) + { + Instruction result = new Instruction(Op.OpDecorateId); + + result.AddOperand(target); + result.AddOperand(decoration); + result.AddOperand(parameters); + AddAnnotation(result); + + return result; + } + + public Instruction DecorateString(Instruction target, Decoration decoration, params Operand[] parameters) + { + Instruction result = new Instruction(Op.OpDecorateString); + + result.AddOperand(target); + result.AddOperand(decoration); + result.AddOperand(parameters); + AddAnnotation(result); + + return result; + } + + public Instruction DecorateStringGOOGLE(Instruction target, Decoration decoration, params Operand[] parameters) + { + Instruction result = new Instruction(Op.OpDecorateStringGOOGLE); + + result.AddOperand(target); + result.AddOperand(decoration); + result.AddOperand(parameters); + AddAnnotation(result); + + return result; + } + + public Instruction MemberDecorateString(Instruction structType, LiteralInteger member, Decoration decoration, params Operand[] parameters) + { + Instruction result = new Instruction(Op.OpMemberDecorateString); + + result.AddOperand(structType); + result.AddOperand(member); + result.AddOperand(decoration); + result.AddOperand(parameters); + AddAnnotation(result); + + return result; + } + + public Instruction MemberDecorateStringGOOGLE(Instruction structType, LiteralInteger member, Decoration decoration, params Operand[] parameters) + { + Instruction result = new Instruction(Op.OpMemberDecorateStringGOOGLE); + + result.AddOperand(structType); + result.AddOperand(member); + result.AddOperand(decoration); + result.AddOperand(parameters); + AddAnnotation(result); + + return result; + } + + // Type-Declaration + + public Instruction TypeVoid(bool forceIdAllocation = false) + { + Instruction result = new Instruction(Op.OpTypeVoid); + + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeBool(bool forceIdAllocation = false) + { + Instruction result = new Instruction(Op.OpTypeBool); + + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeInt(LiteralInteger width, LiteralInteger signedness, bool forceIdAllocation = false) + { + Instruction result = new Instruction(Op.OpTypeInt); + + result.AddOperand(width); + result.AddOperand(signedness); + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeFloat(LiteralInteger width, bool forceIdAllocation = false) + { + Instruction result = new Instruction(Op.OpTypeFloat); + + result.AddOperand(width); + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeVector(Instruction componentType, LiteralInteger componentCount, bool forceIdAllocation = false) + { + Instruction result = new Instruction(Op.OpTypeVector); + + result.AddOperand(componentType); + result.AddOperand(componentCount); + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeMatrix(Instruction columnType, LiteralInteger columnCount, bool forceIdAllocation = false) + { + Instruction result = new Instruction(Op.OpTypeMatrix); + + result.AddOperand(columnType); + result.AddOperand(columnCount); + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeImage(Instruction sampledType, Dim dim, LiteralInteger depth, LiteralInteger arrayed, LiteralInteger mS, LiteralInteger sampled, ImageFormat imageFormat, AccessQualifier accessQualifier = (AccessQualifier)int.MaxValue, bool forceIdAllocation = false) + { + Instruction result = new Instruction(Op.OpTypeImage); + + result.AddOperand(sampledType); + result.AddOperand(dim); + result.AddOperand(depth); + result.AddOperand(arrayed); + result.AddOperand(mS); + result.AddOperand(sampled); + result.AddOperand(imageFormat); + if (accessQualifier != (AccessQualifier)int.MaxValue) + { + result.AddOperand(accessQualifier); + } + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeSampler(bool forceIdAllocation = false) + { + Instruction result = new Instruction(Op.OpTypeSampler); + + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeSampledImage(Instruction imageType, bool forceIdAllocation = false) + { + Instruction result = new Instruction(Op.OpTypeSampledImage); + + result.AddOperand(imageType); + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeArray(Instruction elementType, Instruction length, bool forceIdAllocation = false) + { + Instruction result = new Instruction(Op.OpTypeArray); + + result.AddOperand(elementType); + result.AddOperand(length); + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeRuntimeArray(Instruction elementType, bool forceIdAllocation = false) + { + Instruction result = new Instruction(Op.OpTypeRuntimeArray); + + result.AddOperand(elementType); + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeStruct(bool forceIdAllocation, params Instruction[] parameters) + { + Instruction result = new Instruction(Op.OpTypeStruct); + + result.AddOperand(parameters); + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeOpaque(string thenameoftheopaquetype, bool forceIdAllocation = false) + { + Instruction result = new Instruction(Op.OpTypeOpaque); + + result.AddOperand(thenameoftheopaquetype); + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypePointer(StorageClass storageClass, Instruction type, bool forceIdAllocation = false) + { + Instruction result = new Instruction(Op.OpTypePointer); + + result.AddOperand(storageClass); + result.AddOperand(type); + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeFunction(Instruction returnType, bool forceIdAllocation, params Instruction[] parameters) + { + Instruction result = new Instruction(Op.OpTypeFunction); + + result.AddOperand(returnType); + result.AddOperand(parameters); + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeEvent(bool forceIdAllocation = false) + { + Instruction result = new Instruction(Op.OpTypeEvent); + + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeDeviceEvent(bool forceIdAllocation = false) + { + Instruction result = new Instruction(Op.OpTypeDeviceEvent); + + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeReserveId(bool forceIdAllocation = false) + { + Instruction result = new Instruction(Op.OpTypeReserveId); + + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeQueue(bool forceIdAllocation = false) + { + Instruction result = new Instruction(Op.OpTypeQueue); + + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypePipe(AccessQualifier qualifier, bool forceIdAllocation = false) + { + Instruction result = new Instruction(Op.OpTypePipe); + + result.AddOperand(qualifier); + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeForwardPointer(Instruction pointerType, StorageClass storageClass, bool forceIdAllocation = false) + { + Instruction result = new Instruction(Op.OpTypeForwardPointer); + + result.AddOperand(pointerType); + result.AddOperand(storageClass); + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypePipeStorage(bool forceIdAllocation = false) + { + Instruction result = new Instruction(Op.OpTypePipeStorage); + + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeNamedBarrier(bool forceIdAllocation = false) + { + Instruction result = new Instruction(Op.OpTypeNamedBarrier); + + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + // Constant-Creation + + public Instruction ConstantTrue(Instruction resultType) + { + Instruction result = new Instruction(Op.OpConstantTrue, Instruction.InvalidId, resultType); + + AddConstant(result); + + return result; + } + + public Instruction ConstantFalse(Instruction resultType) + { + Instruction result = new Instruction(Op.OpConstantFalse, Instruction.InvalidId, resultType); + + AddConstant(result); + + return result; + } + + public Instruction Constant(Instruction resultType, LiteralInteger value) + { + Instruction result = new Instruction(Op.OpConstant, Instruction.InvalidId, resultType); + + result.AddOperand(value); + AddConstant(result); + + return result; + } + + public Instruction ConstantComposite(Instruction resultType, params Instruction[] constituents) + { + Instruction result = new Instruction(Op.OpConstantComposite, Instruction.InvalidId, resultType); + + result.AddOperand(constituents); + AddConstant(result); + + return result; + } + + public Instruction ConstantSampler(Instruction resultType, SamplerAddressingMode samplerAddressingMode, LiteralInteger param, SamplerFilterMode samplerFilterMode) + { + Instruction result = new Instruction(Op.OpConstantSampler, Instruction.InvalidId, resultType); + + result.AddOperand(samplerAddressingMode); + result.AddOperand(param); + result.AddOperand(samplerFilterMode); + AddConstant(result); + + return result; + } + + public Instruction ConstantNull(Instruction resultType) + { + Instruction result = new Instruction(Op.OpConstantNull, Instruction.InvalidId, resultType); + + AddConstant(result); + + return result; + } + + public Instruction SpecConstantTrue(Instruction resultType) + { + Instruction result = new Instruction(Op.OpSpecConstantTrue, GetNewId(), resultType); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SpecConstantFalse(Instruction resultType) + { + Instruction result = new Instruction(Op.OpSpecConstantFalse, GetNewId(), resultType); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SpecConstant(Instruction resultType, LiteralInteger value) + { + Instruction result = new Instruction(Op.OpSpecConstant, GetNewId(), resultType); + + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SpecConstantComposite(Instruction resultType, params Instruction[] constituents) + { + Instruction result = new Instruction(Op.OpSpecConstantComposite, GetNewId(), resultType); + + result.AddOperand(constituents); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SpecConstantOp(Instruction resultType, LiteralInteger opcode) + { + Instruction result = new Instruction(Op.OpSpecConstantOp, GetNewId(), resultType); + + result.AddOperand(opcode); + AddToFunctionDefinitions(result); + + return result; + } + + // Memory + + public Instruction Variable(Instruction resultType, StorageClass storageClass, Instruction initializer = null) + { + Instruction result = new Instruction(Op.OpVariable, GetNewId(), resultType); + + result.AddOperand(storageClass); + if (initializer != null) + { + result.AddOperand(initializer); + } + return result; + } + + public Instruction ImageTexelPointer(Instruction resultType, Instruction image, Instruction coordinate, Instruction sample) + { + Instruction result = new Instruction(Op.OpImageTexelPointer, GetNewId(), resultType); + + result.AddOperand(image); + result.AddOperand(coordinate); + result.AddOperand(sample); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Load(Instruction resultType, Instruction pointer, MemoryAccessMask memoryAccess = (MemoryAccessMask)int.MaxValue) + { + Instruction result = new Instruction(Op.OpLoad, GetNewId(), resultType); + + result.AddOperand(pointer); + if (memoryAccess != (MemoryAccessMask)int.MaxValue) + { + result.AddOperand(memoryAccess); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Store(Instruction pointer, Instruction obj, MemoryAccessMask memoryAccess = (MemoryAccessMask)int.MaxValue) + { + Instruction result = new Instruction(Op.OpStore); + + result.AddOperand(pointer); + result.AddOperand(obj); + if (memoryAccess != (MemoryAccessMask)int.MaxValue) + { + result.AddOperand(memoryAccess); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CopyMemory(Instruction target, Instruction source, MemoryAccessMask memoryAccess0 = (MemoryAccessMask)int.MaxValue, MemoryAccessMask memoryAccess1 = (MemoryAccessMask)int.MaxValue) + { + Instruction result = new Instruction(Op.OpCopyMemory); + + result.AddOperand(target); + result.AddOperand(source); + if (memoryAccess0 != (MemoryAccessMask)int.MaxValue) + { + result.AddOperand(memoryAccess0); + } + if (memoryAccess1 != (MemoryAccessMask)int.MaxValue) + { + result.AddOperand(memoryAccess1); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CopyMemorySized(Instruction target, Instruction source, Instruction size, MemoryAccessMask memoryAccess0 = (MemoryAccessMask)int.MaxValue, MemoryAccessMask memoryAccess1 = (MemoryAccessMask)int.MaxValue) + { + Instruction result = new Instruction(Op.OpCopyMemorySized); + + result.AddOperand(target); + result.AddOperand(source); + result.AddOperand(size); + if (memoryAccess0 != (MemoryAccessMask)int.MaxValue) + { + result.AddOperand(memoryAccess0); + } + if (memoryAccess1 != (MemoryAccessMask)int.MaxValue) + { + result.AddOperand(memoryAccess1); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AccessChain(Instruction resultType, Instruction baseObj, params Instruction[] indexes) + { + Instruction result = new Instruction(Op.OpAccessChain, GetNewId(), resultType); + + result.AddOperand(baseObj); + result.AddOperand(indexes); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction InBoundsAccessChain(Instruction resultType, Instruction baseObj, params Instruction[] indexes) + { + Instruction result = new Instruction(Op.OpInBoundsAccessChain, GetNewId(), resultType); + + result.AddOperand(baseObj); + result.AddOperand(indexes); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction PtrAccessChain(Instruction resultType, Instruction baseObj, Instruction element, params Instruction[] indexes) + { + Instruction result = new Instruction(Op.OpPtrAccessChain, GetNewId(), resultType); + + result.AddOperand(baseObj); + result.AddOperand(element); + result.AddOperand(indexes); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ArrayLength(Instruction resultType, Instruction structure, LiteralInteger arraymember) + { + Instruction result = new Instruction(Op.OpArrayLength, GetNewId(), resultType); + + result.AddOperand(structure); + result.AddOperand(arraymember); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GenericPtrMemSemantics(Instruction resultType, Instruction pointer) + { + Instruction result = new Instruction(Op.OpGenericPtrMemSemantics, GetNewId(), resultType); + + result.AddOperand(pointer); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction InBoundsPtrAccessChain(Instruction resultType, Instruction baseObj, Instruction element, params Instruction[] indexes) + { + Instruction result = new Instruction(Op.OpInBoundsPtrAccessChain, GetNewId(), resultType); + + result.AddOperand(baseObj); + result.AddOperand(element); + result.AddOperand(indexes); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction PtrEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpPtrEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction PtrNotEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpPtrNotEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction PtrDiff(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpPtrDiff, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + // Function + + public Instruction Function(Instruction resultType, FunctionControlMask functionControl, Instruction functionType) + { + Instruction result = new Instruction(Op.OpFunction, GetNewId(), resultType); + + result.AddOperand(functionControl); + result.AddOperand(functionType); + + return result; + } + + public void AddFunction(Instruction function) + { + AddToFunctionDefinitions(function); + } + + public Instruction FunctionParameter(Instruction resultType) + { + Instruction result = new Instruction(Op.OpFunctionParameter, GetNewId(), resultType); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FunctionEnd() + { + Instruction result = new Instruction(Op.OpFunctionEnd); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FunctionCall(Instruction resultType, Instruction function, params Instruction[] parameters) + { + Instruction result = new Instruction(Op.OpFunctionCall, GetNewId(), resultType); + + result.AddOperand(function); + result.AddOperand(parameters); + AddToFunctionDefinitions(result); + + return result; + } + + // Image + + public Instruction SampledImage(Instruction resultType, Instruction image, Instruction sampler) + { + Instruction result = new Instruction(Op.OpSampledImage, GetNewId(), resultType); + + result.AddOperand(image); + result.AddOperand(sampler); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSampleImplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, ImageOperandsMask imageOperands, params Instruction[] operands) + { + Instruction result = new Instruction(Op.OpImageSampleImplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + if (imageOperands != ImageOperandsMask.MaskNone) + { + result.AddOperand(imageOperands); + result.AddOperand(operands); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSampleExplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, ImageOperandsMask imageOperands, params Instruction[] operands) + { + Instruction result = new Instruction(Op.OpImageSampleExplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(imageOperands); + result.AddOperand(operands); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSampleDrefImplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, Instruction dRef, ImageOperandsMask imageOperands, params Instruction[] operands) + { + Instruction result = new Instruction(Op.OpImageSampleDrefImplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(dRef); + if (imageOperands != ImageOperandsMask.MaskNone) + { + result.AddOperand(imageOperands); + result.AddOperand(operands); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSampleDrefExplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, Instruction dRef, ImageOperandsMask imageOperands, params Instruction[] operands) + { + Instruction result = new Instruction(Op.OpImageSampleDrefExplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(dRef); + result.AddOperand(imageOperands); + result.AddOperand(operands); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSampleProjImplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, ImageOperandsMask imageOperands, params Instruction[] operands) + { + Instruction result = new Instruction(Op.OpImageSampleProjImplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + if (imageOperands != ImageOperandsMask.MaskNone) + { + result.AddOperand(imageOperands); + result.AddOperand(operands); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSampleProjExplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, ImageOperandsMask imageOperands, params Instruction[] operands) + { + Instruction result = new Instruction(Op.OpImageSampleProjExplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(imageOperands); + result.AddOperand(operands); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSampleProjDrefImplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, Instruction dRef, ImageOperandsMask imageOperands, params Instruction[] operands) + { + Instruction result = new Instruction(Op.OpImageSampleProjDrefImplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(dRef); + if (imageOperands != ImageOperandsMask.MaskNone) + { + result.AddOperand(imageOperands); + result.AddOperand(operands); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSampleProjDrefExplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, Instruction dRef, ImageOperandsMask imageOperands, params Instruction[] operands) + { + Instruction result = new Instruction(Op.OpImageSampleProjDrefExplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(dRef); + result.AddOperand(imageOperands); + result.AddOperand(operands); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageFetch(Instruction resultType, Instruction image, Instruction coordinate, ImageOperandsMask imageOperands, params Instruction[] operands) + { + Instruction result = new Instruction(Op.OpImageFetch, GetNewId(), resultType); + + result.AddOperand(image); + result.AddOperand(coordinate); + if (imageOperands != ImageOperandsMask.MaskNone) + { + result.AddOperand(imageOperands); + result.AddOperand(operands); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageGather(Instruction resultType, Instruction sampledImage, Instruction coordinate, Instruction component, ImageOperandsMask imageOperands, params Instruction[] operands) + { + Instruction result = new Instruction(Op.OpImageGather, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(component); + if (imageOperands != ImageOperandsMask.MaskNone) + { + result.AddOperand(imageOperands); + result.AddOperand(operands); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageDrefGather(Instruction resultType, Instruction sampledImage, Instruction coordinate, Instruction dRef, ImageOperandsMask imageOperands, params Instruction[] operands) + { + Instruction result = new Instruction(Op.OpImageDrefGather, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(dRef); + if (imageOperands != ImageOperandsMask.MaskNone) + { + result.AddOperand(imageOperands); + result.AddOperand(operands); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageRead(Instruction resultType, Instruction image, Instruction coordinate, ImageOperandsMask imageOperands = (ImageOperandsMask)int.MaxValue) + { + Instruction result = new Instruction(Op.OpImageRead, GetNewId(), resultType); + + result.AddOperand(image); + result.AddOperand(coordinate); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageWrite(Instruction image, Instruction coordinate, Instruction texel, ImageOperandsMask imageOperands = (ImageOperandsMask)int.MaxValue) + { + Instruction result = new Instruction(Op.OpImageWrite); + + result.AddOperand(image); + result.AddOperand(coordinate); + result.AddOperand(texel); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Image(Instruction resultType, Instruction sampledImage) + { + Instruction result = new Instruction(Op.OpImage, GetNewId(), resultType); + + result.AddOperand(sampledImage); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageQueryFormat(Instruction resultType, Instruction image) + { + Instruction result = new Instruction(Op.OpImageQueryFormat, GetNewId(), resultType); + + result.AddOperand(image); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageQueryOrder(Instruction resultType, Instruction image) + { + Instruction result = new Instruction(Op.OpImageQueryOrder, GetNewId(), resultType); + + result.AddOperand(image); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageQuerySizeLod(Instruction resultType, Instruction image, Instruction levelofDetail) + { + Instruction result = new Instruction(Op.OpImageQuerySizeLod, GetNewId(), resultType); + + result.AddOperand(image); + result.AddOperand(levelofDetail); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageQuerySize(Instruction resultType, Instruction image) + { + Instruction result = new Instruction(Op.OpImageQuerySize, GetNewId(), resultType); + + result.AddOperand(image); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageQueryLod(Instruction resultType, Instruction sampledImage, Instruction coordinate) + { + Instruction result = new Instruction(Op.OpImageQueryLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageQueryLevels(Instruction resultType, Instruction image) + { + Instruction result = new Instruction(Op.OpImageQueryLevels, GetNewId(), resultType); + + result.AddOperand(image); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageQuerySamples(Instruction resultType, Instruction image) + { + Instruction result = new Instruction(Op.OpImageQuerySamples, GetNewId(), resultType); + + result.AddOperand(image); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSparseSampleImplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, ImageOperandsMask imageOperands = (ImageOperandsMask)int.MaxValue) + { + Instruction result = new Instruction(Op.OpImageSparseSampleImplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSparseSampleExplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, ImageOperandsMask imageOperands) + { + Instruction result = new Instruction(Op.OpImageSparseSampleExplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(imageOperands); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSparseSampleDrefImplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, Instruction dRef, ImageOperandsMask imageOperands = (ImageOperandsMask)int.MaxValue) + { + Instruction result = new Instruction(Op.OpImageSparseSampleDrefImplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(dRef); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSparseSampleDrefExplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, Instruction dRef, ImageOperandsMask imageOperands) + { + Instruction result = new Instruction(Op.OpImageSparseSampleDrefExplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(dRef); + result.AddOperand(imageOperands); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSparseSampleProjImplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, ImageOperandsMask imageOperands = (ImageOperandsMask)int.MaxValue) + { + Instruction result = new Instruction(Op.OpImageSparseSampleProjImplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSparseSampleProjExplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, ImageOperandsMask imageOperands) + { + Instruction result = new Instruction(Op.OpImageSparseSampleProjExplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(imageOperands); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSparseSampleProjDrefImplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, Instruction dRef, ImageOperandsMask imageOperands = (ImageOperandsMask)int.MaxValue) + { + Instruction result = new Instruction(Op.OpImageSparseSampleProjDrefImplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(dRef); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSparseSampleProjDrefExplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, Instruction dRef, ImageOperandsMask imageOperands) + { + Instruction result = new Instruction(Op.OpImageSparseSampleProjDrefExplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(dRef); + result.AddOperand(imageOperands); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSparseFetch(Instruction resultType, Instruction image, Instruction coordinate, ImageOperandsMask imageOperands = (ImageOperandsMask)int.MaxValue) + { + Instruction result = new Instruction(Op.OpImageSparseFetch, GetNewId(), resultType); + + result.AddOperand(image); + result.AddOperand(coordinate); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSparseGather(Instruction resultType, Instruction sampledImage, Instruction coordinate, Instruction component, ImageOperandsMask imageOperands = (ImageOperandsMask)int.MaxValue) + { + Instruction result = new Instruction(Op.OpImageSparseGather, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(component); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSparseDrefGather(Instruction resultType, Instruction sampledImage, Instruction coordinate, Instruction dRef, ImageOperandsMask imageOperands = (ImageOperandsMask)int.MaxValue) + { + Instruction result = new Instruction(Op.OpImageSparseDrefGather, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(dRef); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSparseTexelsResident(Instruction resultType, Instruction residentCode) + { + Instruction result = new Instruction(Op.OpImageSparseTexelsResident, GetNewId(), resultType); + + result.AddOperand(residentCode); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSparseRead(Instruction resultType, Instruction image, Instruction coordinate, ImageOperandsMask imageOperands = (ImageOperandsMask)int.MaxValue) + { + Instruction result = new Instruction(Op.OpImageSparseRead, GetNewId(), resultType); + + result.AddOperand(image); + result.AddOperand(coordinate); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSampleFootprintNV(Instruction resultType, Instruction sampledImage, Instruction coordinate, Instruction granularity, Instruction coarse, ImageOperandsMask imageOperands = (ImageOperandsMask)int.MaxValue) + { + Instruction result = new Instruction(Op.OpImageSampleFootprintNV, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(granularity); + result.AddOperand(coarse); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + AddToFunctionDefinitions(result); + + return result; + } + + // Conversion + + public Instruction ConvertFToU(Instruction resultType, Instruction floatValue) + { + Instruction result = new Instruction(Op.OpConvertFToU, GetNewId(), resultType); + + result.AddOperand(floatValue); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ConvertFToS(Instruction resultType, Instruction floatValue) + { + Instruction result = new Instruction(Op.OpConvertFToS, GetNewId(), resultType); + + result.AddOperand(floatValue); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ConvertSToF(Instruction resultType, Instruction signedValue) + { + Instruction result = new Instruction(Op.OpConvertSToF, GetNewId(), resultType); + + result.AddOperand(signedValue); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ConvertUToF(Instruction resultType, Instruction unsignedValue) + { + Instruction result = new Instruction(Op.OpConvertUToF, GetNewId(), resultType); + + result.AddOperand(unsignedValue); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction UConvert(Instruction resultType, Instruction unsignedValue) + { + Instruction result = new Instruction(Op.OpUConvert, GetNewId(), resultType); + + result.AddOperand(unsignedValue); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SConvert(Instruction resultType, Instruction signedValue) + { + Instruction result = new Instruction(Op.OpSConvert, GetNewId(), resultType); + + result.AddOperand(signedValue); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FConvert(Instruction resultType, Instruction floatValue) + { + Instruction result = new Instruction(Op.OpFConvert, GetNewId(), resultType); + + result.AddOperand(floatValue); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction QuantizeToF16(Instruction resultType, Instruction value) + { + Instruction result = new Instruction(Op.OpQuantizeToF16, GetNewId(), resultType); + + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ConvertPtrToU(Instruction resultType, Instruction pointer) + { + Instruction result = new Instruction(Op.OpConvertPtrToU, GetNewId(), resultType); + + result.AddOperand(pointer); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SatConvertSToU(Instruction resultType, Instruction signedValue) + { + Instruction result = new Instruction(Op.OpSatConvertSToU, GetNewId(), resultType); + + result.AddOperand(signedValue); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SatConvertUToS(Instruction resultType, Instruction unsignedValue) + { + Instruction result = new Instruction(Op.OpSatConvertUToS, GetNewId(), resultType); + + result.AddOperand(unsignedValue); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ConvertUToPtr(Instruction resultType, Instruction integerValue) + { + Instruction result = new Instruction(Op.OpConvertUToPtr, GetNewId(), resultType); + + result.AddOperand(integerValue); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction PtrCastToGeneric(Instruction resultType, Instruction pointer) + { + Instruction result = new Instruction(Op.OpPtrCastToGeneric, GetNewId(), resultType); + + result.AddOperand(pointer); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GenericCastToPtr(Instruction resultType, Instruction pointer) + { + Instruction result = new Instruction(Op.OpGenericCastToPtr, GetNewId(), resultType); + + result.AddOperand(pointer); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GenericCastToPtrExplicit(Instruction resultType, Instruction pointer, StorageClass storage) + { + Instruction result = new Instruction(Op.OpGenericCastToPtrExplicit, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(storage); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Bitcast(Instruction resultType, Instruction operand) + { + Instruction result = new Instruction(Op.OpBitcast, GetNewId(), resultType); + + result.AddOperand(operand); + AddToFunctionDefinitions(result); + + return result; + } + + // Composite + + public Instruction VectorExtractDynamic(Instruction resultType, Instruction vector, Instruction index) + { + Instruction result = new Instruction(Op.OpVectorExtractDynamic, GetNewId(), resultType); + + result.AddOperand(vector); + result.AddOperand(index); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction VectorInsertDynamic(Instruction resultType, Instruction vector, Instruction component, Instruction index) + { + Instruction result = new Instruction(Op.OpVectorInsertDynamic, GetNewId(), resultType); + + result.AddOperand(vector); + result.AddOperand(component); + result.AddOperand(index); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction VectorShuffle(Instruction resultType, Instruction vector1, Instruction vector2, params LiteralInteger[] components) + { + Instruction result = new Instruction(Op.OpVectorShuffle, GetNewId(), resultType); + + result.AddOperand(vector1); + result.AddOperand(vector2); + result.AddOperand(components); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CompositeConstruct(Instruction resultType, params Instruction[] constituents) + { + Instruction result = new Instruction(Op.OpCompositeConstruct, GetNewId(), resultType); + + result.AddOperand(constituents); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CompositeExtract(Instruction resultType, Instruction composite, params LiteralInteger[] indexes) + { + Instruction result = new Instruction(Op.OpCompositeExtract, GetNewId(), resultType); + + result.AddOperand(composite); + result.AddOperand(indexes); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CompositeInsert(Instruction resultType, Instruction obj, Instruction composite, params LiteralInteger[] indexes) + { + Instruction result = new Instruction(Op.OpCompositeInsert, GetNewId(), resultType); + + result.AddOperand(obj); + result.AddOperand(composite); + result.AddOperand(indexes); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CopyObject(Instruction resultType, Instruction operand) + { + Instruction result = new Instruction(Op.OpCopyObject, GetNewId(), resultType); + + result.AddOperand(operand); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Transpose(Instruction resultType, Instruction matrix) + { + Instruction result = new Instruction(Op.OpTranspose, GetNewId(), resultType); + + result.AddOperand(matrix); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CopyLogical(Instruction resultType, Instruction operand) + { + Instruction result = new Instruction(Op.OpCopyLogical, GetNewId(), resultType); + + result.AddOperand(operand); + AddToFunctionDefinitions(result); + + return result; + } + + // Arithmetic + + public Instruction SNegate(Instruction resultType, Instruction operand) + { + Instruction result = new Instruction(Op.OpSNegate, GetNewId(), resultType); + + result.AddOperand(operand); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FNegate(Instruction resultType, Instruction operand) + { + Instruction result = new Instruction(Op.OpFNegate, GetNewId(), resultType); + + result.AddOperand(operand); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IAdd(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpIAdd, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FAdd(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpFAdd, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ISub(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpISub, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FSub(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpFSub, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IMul(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpIMul, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FMul(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpFMul, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction UDiv(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpUDiv, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SDiv(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpSDiv, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FDiv(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpFDiv, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction UMod(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpUMod, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SRem(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpSRem, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SMod(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpSMod, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FRem(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpFRem, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FMod(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpFMod, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction VectorTimesScalar(Instruction resultType, Instruction vector, Instruction scalar) + { + Instruction result = new Instruction(Op.OpVectorTimesScalar, GetNewId(), resultType); + + result.AddOperand(vector); + result.AddOperand(scalar); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction MatrixTimesScalar(Instruction resultType, Instruction matrix, Instruction scalar) + { + Instruction result = new Instruction(Op.OpMatrixTimesScalar, GetNewId(), resultType); + + result.AddOperand(matrix); + result.AddOperand(scalar); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction VectorTimesMatrix(Instruction resultType, Instruction vector, Instruction matrix) + { + Instruction result = new Instruction(Op.OpVectorTimesMatrix, GetNewId(), resultType); + + result.AddOperand(vector); + result.AddOperand(matrix); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction MatrixTimesVector(Instruction resultType, Instruction matrix, Instruction vector) + { + Instruction result = new Instruction(Op.OpMatrixTimesVector, GetNewId(), resultType); + + result.AddOperand(matrix); + result.AddOperand(vector); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction MatrixTimesMatrix(Instruction resultType, Instruction leftMatrix, Instruction rightMatrix) + { + Instruction result = new Instruction(Op.OpMatrixTimesMatrix, GetNewId(), resultType); + + result.AddOperand(leftMatrix); + result.AddOperand(rightMatrix); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction OuterProduct(Instruction resultType, Instruction vector1, Instruction vector2) + { + Instruction result = new Instruction(Op.OpOuterProduct, GetNewId(), resultType); + + result.AddOperand(vector1); + result.AddOperand(vector2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Dot(Instruction resultType, Instruction vector1, Instruction vector2) + { + Instruction result = new Instruction(Op.OpDot, GetNewId(), resultType); + + result.AddOperand(vector1); + result.AddOperand(vector2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IAddCarry(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpIAddCarry, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ISubBorrow(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpISubBorrow, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction UMulExtended(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpUMulExtended, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SMulExtended(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpSMulExtended, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + // Bit + + public Instruction ShiftRightLogical(Instruction resultType, Instruction baseObj, Instruction shift) + { + Instruction result = new Instruction(Op.OpShiftRightLogical, GetNewId(), resultType); + + result.AddOperand(baseObj); + result.AddOperand(shift); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ShiftRightArithmetic(Instruction resultType, Instruction baseObj, Instruction shift) + { + Instruction result = new Instruction(Op.OpShiftRightArithmetic, GetNewId(), resultType); + + result.AddOperand(baseObj); + result.AddOperand(shift); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ShiftLeftLogical(Instruction resultType, Instruction baseObj, Instruction shift) + { + Instruction result = new Instruction(Op.OpShiftLeftLogical, GetNewId(), resultType); + + result.AddOperand(baseObj); + result.AddOperand(shift); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction BitwiseOr(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpBitwiseOr, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction BitwiseXor(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpBitwiseXor, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction BitwiseAnd(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpBitwiseAnd, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Not(Instruction resultType, Instruction operand) + { + Instruction result = new Instruction(Op.OpNot, GetNewId(), resultType); + + result.AddOperand(operand); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction BitFieldInsert(Instruction resultType, Instruction baseObj, Instruction insert, Instruction offset, Instruction count) + { + Instruction result = new Instruction(Op.OpBitFieldInsert, GetNewId(), resultType); + + result.AddOperand(baseObj); + result.AddOperand(insert); + result.AddOperand(offset); + result.AddOperand(count); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction BitFieldSExtract(Instruction resultType, Instruction baseObj, Instruction offset, Instruction count) + { + Instruction result = new Instruction(Op.OpBitFieldSExtract, GetNewId(), resultType); + + result.AddOperand(baseObj); + result.AddOperand(offset); + result.AddOperand(count); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction BitFieldUExtract(Instruction resultType, Instruction baseObj, Instruction offset, Instruction count) + { + Instruction result = new Instruction(Op.OpBitFieldUExtract, GetNewId(), resultType); + + result.AddOperand(baseObj); + result.AddOperand(offset); + result.AddOperand(count); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction BitReverse(Instruction resultType, Instruction baseObj) + { + Instruction result = new Instruction(Op.OpBitReverse, GetNewId(), resultType); + + result.AddOperand(baseObj); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction BitCount(Instruction resultType, Instruction baseObj) + { + Instruction result = new Instruction(Op.OpBitCount, GetNewId(), resultType); + + result.AddOperand(baseObj); + AddToFunctionDefinitions(result); + + return result; + } + + // Relational_and_Logical + + public Instruction Any(Instruction resultType, Instruction vector) + { + Instruction result = new Instruction(Op.OpAny, GetNewId(), resultType); + + result.AddOperand(vector); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction All(Instruction resultType, Instruction vector) + { + Instruction result = new Instruction(Op.OpAll, GetNewId(), resultType); + + result.AddOperand(vector); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IsNan(Instruction resultType, Instruction x) + { + Instruction result = new Instruction(Op.OpIsNan, GetNewId(), resultType); + + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IsInf(Instruction resultType, Instruction x) + { + Instruction result = new Instruction(Op.OpIsInf, GetNewId(), resultType); + + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IsFinite(Instruction resultType, Instruction x) + { + Instruction result = new Instruction(Op.OpIsFinite, GetNewId(), resultType); + + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IsNormal(Instruction resultType, Instruction x) + { + Instruction result = new Instruction(Op.OpIsNormal, GetNewId(), resultType); + + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SignBitSet(Instruction resultType, Instruction x) + { + Instruction result = new Instruction(Op.OpSignBitSet, GetNewId(), resultType); + + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction LessOrGreater(Instruction resultType, Instruction x, Instruction y) + { + Instruction result = new Instruction(Op.OpLessOrGreater, GetNewId(), resultType); + + result.AddOperand(x); + result.AddOperand(y); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Ordered(Instruction resultType, Instruction x, Instruction y) + { + Instruction result = new Instruction(Op.OpOrdered, GetNewId(), resultType); + + result.AddOperand(x); + result.AddOperand(y); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Unordered(Instruction resultType, Instruction x, Instruction y) + { + Instruction result = new Instruction(Op.OpUnordered, GetNewId(), resultType); + + result.AddOperand(x); + result.AddOperand(y); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction LogicalEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpLogicalEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction LogicalNotEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpLogicalNotEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction LogicalOr(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpLogicalOr, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction LogicalAnd(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpLogicalAnd, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction LogicalNot(Instruction resultType, Instruction operand) + { + Instruction result = new Instruction(Op.OpLogicalNot, GetNewId(), resultType); + + result.AddOperand(operand); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Select(Instruction resultType, Instruction condition, Instruction object1, Instruction object2) + { + Instruction result = new Instruction(Op.OpSelect, GetNewId(), resultType); + + result.AddOperand(condition); + result.AddOperand(object1); + result.AddOperand(object2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpIEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction INotEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpINotEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction UGreaterThan(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpUGreaterThan, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SGreaterThan(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpSGreaterThan, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction UGreaterThanEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpUGreaterThanEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SGreaterThanEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpSGreaterThanEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ULessThan(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpULessThan, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SLessThan(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpSLessThan, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ULessThanEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpULessThanEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SLessThanEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpSLessThanEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FOrdEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpFOrdEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FUnordEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpFUnordEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FOrdNotEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpFOrdNotEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FUnordNotEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpFUnordNotEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FOrdLessThan(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpFOrdLessThan, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FUnordLessThan(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpFUnordLessThan, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FOrdGreaterThan(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpFOrdGreaterThan, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FUnordGreaterThan(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpFUnordGreaterThan, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FOrdLessThanEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpFOrdLessThanEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FUnordLessThanEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpFUnordLessThanEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FOrdGreaterThanEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpFOrdGreaterThanEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FUnordGreaterThanEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpFUnordGreaterThanEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + // Derivative + + public Instruction DPdx(Instruction resultType, Instruction p) + { + Instruction result = new Instruction(Op.OpDPdx, GetNewId(), resultType); + + result.AddOperand(p); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction DPdy(Instruction resultType, Instruction p) + { + Instruction result = new Instruction(Op.OpDPdy, GetNewId(), resultType); + + result.AddOperand(p); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Fwidth(Instruction resultType, Instruction p) + { + Instruction result = new Instruction(Op.OpFwidth, GetNewId(), resultType); + + result.AddOperand(p); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction DPdxFine(Instruction resultType, Instruction p) + { + Instruction result = new Instruction(Op.OpDPdxFine, GetNewId(), resultType); + + result.AddOperand(p); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction DPdyFine(Instruction resultType, Instruction p) + { + Instruction result = new Instruction(Op.OpDPdyFine, GetNewId(), resultType); + + result.AddOperand(p); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FwidthFine(Instruction resultType, Instruction p) + { + Instruction result = new Instruction(Op.OpFwidthFine, GetNewId(), resultType); + + result.AddOperand(p); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction DPdxCoarse(Instruction resultType, Instruction p) + { + Instruction result = new Instruction(Op.OpDPdxCoarse, GetNewId(), resultType); + + result.AddOperand(p); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction DPdyCoarse(Instruction resultType, Instruction p) + { + Instruction result = new Instruction(Op.OpDPdyCoarse, GetNewId(), resultType); + + result.AddOperand(p); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FwidthCoarse(Instruction resultType, Instruction p) + { + Instruction result = new Instruction(Op.OpFwidthCoarse, GetNewId(), resultType); + + result.AddOperand(p); + AddToFunctionDefinitions(result); + + return result; + } + + // Control-Flow + + public Instruction Phi(Instruction resultType, params Instruction[] parameters) + { + Instruction result = new Instruction(Op.OpPhi, GetNewId(), resultType); + + result.AddOperand(parameters); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction LoopMerge(Instruction mergeBlock, Instruction continueTarget, LoopControlMask loopControl) + { + Instruction result = new Instruction(Op.OpLoopMerge); + + result.AddOperand(mergeBlock); + result.AddOperand(continueTarget); + result.AddOperand(loopControl); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SelectionMerge(Instruction mergeBlock, SelectionControlMask selectionControl) + { + Instruction result = new Instruction(Op.OpSelectionMerge); + + result.AddOperand(mergeBlock); + result.AddOperand(selectionControl); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Label() + { + Instruction result = new Instruction(Op.OpLabel); + + return result; + } + + public Instruction Branch(Instruction targetLabel) + { + Instruction result = new Instruction(Op.OpBranch); + + result.AddOperand(targetLabel); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction BranchConditional(Instruction condition, Instruction trueLabel, Instruction falseLabel, params LiteralInteger[] branchweights) + { + Instruction result = new Instruction(Op.OpBranchConditional); + + result.AddOperand(condition); + result.AddOperand(trueLabel); + result.AddOperand(falseLabel); + result.AddOperand(branchweights); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Switch(Instruction selector, Instruction defaultObj, params Operand[] target) + { + Instruction result = new Instruction(Op.OpSwitch); + + result.AddOperand(selector); + result.AddOperand(defaultObj); + result.AddOperand(target); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Kill() + { + Instruction result = new Instruction(Op.OpKill); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Return() + { + Instruction result = new Instruction(Op.OpReturn); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ReturnValue(Instruction value) + { + Instruction result = new Instruction(Op.OpReturnValue); + + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Unreachable() + { + Instruction result = new Instruction(Op.OpUnreachable); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction LifetimeStart(Instruction pointer, LiteralInteger size) + { + Instruction result = new Instruction(Op.OpLifetimeStart); + + result.AddOperand(pointer); + result.AddOperand(size); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction LifetimeStop(Instruction pointer, LiteralInteger size) + { + Instruction result = new Instruction(Op.OpLifetimeStop); + + result.AddOperand(pointer); + result.AddOperand(size); + AddToFunctionDefinitions(result); + + return result; + } + + // Atomic + + public Instruction AtomicLoad(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics) + { + Instruction result = new Instruction(Op.OpAtomicLoad, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicStore(Instruction pointer, Instruction memory, Instruction semantics, Instruction value) + { + Instruction result = new Instruction(Op.OpAtomicStore); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicExchange(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics, Instruction value) + { + Instruction result = new Instruction(Op.OpAtomicExchange, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicCompareExchange(Instruction resultType, Instruction pointer, Instruction memory, Instruction equal, Instruction unequal, Instruction value, Instruction comparator) + { + Instruction result = new Instruction(Op.OpAtomicCompareExchange, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(equal); + result.AddOperand(unequal); + result.AddOperand(value); + result.AddOperand(comparator); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicCompareExchangeWeak(Instruction resultType, Instruction pointer, Instruction memory, Instruction equal, Instruction unequal, Instruction value, Instruction comparator) + { + Instruction result = new Instruction(Op.OpAtomicCompareExchangeWeak, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(equal); + result.AddOperand(unequal); + result.AddOperand(value); + result.AddOperand(comparator); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicIIncrement(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics) + { + Instruction result = new Instruction(Op.OpAtomicIIncrement, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicIDecrement(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics) + { + Instruction result = new Instruction(Op.OpAtomicIDecrement, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicIAdd(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics, Instruction value) + { + Instruction result = new Instruction(Op.OpAtomicIAdd, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicISub(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics, Instruction value) + { + Instruction result = new Instruction(Op.OpAtomicISub, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicSMin(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics, Instruction value) + { + Instruction result = new Instruction(Op.OpAtomicSMin, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicUMin(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics, Instruction value) + { + Instruction result = new Instruction(Op.OpAtomicUMin, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicSMax(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics, Instruction value) + { + Instruction result = new Instruction(Op.OpAtomicSMax, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicUMax(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics, Instruction value) + { + Instruction result = new Instruction(Op.OpAtomicUMax, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicAnd(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics, Instruction value) + { + Instruction result = new Instruction(Op.OpAtomicAnd, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicOr(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics, Instruction value) + { + Instruction result = new Instruction(Op.OpAtomicOr, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicXor(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics, Instruction value) + { + Instruction result = new Instruction(Op.OpAtomicXor, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicFlagTestAndSet(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics) + { + Instruction result = new Instruction(Op.OpAtomicFlagTestAndSet, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicFlagClear(Instruction pointer, Instruction memory, Instruction semantics) + { + Instruction result = new Instruction(Op.OpAtomicFlagClear); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + AddToFunctionDefinitions(result); + + return result; + } + + // Primitive + + public Instruction EmitVertex() + { + Instruction result = new Instruction(Op.OpEmitVertex); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction EndPrimitive() + { + Instruction result = new Instruction(Op.OpEndPrimitive); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction EmitStreamVertex(Instruction stream) + { + Instruction result = new Instruction(Op.OpEmitStreamVertex); + + result.AddOperand(stream); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction EndStreamPrimitive(Instruction stream) + { + Instruction result = new Instruction(Op.OpEndStreamPrimitive); + + result.AddOperand(stream); + AddToFunctionDefinitions(result); + + return result; + } + + // Barrier + + public Instruction ControlBarrier(Instruction execution, Instruction memory, Instruction semantics) + { + Instruction result = new Instruction(Op.OpControlBarrier); + + result.AddOperand(execution); + result.AddOperand(memory); + result.AddOperand(semantics); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction MemoryBarrier(Instruction memory, Instruction semantics) + { + Instruction result = new Instruction(Op.OpMemoryBarrier); + + result.AddOperand(memory); + result.AddOperand(semantics); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction NamedBarrierInitialize(Instruction resultType, Instruction subgroupCount) + { + Instruction result = new Instruction(Op.OpNamedBarrierInitialize, GetNewId(), resultType); + + result.AddOperand(subgroupCount); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction MemoryNamedBarrier(Instruction namedBarrier, Instruction memory, Instruction semantics) + { + Instruction result = new Instruction(Op.OpMemoryNamedBarrier); + + result.AddOperand(namedBarrier); + result.AddOperand(memory); + result.AddOperand(semantics); + AddToFunctionDefinitions(result); + + return result; + } + + // Group + + public Instruction GroupAsyncCopy(Instruction resultType, Instruction execution, Instruction destination, Instruction source, Instruction numElements, Instruction stride, Instruction eventObj) + { + Instruction result = new Instruction(Op.OpGroupAsyncCopy, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(destination); + result.AddOperand(source); + result.AddOperand(numElements); + result.AddOperand(stride); + result.AddOperand(eventObj); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupWaitEvents(Instruction execution, Instruction numEvents, Instruction eventsList) + { + Instruction result = new Instruction(Op.OpGroupWaitEvents); + + result.AddOperand(execution); + result.AddOperand(numEvents); + result.AddOperand(eventsList); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupAll(Instruction resultType, Instruction execution, Instruction predicate) + { + Instruction result = new Instruction(Op.OpGroupAll, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(predicate); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupAny(Instruction resultType, Instruction execution, Instruction predicate) + { + Instruction result = new Instruction(Op.OpGroupAny, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(predicate); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupBroadcast(Instruction resultType, Instruction execution, Instruction value, Instruction localId) + { + Instruction result = new Instruction(Op.OpGroupBroadcast, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(value); + result.AddOperand(localId); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupIAdd(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = new Instruction(Op.OpGroupIAdd, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupFAdd(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = new Instruction(Op.OpGroupFAdd, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupFMin(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = new Instruction(Op.OpGroupFMin, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupUMin(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = new Instruction(Op.OpGroupUMin, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupSMin(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = new Instruction(Op.OpGroupSMin, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupFMax(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = new Instruction(Op.OpGroupFMax, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupUMax(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = new Instruction(Op.OpGroupUMax, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupSMax(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = new Instruction(Op.OpGroupSMax, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupBallotKHR(Instruction resultType, Instruction predicate) + { + Instruction result = new Instruction(Op.OpSubgroupBallotKHR, GetNewId(), resultType); + + result.AddOperand(predicate); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupFirstInvocationKHR(Instruction resultType, Instruction value) + { + Instruction result = new Instruction(Op.OpSubgroupFirstInvocationKHR, GetNewId(), resultType); + + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupAllKHR(Instruction resultType, Instruction predicate) + { + Instruction result = new Instruction(Op.OpSubgroupAllKHR, GetNewId(), resultType); + + result.AddOperand(predicate); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupAnyKHR(Instruction resultType, Instruction predicate) + { + Instruction result = new Instruction(Op.OpSubgroupAnyKHR, GetNewId(), resultType); + + result.AddOperand(predicate); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupAllEqualKHR(Instruction resultType, Instruction predicate) + { + Instruction result = new Instruction(Op.OpSubgroupAllEqualKHR, GetNewId(), resultType); + + result.AddOperand(predicate); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupReadInvocationKHR(Instruction resultType, Instruction value, Instruction index) + { + Instruction result = new Instruction(Op.OpSubgroupReadInvocationKHR, GetNewId(), resultType); + + result.AddOperand(value); + result.AddOperand(index); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupIAddNonUniformAMD(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = new Instruction(Op.OpGroupIAddNonUniformAMD, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupFAddNonUniformAMD(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = new Instruction(Op.OpGroupFAddNonUniformAMD, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupFMinNonUniformAMD(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = new Instruction(Op.OpGroupFMinNonUniformAMD, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupUMinNonUniformAMD(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = new Instruction(Op.OpGroupUMinNonUniformAMD, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupSMinNonUniformAMD(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = new Instruction(Op.OpGroupSMinNonUniformAMD, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupFMaxNonUniformAMD(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = new Instruction(Op.OpGroupFMaxNonUniformAMD, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupUMaxNonUniformAMD(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = new Instruction(Op.OpGroupUMaxNonUniformAMD, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupSMaxNonUniformAMD(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = new Instruction(Op.OpGroupSMaxNonUniformAMD, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupShuffleINTEL(Instruction resultType, Instruction data, Instruction invocationId) + { + Instruction result = new Instruction(Op.OpSubgroupShuffleINTEL, GetNewId(), resultType); + + result.AddOperand(data); + result.AddOperand(invocationId); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupShuffleDownINTEL(Instruction resultType, Instruction current, Instruction next, Instruction delta) + { + Instruction result = new Instruction(Op.OpSubgroupShuffleDownINTEL, GetNewId(), resultType); + + result.AddOperand(current); + result.AddOperand(next); + result.AddOperand(delta); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupShuffleUpINTEL(Instruction resultType, Instruction previous, Instruction current, Instruction delta) + { + Instruction result = new Instruction(Op.OpSubgroupShuffleUpINTEL, GetNewId(), resultType); + + result.AddOperand(previous); + result.AddOperand(current); + result.AddOperand(delta); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupShuffleXorINTEL(Instruction resultType, Instruction data, Instruction value) + { + Instruction result = new Instruction(Op.OpSubgroupShuffleXorINTEL, GetNewId(), resultType); + + result.AddOperand(data); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupBlockReadINTEL(Instruction resultType, Instruction ptr) + { + Instruction result = new Instruction(Op.OpSubgroupBlockReadINTEL, GetNewId(), resultType); + + result.AddOperand(ptr); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupBlockWriteINTEL(Instruction ptr, Instruction data) + { + Instruction result = new Instruction(Op.OpSubgroupBlockWriteINTEL); + + result.AddOperand(ptr); + result.AddOperand(data); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupImageBlockReadINTEL(Instruction resultType, Instruction image, Instruction coordinate) + { + Instruction result = new Instruction(Op.OpSubgroupImageBlockReadINTEL, GetNewId(), resultType); + + result.AddOperand(image); + result.AddOperand(coordinate); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupImageBlockWriteINTEL(Instruction image, Instruction coordinate, Instruction data) + { + Instruction result = new Instruction(Op.OpSubgroupImageBlockWriteINTEL); + + result.AddOperand(image); + result.AddOperand(coordinate); + result.AddOperand(data); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupImageMediaBlockReadINTEL(Instruction resultType, Instruction image, Instruction coordinate, Instruction width, Instruction height) + { + Instruction result = new Instruction(Op.OpSubgroupImageMediaBlockReadINTEL, GetNewId(), resultType); + + result.AddOperand(image); + result.AddOperand(coordinate); + result.AddOperand(width); + result.AddOperand(height); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupImageMediaBlockWriteINTEL(Instruction image, Instruction coordinate, Instruction width, Instruction height, Instruction data) + { + Instruction result = new Instruction(Op.OpSubgroupImageMediaBlockWriteINTEL); + + result.AddOperand(image); + result.AddOperand(coordinate); + result.AddOperand(width); + result.AddOperand(height); + result.AddOperand(data); + AddToFunctionDefinitions(result); + + return result; + } + + // Device-Side_Enqueue + + public Instruction EnqueueMarker(Instruction resultType, Instruction queue, Instruction numEvents, Instruction waitEvents, Instruction retEvent) + { + Instruction result = new Instruction(Op.OpEnqueueMarker, GetNewId(), resultType); + + result.AddOperand(queue); + result.AddOperand(numEvents); + result.AddOperand(waitEvents); + result.AddOperand(retEvent); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction EnqueueKernel(Instruction resultType, Instruction queue, Instruction flags, Instruction nDRange, Instruction numEvents, Instruction waitEvents, Instruction retEvent, Instruction invoke, Instruction param, Instruction paramSize, Instruction paramAlign, params Instruction[] localSize) + { + Instruction result = new Instruction(Op.OpEnqueueKernel, GetNewId(), resultType); + + result.AddOperand(queue); + result.AddOperand(flags); + result.AddOperand(nDRange); + result.AddOperand(numEvents); + result.AddOperand(waitEvents); + result.AddOperand(retEvent); + result.AddOperand(invoke); + result.AddOperand(param); + result.AddOperand(paramSize); + result.AddOperand(paramAlign); + result.AddOperand(localSize); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GetKernelNDrangeSubGroupCount(Instruction resultType, Instruction nDRange, Instruction invoke, Instruction param, Instruction paramSize, Instruction paramAlign) + { + Instruction result = new Instruction(Op.OpGetKernelNDrangeSubGroupCount, GetNewId(), resultType); + + result.AddOperand(nDRange); + result.AddOperand(invoke); + result.AddOperand(param); + result.AddOperand(paramSize); + result.AddOperand(paramAlign); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GetKernelNDrangeMaxSubGroupSize(Instruction resultType, Instruction nDRange, Instruction invoke, Instruction param, Instruction paramSize, Instruction paramAlign) + { + Instruction result = new Instruction(Op.OpGetKernelNDrangeMaxSubGroupSize, GetNewId(), resultType); + + result.AddOperand(nDRange); + result.AddOperand(invoke); + result.AddOperand(param); + result.AddOperand(paramSize); + result.AddOperand(paramAlign); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GetKernelWorkGroupSize(Instruction resultType, Instruction invoke, Instruction param, Instruction paramSize, Instruction paramAlign) + { + Instruction result = new Instruction(Op.OpGetKernelWorkGroupSize, GetNewId(), resultType); + + result.AddOperand(invoke); + result.AddOperand(param); + result.AddOperand(paramSize); + result.AddOperand(paramAlign); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GetKernelPreferredWorkGroupSizeMultiple(Instruction resultType, Instruction invoke, Instruction param, Instruction paramSize, Instruction paramAlign) + { + Instruction result = new Instruction(Op.OpGetKernelPreferredWorkGroupSizeMultiple, GetNewId(), resultType); + + result.AddOperand(invoke); + result.AddOperand(param); + result.AddOperand(paramSize); + result.AddOperand(paramAlign); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RetainEvent(Instruction eventObj) + { + Instruction result = new Instruction(Op.OpRetainEvent); + + result.AddOperand(eventObj); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ReleaseEvent(Instruction eventObj) + { + Instruction result = new Instruction(Op.OpReleaseEvent); + + result.AddOperand(eventObj); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CreateUserEvent(Instruction resultType) + { + Instruction result = new Instruction(Op.OpCreateUserEvent, GetNewId(), resultType); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IsValidEvent(Instruction resultType, Instruction eventObj) + { + Instruction result = new Instruction(Op.OpIsValidEvent, GetNewId(), resultType); + + result.AddOperand(eventObj); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SetUserEventStatus(Instruction eventObj, Instruction status) + { + Instruction result = new Instruction(Op.OpSetUserEventStatus); + + result.AddOperand(eventObj); + result.AddOperand(status); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CaptureEventProfilingInfo(Instruction eventObj, Instruction profilingInfo, Instruction value) + { + Instruction result = new Instruction(Op.OpCaptureEventProfilingInfo); + + result.AddOperand(eventObj); + result.AddOperand(profilingInfo); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GetDefaultQueue(Instruction resultType) + { + Instruction result = new Instruction(Op.OpGetDefaultQueue, GetNewId(), resultType); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction BuildNDRange(Instruction resultType, Instruction globalWorkSize, Instruction localWorkSize, Instruction globalWorkOffset) + { + Instruction result = new Instruction(Op.OpBuildNDRange, GetNewId(), resultType); + + result.AddOperand(globalWorkSize); + result.AddOperand(localWorkSize); + result.AddOperand(globalWorkOffset); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GetKernelLocalSizeForSubgroupCount(Instruction resultType, Instruction subgroupCount, Instruction invoke, Instruction param, Instruction paramSize, Instruction paramAlign) + { + Instruction result = new Instruction(Op.OpGetKernelLocalSizeForSubgroupCount, GetNewId(), resultType); + + result.AddOperand(subgroupCount); + result.AddOperand(invoke); + result.AddOperand(param); + result.AddOperand(paramSize); + result.AddOperand(paramAlign); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GetKernelMaxNumSubgroups(Instruction resultType, Instruction invoke, Instruction param, Instruction paramSize, Instruction paramAlign) + { + Instruction result = new Instruction(Op.OpGetKernelMaxNumSubgroups, GetNewId(), resultType); + + result.AddOperand(invoke); + result.AddOperand(param); + result.AddOperand(paramSize); + result.AddOperand(paramAlign); + AddToFunctionDefinitions(result); + + return result; + } + + // Pipe + + public Instruction ReadPipe(Instruction resultType, Instruction pipe, Instruction pointer, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = new Instruction(Op.OpReadPipe, GetNewId(), resultType); + + result.AddOperand(pipe); + result.AddOperand(pointer); + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction WritePipe(Instruction resultType, Instruction pipe, Instruction pointer, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = new Instruction(Op.OpWritePipe, GetNewId(), resultType); + + result.AddOperand(pipe); + result.AddOperand(pointer); + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ReservedReadPipe(Instruction resultType, Instruction pipe, Instruction reserveId, Instruction index, Instruction pointer, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = new Instruction(Op.OpReservedReadPipe, GetNewId(), resultType); + + result.AddOperand(pipe); + result.AddOperand(reserveId); + result.AddOperand(index); + result.AddOperand(pointer); + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ReservedWritePipe(Instruction resultType, Instruction pipe, Instruction reserveId, Instruction index, Instruction pointer, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = new Instruction(Op.OpReservedWritePipe, GetNewId(), resultType); + + result.AddOperand(pipe); + result.AddOperand(reserveId); + result.AddOperand(index); + result.AddOperand(pointer); + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ReserveReadPipePackets(Instruction resultType, Instruction pipe, Instruction numPackets, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = new Instruction(Op.OpReserveReadPipePackets, GetNewId(), resultType); + + result.AddOperand(pipe); + result.AddOperand(numPackets); + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ReserveWritePipePackets(Instruction resultType, Instruction pipe, Instruction numPackets, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = new Instruction(Op.OpReserveWritePipePackets, GetNewId(), resultType); + + result.AddOperand(pipe); + result.AddOperand(numPackets); + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CommitReadPipe(Instruction pipe, Instruction reserveId, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = new Instruction(Op.OpCommitReadPipe); + + result.AddOperand(pipe); + result.AddOperand(reserveId); + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CommitWritePipe(Instruction pipe, Instruction reserveId, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = new Instruction(Op.OpCommitWritePipe); + + result.AddOperand(pipe); + result.AddOperand(reserveId); + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IsValidReserveId(Instruction resultType, Instruction reserveId) + { + Instruction result = new Instruction(Op.OpIsValidReserveId, GetNewId(), resultType); + + result.AddOperand(reserveId); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GetNumPipePackets(Instruction resultType, Instruction pipe, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = new Instruction(Op.OpGetNumPipePackets, GetNewId(), resultType); + + result.AddOperand(pipe); + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GetMaxPipePackets(Instruction resultType, Instruction pipe, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = new Instruction(Op.OpGetMaxPipePackets, GetNewId(), resultType); + + result.AddOperand(pipe); + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupReserveReadPipePackets(Instruction resultType, Instruction execution, Instruction pipe, Instruction numPackets, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = new Instruction(Op.OpGroupReserveReadPipePackets, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(pipe); + result.AddOperand(numPackets); + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupReserveWritePipePackets(Instruction resultType, Instruction execution, Instruction pipe, Instruction numPackets, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = new Instruction(Op.OpGroupReserveWritePipePackets, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(pipe); + result.AddOperand(numPackets); + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupCommitReadPipe(Instruction execution, Instruction pipe, Instruction reserveId, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = new Instruction(Op.OpGroupCommitReadPipe); + + result.AddOperand(execution); + result.AddOperand(pipe); + result.AddOperand(reserveId); + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupCommitWritePipe(Instruction execution, Instruction pipe, Instruction reserveId, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = new Instruction(Op.OpGroupCommitWritePipe); + + result.AddOperand(execution); + result.AddOperand(pipe); + result.AddOperand(reserveId); + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ConstantPipeStorage(Instruction resultType, LiteralInteger packetSize, LiteralInteger packetAlignment, LiteralInteger capacity) + { + Instruction result = new Instruction(Op.OpConstantPipeStorage, GetNewId(), resultType); + + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + result.AddOperand(capacity); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CreatePipeFromPipeStorage(Instruction resultType, Instruction pipeStorage) + { + Instruction result = new Instruction(Op.OpCreatePipeFromPipeStorage, GetNewId(), resultType); + + result.AddOperand(pipeStorage); + AddToFunctionDefinitions(result); + + return result; + } + + // Non-Uniform + + public Instruction GroupNonUniformElect(Instruction resultType, Instruction execution) + { + Instruction result = new Instruction(Op.OpGroupNonUniformElect, GetNewId(), resultType); + + result.AddOperand(execution); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformAll(Instruction resultType, Instruction execution, Instruction predicate) + { + Instruction result = new Instruction(Op.OpGroupNonUniformAll, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(predicate); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformAny(Instruction resultType, Instruction execution, Instruction predicate) + { + Instruction result = new Instruction(Op.OpGroupNonUniformAny, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(predicate); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformAllEqual(Instruction resultType, Instruction execution, Instruction value) + { + Instruction result = new Instruction(Op.OpGroupNonUniformAllEqual, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformBroadcast(Instruction resultType, Instruction execution, Instruction value, Instruction id) + { + Instruction result = new Instruction(Op.OpGroupNonUniformBroadcast, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(value); + result.AddOperand(id); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformBroadcastFirst(Instruction resultType, Instruction execution, Instruction value) + { + Instruction result = new Instruction(Op.OpGroupNonUniformBroadcastFirst, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformBallot(Instruction resultType, Instruction execution, Instruction predicate) + { + Instruction result = new Instruction(Op.OpGroupNonUniformBallot, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(predicate); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformInverseBallot(Instruction resultType, Instruction execution, Instruction value) + { + Instruction result = new Instruction(Op.OpGroupNonUniformInverseBallot, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformBallotBitExtract(Instruction resultType, Instruction execution, Instruction value, Instruction index) + { + Instruction result = new Instruction(Op.OpGroupNonUniformBallotBitExtract, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(value); + result.AddOperand(index); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformBallotBitCount(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value) + { + Instruction result = new Instruction(Op.OpGroupNonUniformBallotBitCount, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformBallotFindLSB(Instruction resultType, Instruction execution, Instruction value) + { + Instruction result = new Instruction(Op.OpGroupNonUniformBallotFindLSB, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformBallotFindMSB(Instruction resultType, Instruction execution, Instruction value) + { + Instruction result = new Instruction(Op.OpGroupNonUniformBallotFindMSB, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformShuffle(Instruction resultType, Instruction execution, Instruction value, Instruction id) + { + Instruction result = new Instruction(Op.OpGroupNonUniformShuffle, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(value); + result.AddOperand(id); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformShuffleXor(Instruction resultType, Instruction execution, Instruction value, Instruction mask) + { + Instruction result = new Instruction(Op.OpGroupNonUniformShuffleXor, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(value); + result.AddOperand(mask); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformShuffleUp(Instruction resultType, Instruction execution, Instruction value, Instruction delta) + { + Instruction result = new Instruction(Op.OpGroupNonUniformShuffleUp, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(value); + result.AddOperand(delta); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformShuffleDown(Instruction resultType, Instruction execution, Instruction value, Instruction delta) + { + Instruction result = new Instruction(Op.OpGroupNonUniformShuffleDown, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(value); + result.AddOperand(delta); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformIAdd(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = new Instruction(Op.OpGroupNonUniformIAdd, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformFAdd(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = new Instruction(Op.OpGroupNonUniformFAdd, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformIMul(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = new Instruction(Op.OpGroupNonUniformIMul, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformFMul(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = new Instruction(Op.OpGroupNonUniformFMul, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformSMin(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = new Instruction(Op.OpGroupNonUniformSMin, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformUMin(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = new Instruction(Op.OpGroupNonUniformUMin, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformFMin(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = new Instruction(Op.OpGroupNonUniformFMin, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformSMax(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = new Instruction(Op.OpGroupNonUniformSMax, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformUMax(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = new Instruction(Op.OpGroupNonUniformUMax, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformFMax(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = new Instruction(Op.OpGroupNonUniformFMax, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformBitwiseAnd(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = new Instruction(Op.OpGroupNonUniformBitwiseAnd, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformBitwiseOr(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = new Instruction(Op.OpGroupNonUniformBitwiseOr, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformBitwiseXor(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = new Instruction(Op.OpGroupNonUniformBitwiseXor, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformLogicalAnd(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = new Instruction(Op.OpGroupNonUniformLogicalAnd, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformLogicalOr(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = new Instruction(Op.OpGroupNonUniformLogicalOr, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformLogicalXor(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = new Instruction(Op.OpGroupNonUniformLogicalXor, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformQuadBroadcast(Instruction resultType, Instruction execution, Instruction value, Instruction index) + { + Instruction result = new Instruction(Op.OpGroupNonUniformQuadBroadcast, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(value); + result.AddOperand(index); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformQuadSwap(Instruction resultType, Instruction execution, Instruction value, Instruction direction) + { + Instruction result = new Instruction(Op.OpGroupNonUniformQuadSwap, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(value); + result.AddOperand(direction); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformPartitionNV(Instruction resultType, Instruction value) + { + Instruction result = new Instruction(Op.OpGroupNonUniformPartitionNV, GetNewId(), resultType); + + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + // Reserved + + public Instruction TypeRayQueryProvisionalKHR() + { + Instruction result = new Instruction(Op.OpTypeRayQueryProvisionalKHR, GetNewId()); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryInitializeKHR(Instruction rayQuery, Instruction accel, Instruction rayFlags, Instruction cullMask, Instruction rayOrigin, Instruction rayTMin, Instruction rayDirection, Instruction rayTMax) + { + Instruction result = new Instruction(Op.OpRayQueryInitializeKHR); + + result.AddOperand(rayQuery); + result.AddOperand(accel); + result.AddOperand(rayFlags); + result.AddOperand(cullMask); + result.AddOperand(rayOrigin); + result.AddOperand(rayTMin); + result.AddOperand(rayDirection); + result.AddOperand(rayTMax); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryTerminateKHR(Instruction rayQuery) + { + Instruction result = new Instruction(Op.OpRayQueryTerminateKHR); + + result.AddOperand(rayQuery); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGenerateIntersectionKHR(Instruction rayQuery, Instruction hitT) + { + Instruction result = new Instruction(Op.OpRayQueryGenerateIntersectionKHR); + + result.AddOperand(rayQuery); + result.AddOperand(hitT); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryConfirmIntersectionKHR(Instruction rayQuery) + { + Instruction result = new Instruction(Op.OpRayQueryConfirmIntersectionKHR); + + result.AddOperand(rayQuery); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryProceedKHR(Instruction resultType, Instruction rayQuery) + { + Instruction result = new Instruction(Op.OpRayQueryProceedKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetIntersectionTypeKHR(Instruction resultType, Instruction rayQuery, Instruction intersection) + { + Instruction result = new Instruction(Op.OpRayQueryGetIntersectionTypeKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + result.AddOperand(intersection); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FragmentMaskFetchAMD(Instruction resultType, Instruction image, Instruction coordinate) + { + Instruction result = new Instruction(Op.OpFragmentMaskFetchAMD, GetNewId(), resultType); + + result.AddOperand(image); + result.AddOperand(coordinate); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FragmentFetchAMD(Instruction resultType, Instruction image, Instruction coordinate, Instruction fragmentIndex) + { + Instruction result = new Instruction(Op.OpFragmentFetchAMD, GetNewId(), resultType); + + result.AddOperand(image); + result.AddOperand(coordinate); + result.AddOperand(fragmentIndex); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ReadClockKHR(Instruction resultType, Instruction execution) + { + Instruction result = new Instruction(Op.OpReadClockKHR, GetNewId(), resultType); + + result.AddOperand(execution); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction WritePackedPrimitiveIndices4x8NV(Instruction indexOffset, Instruction packedIndices) + { + Instruction result = new Instruction(Op.OpWritePackedPrimitiveIndices4x8NV); + + result.AddOperand(indexOffset); + result.AddOperand(packedIndices); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ReportIntersectionNV(Instruction resultType, Instruction hit, Instruction hitKind) + { + Instruction result = new Instruction(Op.OpReportIntersectionNV, GetNewId(), resultType); + + result.AddOperand(hit); + result.AddOperand(hitKind); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ReportIntersectionKHR(Instruction resultType, Instruction hit, Instruction hitKind) + { + Instruction result = new Instruction(Op.OpReportIntersectionKHR, GetNewId(), resultType); + + result.AddOperand(hit); + result.AddOperand(hitKind); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IgnoreIntersectionNV() + { + Instruction result = new Instruction(Op.OpIgnoreIntersectionNV); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IgnoreIntersectionKHR() + { + Instruction result = new Instruction(Op.OpIgnoreIntersectionKHR); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction TerminateRayNV() + { + Instruction result = new Instruction(Op.OpTerminateRayNV); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction TerminateRayKHR() + { + Instruction result = new Instruction(Op.OpTerminateRayKHR); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction TraceNV(Instruction accel, Instruction rayFlags, Instruction cullMask, Instruction sBTOffset, Instruction sBTStride, Instruction missIndex, Instruction rayOrigin, Instruction rayTmin, Instruction rayDirection, Instruction rayTmax, Instruction payloadId) + { + Instruction result = new Instruction(Op.OpTraceNV); + + result.AddOperand(accel); + result.AddOperand(rayFlags); + result.AddOperand(cullMask); + result.AddOperand(sBTOffset); + result.AddOperand(sBTStride); + result.AddOperand(missIndex); + result.AddOperand(rayOrigin); + result.AddOperand(rayTmin); + result.AddOperand(rayDirection); + result.AddOperand(rayTmax); + result.AddOperand(payloadId); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction TraceRayKHR(Instruction accel, Instruction rayFlags, Instruction cullMask, Instruction sBTOffset, Instruction sBTStride, Instruction missIndex, Instruction rayOrigin, Instruction rayTmin, Instruction rayDirection, Instruction rayTmax, Instruction payloadId) + { + Instruction result = new Instruction(Op.OpTraceRayKHR); + + result.AddOperand(accel); + result.AddOperand(rayFlags); + result.AddOperand(cullMask); + result.AddOperand(sBTOffset); + result.AddOperand(sBTStride); + result.AddOperand(missIndex); + result.AddOperand(rayOrigin); + result.AddOperand(rayTmin); + result.AddOperand(rayDirection); + result.AddOperand(rayTmax); + result.AddOperand(payloadId); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction TypeAccelerationStructureNV() + { + Instruction result = new Instruction(Op.OpTypeAccelerationStructureNV, GetNewId()); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction TypeAccelerationStructureKHR() + { + Instruction result = new Instruction(Op.OpTypeAccelerationStructureKHR, GetNewId()); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ExecuteCallableNV(Instruction sBTIndex, Instruction callableDataId) + { + Instruction result = new Instruction(Op.OpExecuteCallableNV); + + result.AddOperand(sBTIndex); + result.AddOperand(callableDataId); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ExecuteCallableKHR(Instruction sBTIndex, Instruction callableDataId) + { + Instruction result = new Instruction(Op.OpExecuteCallableKHR); + + result.AddOperand(sBTIndex); + result.AddOperand(callableDataId); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction TypeCooperativeMatrixNV(Instruction componentType, Instruction execution, Instruction rows, Instruction columns) + { + Instruction result = new Instruction(Op.OpTypeCooperativeMatrixNV, GetNewId()); + + result.AddOperand(componentType); + result.AddOperand(execution); + result.AddOperand(rows); + result.AddOperand(columns); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CooperativeMatrixLoadNV(Instruction resultType, Instruction pointer, Instruction stride, Instruction columnMajor, MemoryAccessMask memoryAccess = (MemoryAccessMask)int.MaxValue) + { + Instruction result = new Instruction(Op.OpCooperativeMatrixLoadNV, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(stride); + result.AddOperand(columnMajor); + if (memoryAccess != (MemoryAccessMask)int.MaxValue) + { + result.AddOperand(memoryAccess); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CooperativeMatrixStoreNV(Instruction pointer, Instruction obj, Instruction stride, Instruction columnMajor, MemoryAccessMask memoryAccess = (MemoryAccessMask)int.MaxValue) + { + Instruction result = new Instruction(Op.OpCooperativeMatrixStoreNV); + + result.AddOperand(pointer); + result.AddOperand(obj); + result.AddOperand(stride); + result.AddOperand(columnMajor); + if (memoryAccess != (MemoryAccessMask)int.MaxValue) + { + result.AddOperand(memoryAccess); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CooperativeMatrixMulAddNV(Instruction resultType, Instruction a, Instruction b, Instruction c) + { + Instruction result = new Instruction(Op.OpCooperativeMatrixMulAddNV, GetNewId(), resultType); + + result.AddOperand(a); + result.AddOperand(b); + result.AddOperand(c); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CooperativeMatrixLengthNV(Instruction resultType, Instruction type) + { + Instruction result = new Instruction(Op.OpCooperativeMatrixLengthNV, GetNewId(), resultType); + + result.AddOperand(type); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction BeginInvocationInterlockEXT() + { + Instruction result = new Instruction(Op.OpBeginInvocationInterlockEXT); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction EndInvocationInterlockEXT() + { + Instruction result = new Instruction(Op.OpEndInvocationInterlockEXT); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction DemoteToHelperInvocationEXT() + { + Instruction result = new Instruction(Op.OpDemoteToHelperInvocationEXT); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IsHelperInvocationEXT(Instruction resultType) + { + Instruction result = new Instruction(Op.OpIsHelperInvocationEXT, GetNewId(), resultType); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction UCountLeadingZerosINTEL(Instruction resultType, Instruction operand) + { + Instruction result = new Instruction(Op.OpUCountLeadingZerosINTEL, GetNewId(), resultType); + + result.AddOperand(operand); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction UCountTrailingZerosINTEL(Instruction resultType, Instruction operand) + { + Instruction result = new Instruction(Op.OpUCountTrailingZerosINTEL, GetNewId(), resultType); + + result.AddOperand(operand); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AbsISubINTEL(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpAbsISubINTEL, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AbsUSubINTEL(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpAbsUSubINTEL, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IAddSatINTEL(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpIAddSatINTEL, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction UAddSatINTEL(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpUAddSatINTEL, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IAverageINTEL(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpIAverageINTEL, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction UAverageINTEL(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpUAverageINTEL, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IAverageRoundedINTEL(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpIAverageRoundedINTEL, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction UAverageRoundedINTEL(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpUAverageRoundedINTEL, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ISubSatINTEL(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpISubSatINTEL, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction USubSatINTEL(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpUSubSatINTEL, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IMul32x16INTEL(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpIMul32x16INTEL, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction UMul32x16INTEL(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = new Instruction(Op.OpUMul32x16INTEL, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetRayTMinKHR(Instruction resultType, Instruction rayQuery) + { + Instruction result = new Instruction(Op.OpRayQueryGetRayTMinKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetRayFlagsKHR(Instruction resultType, Instruction rayQuery) + { + Instruction result = new Instruction(Op.OpRayQueryGetRayFlagsKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetIntersectionTKHR(Instruction resultType, Instruction rayQuery, Instruction intersection) + { + Instruction result = new Instruction(Op.OpRayQueryGetIntersectionTKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + result.AddOperand(intersection); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetIntersectionInstanceCustomIndexKHR(Instruction resultType, Instruction rayQuery, Instruction intersection) + { + Instruction result = new Instruction(Op.OpRayQueryGetIntersectionInstanceCustomIndexKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + result.AddOperand(intersection); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetIntersectionInstanceIdKHR(Instruction resultType, Instruction rayQuery, Instruction intersection) + { + Instruction result = new Instruction(Op.OpRayQueryGetIntersectionInstanceIdKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + result.AddOperand(intersection); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetIntersectionInstanceShaderBindingTableRecordOffsetKHR(Instruction resultType, Instruction rayQuery, Instruction intersection) + { + Instruction result = new Instruction(Op.OpRayQueryGetIntersectionInstanceShaderBindingTableRecordOffsetKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + result.AddOperand(intersection); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetIntersectionGeometryIndexKHR(Instruction resultType, Instruction rayQuery, Instruction intersection) + { + Instruction result = new Instruction(Op.OpRayQueryGetIntersectionGeometryIndexKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + result.AddOperand(intersection); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetIntersectionPrimitiveIndexKHR(Instruction resultType, Instruction rayQuery, Instruction intersection) + { + Instruction result = new Instruction(Op.OpRayQueryGetIntersectionPrimitiveIndexKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + result.AddOperand(intersection); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetIntersectionBarycentricsKHR(Instruction resultType, Instruction rayQuery, Instruction intersection) + { + Instruction result = new Instruction(Op.OpRayQueryGetIntersectionBarycentricsKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + result.AddOperand(intersection); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetIntersectionFrontFaceKHR(Instruction resultType, Instruction rayQuery, Instruction intersection) + { + Instruction result = new Instruction(Op.OpRayQueryGetIntersectionFrontFaceKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + result.AddOperand(intersection); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetIntersectionCandidateAABBOpaqueKHR(Instruction resultType, Instruction rayQuery) + { + Instruction result = new Instruction(Op.OpRayQueryGetIntersectionCandidateAABBOpaqueKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetIntersectionObjectRayDirectionKHR(Instruction resultType, Instruction rayQuery, Instruction intersection) + { + Instruction result = new Instruction(Op.OpRayQueryGetIntersectionObjectRayDirectionKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + result.AddOperand(intersection); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetIntersectionObjectRayOriginKHR(Instruction resultType, Instruction rayQuery, Instruction intersection) + { + Instruction result = new Instruction(Op.OpRayQueryGetIntersectionObjectRayOriginKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + result.AddOperand(intersection); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetWorldRayDirectionKHR(Instruction resultType, Instruction rayQuery) + { + Instruction result = new Instruction(Op.OpRayQueryGetWorldRayDirectionKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetWorldRayOriginKHR(Instruction resultType, Instruction rayQuery) + { + Instruction result = new Instruction(Op.OpRayQueryGetWorldRayOriginKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetIntersectionObjectToWorldKHR(Instruction resultType, Instruction rayQuery, Instruction intersection) + { + Instruction result = new Instruction(Op.OpRayQueryGetIntersectionObjectToWorldKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + result.AddOperand(intersection); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetIntersectionWorldToObjectKHR(Instruction resultType, Instruction rayQuery, Instruction intersection) + { + Instruction result = new Instruction(Op.OpRayQueryGetIntersectionWorldToObjectKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + result.AddOperand(intersection); + AddToFunctionDefinitions(result); + + return result; + } + + } +} diff --git a/SpvGen/Autogenerated/spirv.cs b/SpvGen/Autogenerated/spirv.cs new file mode 100644 index 000000000..82d73e3c9 --- /dev/null +++ b/SpvGen/Autogenerated/spirv.cs @@ -0,0 +1,1548 @@ +// Copyright (c) 2014-2020 The Khronos Group Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and/or associated documentation files (the "Materials"), +// to deal in the Materials without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Materials, and to permit persons to whom the +// Materials are furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Materials. +// +// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS +// STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND +// HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ +// +// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS +// IN THE MATERIALS. + +// This header is automatically generated by the same tool that creates +// the Binary Section of the SPIR-V specification. + +// Enumeration tokens for SPIR-V, in various styles: +// C, C++, C++11, JSON, Lua, Python, C#, D +// +// - C will have tokens with a "Spv" prefix, e.g.: SpvSourceLanguageGLSL +// - C++ will have tokens in the "spv" name space, e.g.: spv::SourceLanguageGLSL +// - C++11 will use enum classes in the spv namespace, e.g.: spv::SourceLanguage::GLSL +// - Lua will use tables, e.g.: spv.SourceLanguage.GLSL +// - Python will use dictionaries, e.g.: spv['SourceLanguage']['GLSL'] +// - C# will use enum classes in the Specification class located in the "Spv" namespace, +// e.g.: Spv.Specification.SourceLanguage.GLSL +// - D will have tokens under the "spv" module, e.g: spv.SourceLanguage.GLSL +// +// Some tokens act like mask values, which can be OR'd together, +// while others are mutually exclusive. The mask-like ones have +// "Mask" in their name, and a parallel enum that has the shift +// amount (1 << x) for each corresponding enumerant. + +namespace Spv +{ + + public static class Specification + { + public const uint MagicNumber = 0x07230203; + public const uint Version = 0x00010500; + public const uint Revision = 3; + public const uint OpCodeMask = 0xffff; + public const uint WordCountShift = 16; + + public enum SourceLanguage + { + Unknown = 0, + ESSL = 1, + GLSL = 2, + OpenCL_C = 3, + OpenCL_CPP = 4, + HLSL = 5, + } + + public enum ExecutionModel + { + Vertex = 0, + TessellationControl = 1, + TessellationEvaluation = 2, + Geometry = 3, + Fragment = 4, + GLCompute = 5, + Kernel = 6, + TaskNV = 5267, + MeshNV = 5268, + RayGenerationKHR = 5313, + RayGenerationNV = 5313, + IntersectionKHR = 5314, + IntersectionNV = 5314, + AnyHitKHR = 5315, + AnyHitNV = 5315, + ClosestHitKHR = 5316, + ClosestHitNV = 5316, + MissKHR = 5317, + MissNV = 5317, + CallableKHR = 5318, + CallableNV = 5318, + } + + public enum AddressingModel + { + Logical = 0, + Physical32 = 1, + Physical64 = 2, + PhysicalStorageBuffer64 = 5348, + PhysicalStorageBuffer64EXT = 5348, + } + + public enum MemoryModel + { + Simple = 0, + GLSL450 = 1, + OpenCL = 2, + Vulkan = 3, + VulkanKHR = 3, + } + + public enum ExecutionMode + { + Invocations = 0, + SpacingEqual = 1, + SpacingFractionalEven = 2, + SpacingFractionalOdd = 3, + VertexOrderCw = 4, + VertexOrderCcw = 5, + PixelCenterInteger = 6, + OriginUpperLeft = 7, + OriginLowerLeft = 8, + EarlyFragmentTests = 9, + PointMode = 10, + Xfb = 11, + DepthReplacing = 12, + DepthGreater = 14, + DepthLess = 15, + DepthUnchanged = 16, + LocalSize = 17, + LocalSizeHint = 18, + InputPoints = 19, + InputLines = 20, + InputLinesAdjacency = 21, + Triangles = 22, + InputTrianglesAdjacency = 23, + Quads = 24, + Isolines = 25, + OutputVertices = 26, + OutputPoints = 27, + OutputLineStrip = 28, + OutputTriangleStrip = 29, + VecTypeHint = 30, + ContractionOff = 31, + Initializer = 33, + Finalizer = 34, + SubgroupSize = 35, + SubgroupsPerWorkgroup = 36, + SubgroupsPerWorkgroupId = 37, + LocalSizeId = 38, + LocalSizeHintId = 39, + PostDepthCoverage = 4446, + DenormPreserve = 4459, + DenormFlushToZero = 4460, + SignedZeroInfNanPreserve = 4461, + RoundingModeRTE = 4462, + RoundingModeRTZ = 4463, + StencilRefReplacingEXT = 5027, + OutputLinesNV = 5269, + OutputPrimitivesNV = 5270, + DerivativeGroupQuadsNV = 5289, + DerivativeGroupLinearNV = 5290, + OutputTrianglesNV = 5298, + PixelInterlockOrderedEXT = 5366, + PixelInterlockUnorderedEXT = 5367, + SampleInterlockOrderedEXT = 5368, + SampleInterlockUnorderedEXT = 5369, + ShadingRateInterlockOrderedEXT = 5370, + ShadingRateInterlockUnorderedEXT = 5371, + } + + public enum StorageClass + { + UniformConstant = 0, + Input = 1, + Uniform = 2, + Output = 3, + Workgroup = 4, + CrossWorkgroup = 5, + Private = 6, + Function = 7, + Generic = 8, + PushConstant = 9, + AtomicCounter = 10, + Image = 11, + StorageBuffer = 12, + CallableDataKHR = 5328, + CallableDataNV = 5328, + IncomingCallableDataKHR = 5329, + IncomingCallableDataNV = 5329, + RayPayloadKHR = 5338, + RayPayloadNV = 5338, + HitAttributeKHR = 5339, + HitAttributeNV = 5339, + IncomingRayPayloadKHR = 5342, + IncomingRayPayloadNV = 5342, + ShaderRecordBufferKHR = 5343, + ShaderRecordBufferNV = 5343, + PhysicalStorageBuffer = 5349, + PhysicalStorageBufferEXT = 5349, + } + + public enum Dim + { + Dim1D = 0, + Dim2D = 1, + Dim3D = 2, + Cube = 3, + Rect = 4, + Buffer = 5, + SubpassData = 6, + } + + public enum SamplerAddressingMode + { + None = 0, + ClampToEdge = 1, + Clamp = 2, + Repeat = 3, + RepeatMirrored = 4, + } + + public enum SamplerFilterMode + { + Nearest = 0, + Linear = 1, + } + + public enum ImageFormat + { + Unknown = 0, + Rgba32f = 1, + Rgba16f = 2, + R32f = 3, + Rgba8 = 4, + Rgba8Snorm = 5, + Rg32f = 6, + Rg16f = 7, + R11fG11fB10f = 8, + R16f = 9, + Rgba16 = 10, + Rgb10A2 = 11, + Rg16 = 12, + Rg8 = 13, + R16 = 14, + R8 = 15, + Rgba16Snorm = 16, + Rg16Snorm = 17, + Rg8Snorm = 18, + R16Snorm = 19, + R8Snorm = 20, + Rgba32i = 21, + Rgba16i = 22, + Rgba8i = 23, + R32i = 24, + Rg32i = 25, + Rg16i = 26, + Rg8i = 27, + R16i = 28, + R8i = 29, + Rgba32ui = 30, + Rgba16ui = 31, + Rgba8ui = 32, + R32ui = 33, + Rgb10a2ui = 34, + Rg32ui = 35, + Rg16ui = 36, + Rg8ui = 37, + R16ui = 38, + R8ui = 39, + } + + public enum ImageChannelOrder + { + R = 0, + A = 1, + RG = 2, + RA = 3, + RGB = 4, + RGBA = 5, + BGRA = 6, + ARGB = 7, + Intensity = 8, + Luminance = 9, + Rx = 10, + RGx = 11, + RGBx = 12, + Depth = 13, + DepthStencil = 14, + sRGB = 15, + sRGBx = 16, + sRGBA = 17, + sBGRA = 18, + ABGR = 19, + } + + public enum ImageChannelDataType + { + SnormInt8 = 0, + SnormInt16 = 1, + UnormInt8 = 2, + UnormInt16 = 3, + UnormShort565 = 4, + UnormShort555 = 5, + UnormInt101010 = 6, + SignedInt8 = 7, + SignedInt16 = 8, + SignedInt32 = 9, + UnsignedInt8 = 10, + UnsignedInt16 = 11, + UnsignedInt32 = 12, + HalfFloat = 13, + Float = 14, + UnormInt24 = 15, + UnormInt101010_2 = 16, + } + + public enum ImageOperandsShift + { + Bias = 0, + Lod = 1, + Grad = 2, + ConstOffset = 3, + Offset = 4, + ConstOffsets = 5, + Sample = 6, + MinLod = 7, + MakeTexelAvailable = 8, + MakeTexelAvailableKHR = 8, + MakeTexelVisible = 9, + MakeTexelVisibleKHR = 9, + NonPrivateTexel = 10, + NonPrivateTexelKHR = 10, + VolatileTexel = 11, + VolatileTexelKHR = 11, + SignExtend = 12, + ZeroExtend = 13, + } + + public enum ImageOperandsMask + { + MaskNone = 0, + Bias = 0x00000001, + Lod = 0x00000002, + Grad = 0x00000004, + ConstOffset = 0x00000008, + Offset = 0x00000010, + ConstOffsets = 0x00000020, + Sample = 0x00000040, + MinLod = 0x00000080, + MakeTexelAvailable = 0x00000100, + MakeTexelAvailableKHR = 0x00000100, + MakeTexelVisible = 0x00000200, + MakeTexelVisibleKHR = 0x00000200, + NonPrivateTexel = 0x00000400, + NonPrivateTexelKHR = 0x00000400, + VolatileTexel = 0x00000800, + VolatileTexelKHR = 0x00000800, + SignExtend = 0x00001000, + ZeroExtend = 0x00002000, + } + + public enum FPFastMathModeShift + { + NotNaN = 0, + NotInf = 1, + NSZ = 2, + AllowRecip = 3, + Fast = 4, + } + + public enum FPFastMathModeMask + { + MaskNone = 0, + NotNaN = 0x00000001, + NotInf = 0x00000002, + NSZ = 0x00000004, + AllowRecip = 0x00000008, + Fast = 0x00000010, + } + + public enum FPRoundingMode + { + RTE = 0, + RTZ = 1, + RTP = 2, + RTN = 3, + } + + public enum LinkageType + { + Export = 0, + Import = 1, + } + + public enum AccessQualifier + { + ReadOnly = 0, + WriteOnly = 1, + ReadWrite = 2, + } + + public enum FunctionParameterAttribute + { + Zext = 0, + Sext = 1, + ByVal = 2, + Sret = 3, + NoAlias = 4, + NoCapture = 5, + NoWrite = 6, + NoReadWrite = 7, + } + + public enum Decoration + { + RelaxedPrecision = 0, + SpecId = 1, + Block = 2, + BufferBlock = 3, + RowMajor = 4, + ColMajor = 5, + ArrayStride = 6, + MatrixStride = 7, + GLSLShared = 8, + GLSLPacked = 9, + CPacked = 10, + BuiltIn = 11, + NoPerspective = 13, + Flat = 14, + Patch = 15, + Centroid = 16, + Sample = 17, + Invariant = 18, + Restrict = 19, + Aliased = 20, + Volatile = 21, + Constant = 22, + Coherent = 23, + NonWritable = 24, + NonReadable = 25, + Uniform = 26, + UniformId = 27, + SaturatedConversion = 28, + Stream = 29, + Location = 30, + Component = 31, + Index = 32, + Binding = 33, + DescriptorSet = 34, + Offset = 35, + XfbBuffer = 36, + XfbStride = 37, + FuncParamAttr = 38, + FPRoundingMode = 39, + FPFastMathMode = 40, + LinkageAttributes = 41, + NoContraction = 42, + InputAttachmentIndex = 43, + Alignment = 44, + MaxByteOffset = 45, + AlignmentId = 46, + MaxByteOffsetId = 47, + NoSignedWrap = 4469, + NoUnsignedWrap = 4470, + ExplicitInterpAMD = 4999, + OverrideCoverageNV = 5248, + PassthroughNV = 5250, + ViewportRelativeNV = 5252, + SecondaryViewportRelativeNV = 5256, + PerPrimitiveNV = 5271, + PerViewNV = 5272, + PerTaskNV = 5273, + PerVertexNV = 5285, + NonUniform = 5300, + NonUniformEXT = 5300, + RestrictPointer = 5355, + RestrictPointerEXT = 5355, + AliasedPointer = 5356, + AliasedPointerEXT = 5356, + CounterBuffer = 5634, + HlslCounterBufferGOOGLE = 5634, + HlslSemanticGOOGLE = 5635, + UserSemantic = 5635, + UserTypeGOOGLE = 5636, + } + + public enum BuiltIn + { + Position = 0, + PointSize = 1, + ClipDistance = 3, + CullDistance = 4, + VertexId = 5, + InstanceId = 6, + PrimitiveId = 7, + InvocationId = 8, + Layer = 9, + ViewportIndex = 10, + TessLevelOuter = 11, + TessLevelInner = 12, + TessCoord = 13, + PatchVertices = 14, + FragCoord = 15, + PointCoord = 16, + FrontFacing = 17, + SampleId = 18, + SamplePosition = 19, + SampleMask = 20, + FragDepth = 22, + HelperInvocation = 23, + NumWorkgroups = 24, + WorkgroupSize = 25, + WorkgroupId = 26, + LocalInvocationId = 27, + GlobalInvocationId = 28, + LocalInvocationIndex = 29, + WorkDim = 30, + GlobalSize = 31, + EnqueuedWorkgroupSize = 32, + GlobalOffset = 33, + GlobalLinearId = 34, + SubgroupSize = 36, + SubgroupMaxSize = 37, + NumSubgroups = 38, + NumEnqueuedSubgroups = 39, + SubgroupId = 40, + SubgroupLocalInvocationId = 41, + VertexIndex = 42, + InstanceIndex = 43, + SubgroupEqMask = 4416, + SubgroupEqMaskKHR = 4416, + SubgroupGeMask = 4417, + SubgroupGeMaskKHR = 4417, + SubgroupGtMask = 4418, + SubgroupGtMaskKHR = 4418, + SubgroupLeMask = 4419, + SubgroupLeMaskKHR = 4419, + SubgroupLtMask = 4420, + SubgroupLtMaskKHR = 4420, + BaseVertex = 4424, + BaseInstance = 4425, + DrawIndex = 4426, + DeviceIndex = 4438, + ViewIndex = 4440, + BaryCoordNoPerspAMD = 4992, + BaryCoordNoPerspCentroidAMD = 4993, + BaryCoordNoPerspSampleAMD = 4994, + BaryCoordSmoothAMD = 4995, + BaryCoordSmoothCentroidAMD = 4996, + BaryCoordSmoothSampleAMD = 4997, + BaryCoordPullModelAMD = 4998, + FragStencilRefEXT = 5014, + ViewportMaskNV = 5253, + SecondaryPositionNV = 5257, + SecondaryViewportMaskNV = 5258, + PositionPerViewNV = 5261, + ViewportMaskPerViewNV = 5262, + FullyCoveredEXT = 5264, + TaskCountNV = 5274, + PrimitiveCountNV = 5275, + PrimitiveIndicesNV = 5276, + ClipDistancePerViewNV = 5277, + CullDistancePerViewNV = 5278, + LayerPerViewNV = 5279, + MeshViewCountNV = 5280, + MeshViewIndicesNV = 5281, + BaryCoordNV = 5286, + BaryCoordNoPerspNV = 5287, + FragSizeEXT = 5292, + FragmentSizeNV = 5292, + FragInvocationCountEXT = 5293, + InvocationsPerPixelNV = 5293, + LaunchIdKHR = 5319, + LaunchIdNV = 5319, + LaunchSizeKHR = 5320, + LaunchSizeNV = 5320, + WorldRayOriginKHR = 5321, + WorldRayOriginNV = 5321, + WorldRayDirectionKHR = 5322, + WorldRayDirectionNV = 5322, + ObjectRayOriginKHR = 5323, + ObjectRayOriginNV = 5323, + ObjectRayDirectionKHR = 5324, + ObjectRayDirectionNV = 5324, + RayTminKHR = 5325, + RayTminNV = 5325, + RayTmaxKHR = 5326, + RayTmaxNV = 5326, + InstanceCustomIndexKHR = 5327, + InstanceCustomIndexNV = 5327, + ObjectToWorldKHR = 5330, + ObjectToWorldNV = 5330, + WorldToObjectKHR = 5331, + WorldToObjectNV = 5331, + HitTKHR = 5332, + HitTNV = 5332, + HitKindKHR = 5333, + HitKindNV = 5333, + IncomingRayFlagsKHR = 5351, + IncomingRayFlagsNV = 5351, + RayGeometryIndexKHR = 5352, + WarpsPerSMNV = 5374, + SMCountNV = 5375, + WarpIDNV = 5376, + SMIDNV = 5377, + } + + public enum SelectionControlShift + { + Flatten = 0, + DontFlatten = 1, + } + + public enum SelectionControlMask + { + MaskNone = 0, + Flatten = 0x00000001, + DontFlatten = 0x00000002, + } + + public enum LoopControlShift + { + Unroll = 0, + DontUnroll = 1, + DependencyInfinite = 2, + DependencyLength = 3, + MinIterations = 4, + MaxIterations = 5, + IterationMultiple = 6, + PeelCount = 7, + PartialCount = 8, + } + + public enum LoopControlMask + { + MaskNone = 0, + Unroll = 0x00000001, + DontUnroll = 0x00000002, + DependencyInfinite = 0x00000004, + DependencyLength = 0x00000008, + MinIterations = 0x00000010, + MaxIterations = 0x00000020, + IterationMultiple = 0x00000040, + PeelCount = 0x00000080, + PartialCount = 0x00000100, + } + + public enum FunctionControlShift + { + Inline = 0, + DontInline = 1, + Pure = 2, + Const = 3, + } + + public enum FunctionControlMask + { + MaskNone = 0, + Inline = 0x00000001, + DontInline = 0x00000002, + Pure = 0x00000004, + Const = 0x00000008, + } + + public enum MemorySemanticsShift + { + Acquire = 1, + Release = 2, + AcquireRelease = 3, + SequentiallyConsistent = 4, + UniformMemory = 6, + SubgroupMemory = 7, + WorkgroupMemory = 8, + CrossWorkgroupMemory = 9, + AtomicCounterMemory = 10, + ImageMemory = 11, + OutputMemory = 12, + OutputMemoryKHR = 12, + MakeAvailable = 13, + MakeAvailableKHR = 13, + MakeVisible = 14, + MakeVisibleKHR = 14, + Volatile = 15, + } + + public enum MemorySemanticsMask + { + MaskNone = 0, + Acquire = 0x00000002, + Release = 0x00000004, + AcquireRelease = 0x00000008, + SequentiallyConsistent = 0x00000010, + UniformMemory = 0x00000040, + SubgroupMemory = 0x00000080, + WorkgroupMemory = 0x00000100, + CrossWorkgroupMemory = 0x00000200, + AtomicCounterMemory = 0x00000400, + ImageMemory = 0x00000800, + OutputMemory = 0x00001000, + OutputMemoryKHR = 0x00001000, + MakeAvailable = 0x00002000, + MakeAvailableKHR = 0x00002000, + MakeVisible = 0x00004000, + MakeVisibleKHR = 0x00004000, + Volatile = 0x00008000, + } + + public enum MemoryAccessShift + { + Volatile = 0, + Aligned = 1, + Nontemporal = 2, + MakePointerAvailable = 3, + MakePointerAvailableKHR = 3, + MakePointerVisible = 4, + MakePointerVisibleKHR = 4, + NonPrivatePointer = 5, + NonPrivatePointerKHR = 5, + } + + public enum MemoryAccessMask + { + MaskNone = 0, + Volatile = 0x00000001, + Aligned = 0x00000002, + Nontemporal = 0x00000004, + MakePointerAvailable = 0x00000008, + MakePointerAvailableKHR = 0x00000008, + MakePointerVisible = 0x00000010, + MakePointerVisibleKHR = 0x00000010, + NonPrivatePointer = 0x00000020, + NonPrivatePointerKHR = 0x00000020, + } + + public enum Scope + { + CrossDevice = 0, + Device = 1, + Workgroup = 2, + Subgroup = 3, + Invocation = 4, + QueueFamily = 5, + QueueFamilyKHR = 5, + ShaderCallKHR = 6, + } + + public enum GroupOperation + { + Reduce = 0, + InclusiveScan = 1, + ExclusiveScan = 2, + ClusteredReduce = 3, + PartitionedReduceNV = 6, + PartitionedInclusiveScanNV = 7, + PartitionedExclusiveScanNV = 8, + } + + public enum KernelEnqueueFlags + { + NoWait = 0, + WaitKernel = 1, + WaitWorkGroup = 2, + } + + public enum KernelProfilingInfoShift + { + CmdExecTime = 0, + } + + public enum KernelProfilingInfoMask + { + MaskNone = 0, + CmdExecTime = 0x00000001, + } + + public enum Capability + { + Matrix = 0, + Shader = 1, + Geometry = 2, + Tessellation = 3, + Addresses = 4, + Linkage = 5, + Kernel = 6, + Vector16 = 7, + Float16Buffer = 8, + Float16 = 9, + Float64 = 10, + Int64 = 11, + Int64Atomics = 12, + ImageBasic = 13, + ImageReadWrite = 14, + ImageMipmap = 15, + Pipes = 17, + Groups = 18, + DeviceEnqueue = 19, + LiteralSampler = 20, + AtomicStorage = 21, + Int16 = 22, + TessellationPointSize = 23, + GeometryPointSize = 24, + ImageGatherExtended = 25, + StorageImageMultisample = 27, + UniformBufferArrayDynamicIndexing = 28, + SampledImageArrayDynamicIndexing = 29, + StorageBufferArrayDynamicIndexing = 30, + StorageImageArrayDynamicIndexing = 31, + ClipDistance = 32, + CullDistance = 33, + ImageCubeArray = 34, + SampleRateShading = 35, + ImageRect = 36, + SampledRect = 37, + GenericPointer = 38, + Int8 = 39, + InputAttachment = 40, + SparseResidency = 41, + MinLod = 42, + Sampled1D = 43, + Image1D = 44, + SampledCubeArray = 45, + SampledBuffer = 46, + ImageBuffer = 47, + ImageMSArray = 48, + StorageImageExtendedFormats = 49, + ImageQuery = 50, + DerivativeControl = 51, + InterpolationFunction = 52, + TransformFeedback = 53, + GeometryStreams = 54, + StorageImageReadWithoutFormat = 55, + StorageImageWriteWithoutFormat = 56, + MultiViewport = 57, + SubgroupDispatch = 58, + NamedBarrier = 59, + PipeStorage = 60, + GroupNonUniform = 61, + GroupNonUniformVote = 62, + GroupNonUniformArithmetic = 63, + GroupNonUniformBallot = 64, + GroupNonUniformShuffle = 65, + GroupNonUniformShuffleRelative = 66, + GroupNonUniformClustered = 67, + GroupNonUniformQuad = 68, + ShaderLayer = 69, + ShaderViewportIndex = 70, + SubgroupBallotKHR = 4423, + DrawParameters = 4427, + SubgroupVoteKHR = 4431, + StorageBuffer16BitAccess = 4433, + StorageUniformBufferBlock16 = 4433, + StorageUniform16 = 4434, + UniformAndStorageBuffer16BitAccess = 4434, + StoragePushConstant16 = 4435, + StorageInputOutput16 = 4436, + DeviceGroup = 4437, + MultiView = 4439, + VariablePointersStorageBuffer = 4441, + VariablePointers = 4442, + AtomicStorageOps = 4445, + SampleMaskPostDepthCoverage = 4447, + StorageBuffer8BitAccess = 4448, + UniformAndStorageBuffer8BitAccess = 4449, + StoragePushConstant8 = 4450, + DenormPreserve = 4464, + DenormFlushToZero = 4465, + SignedZeroInfNanPreserve = 4466, + RoundingModeRTE = 4467, + RoundingModeRTZ = 4468, + RayQueryProvisionalKHR = 4471, + RayTraversalPrimitiveCullingProvisionalKHR = 4478, + Float16ImageAMD = 5008, + ImageGatherBiasLodAMD = 5009, + FragmentMaskAMD = 5010, + StencilExportEXT = 5013, + ImageReadWriteLodAMD = 5015, + ShaderClockKHR = 5055, + SampleMaskOverrideCoverageNV = 5249, + GeometryShaderPassthroughNV = 5251, + ShaderViewportIndexLayerEXT = 5254, + ShaderViewportIndexLayerNV = 5254, + ShaderViewportMaskNV = 5255, + ShaderStereoViewNV = 5259, + PerViewAttributesNV = 5260, + FragmentFullyCoveredEXT = 5265, + MeshShadingNV = 5266, + ImageFootprintNV = 5282, + FragmentBarycentricNV = 5284, + ComputeDerivativeGroupQuadsNV = 5288, + FragmentDensityEXT = 5291, + ShadingRateNV = 5291, + GroupNonUniformPartitionedNV = 5297, + ShaderNonUniform = 5301, + ShaderNonUniformEXT = 5301, + RuntimeDescriptorArray = 5302, + RuntimeDescriptorArrayEXT = 5302, + InputAttachmentArrayDynamicIndexing = 5303, + InputAttachmentArrayDynamicIndexingEXT = 5303, + UniformTexelBufferArrayDynamicIndexing = 5304, + UniformTexelBufferArrayDynamicIndexingEXT = 5304, + StorageTexelBufferArrayDynamicIndexing = 5305, + StorageTexelBufferArrayDynamicIndexingEXT = 5305, + UniformBufferArrayNonUniformIndexing = 5306, + UniformBufferArrayNonUniformIndexingEXT = 5306, + SampledImageArrayNonUniformIndexing = 5307, + SampledImageArrayNonUniformIndexingEXT = 5307, + StorageBufferArrayNonUniformIndexing = 5308, + StorageBufferArrayNonUniformIndexingEXT = 5308, + StorageImageArrayNonUniformIndexing = 5309, + StorageImageArrayNonUniformIndexingEXT = 5309, + InputAttachmentArrayNonUniformIndexing = 5310, + InputAttachmentArrayNonUniformIndexingEXT = 5310, + UniformTexelBufferArrayNonUniformIndexing = 5311, + UniformTexelBufferArrayNonUniformIndexingEXT = 5311, + StorageTexelBufferArrayNonUniformIndexing = 5312, + StorageTexelBufferArrayNonUniformIndexingEXT = 5312, + RayTracingNV = 5340, + VulkanMemoryModel = 5345, + VulkanMemoryModelKHR = 5345, + VulkanMemoryModelDeviceScope = 5346, + VulkanMemoryModelDeviceScopeKHR = 5346, + PhysicalStorageBufferAddresses = 5347, + PhysicalStorageBufferAddressesEXT = 5347, + ComputeDerivativeGroupLinearNV = 5350, + RayTracingProvisionalKHR = 5353, + CooperativeMatrixNV = 5357, + FragmentShaderSampleInterlockEXT = 5363, + FragmentShaderShadingRateInterlockEXT = 5372, + ShaderSMBuiltinsNV = 5373, + FragmentShaderPixelInterlockEXT = 5378, + DemoteToHelperInvocationEXT = 5379, + SubgroupShuffleINTEL = 5568, + SubgroupBufferBlockIOINTEL = 5569, + SubgroupImageBlockIOINTEL = 5570, + SubgroupImageMediaBlockIOINTEL = 5579, + IntegerFunctions2INTEL = 5584, + SubgroupAvcMotionEstimationINTEL = 5696, + SubgroupAvcMotionEstimationIntraINTEL = 5697, + SubgroupAvcMotionEstimationChromaINTEL = 5698, + } + + public enum RayFlagsShift + { + OpaqueKHR = 0, + NoOpaqueKHR = 1, + TerminateOnFirstHitKHR = 2, + SkipClosestHitShaderKHR = 3, + CullBackFacingTrianglesKHR = 4, + CullFrontFacingTrianglesKHR = 5, + CullOpaqueKHR = 6, + CullNoOpaqueKHR = 7, + SkipTrianglesKHR = 8, + SkipAABBsKHR = 9, + } + + public enum RayFlagsMask + { + MaskNone = 0, + OpaqueKHR = 0x00000001, + NoOpaqueKHR = 0x00000002, + TerminateOnFirstHitKHR = 0x00000004, + SkipClosestHitShaderKHR = 0x00000008, + CullBackFacingTrianglesKHR = 0x00000010, + CullFrontFacingTrianglesKHR = 0x00000020, + CullOpaqueKHR = 0x00000040, + CullNoOpaqueKHR = 0x00000080, + SkipTrianglesKHR = 0x00000100, + SkipAABBsKHR = 0x00000200, + } + + public enum RayQueryIntersection + { + RayQueryCandidateIntersectionKHR = 0, + RayQueryCommittedIntersectionKHR = 1, + } + + public enum RayQueryCommittedIntersectionType + { + RayQueryCommittedIntersectionNoneKHR = 0, + RayQueryCommittedIntersectionTriangleKHR = 1, + RayQueryCommittedIntersectionGeneratedKHR = 2, + } + + public enum RayQueryCandidateIntersectionType + { + RayQueryCandidateIntersectionTriangleKHR = 0, + RayQueryCandidateIntersectionAABBKHR = 1, + } + + public enum Op + { + OpNop = 0, + OpUndef = 1, + OpSourceContinued = 2, + OpSource = 3, + OpSourceExtension = 4, + OpName = 5, + OpMemberName = 6, + OpString = 7, + OpLine = 8, + OpExtension = 10, + OpExtInstImport = 11, + OpExtInst = 12, + OpMemoryModel = 14, + OpEntryPoint = 15, + OpExecutionMode = 16, + OpCapability = 17, + OpTypeVoid = 19, + OpTypeBool = 20, + OpTypeInt = 21, + OpTypeFloat = 22, + OpTypeVector = 23, + OpTypeMatrix = 24, + OpTypeImage = 25, + OpTypeSampler = 26, + OpTypeSampledImage = 27, + OpTypeArray = 28, + OpTypeRuntimeArray = 29, + OpTypeStruct = 30, + OpTypeOpaque = 31, + OpTypePointer = 32, + OpTypeFunction = 33, + OpTypeEvent = 34, + OpTypeDeviceEvent = 35, + OpTypeReserveId = 36, + OpTypeQueue = 37, + OpTypePipe = 38, + OpTypeForwardPointer = 39, + OpConstantTrue = 41, + OpConstantFalse = 42, + OpConstant = 43, + OpConstantComposite = 44, + OpConstantSampler = 45, + OpConstantNull = 46, + OpSpecConstantTrue = 48, + OpSpecConstantFalse = 49, + OpSpecConstant = 50, + OpSpecConstantComposite = 51, + OpSpecConstantOp = 52, + OpFunction = 54, + OpFunctionParameter = 55, + OpFunctionEnd = 56, + OpFunctionCall = 57, + OpVariable = 59, + OpImageTexelPointer = 60, + OpLoad = 61, + OpStore = 62, + OpCopyMemory = 63, + OpCopyMemorySized = 64, + OpAccessChain = 65, + OpInBoundsAccessChain = 66, + OpPtrAccessChain = 67, + OpArrayLength = 68, + OpGenericPtrMemSemantics = 69, + OpInBoundsPtrAccessChain = 70, + OpDecorate = 71, + OpMemberDecorate = 72, + OpDecorationGroup = 73, + OpGroupDecorate = 74, + OpGroupMemberDecorate = 75, + OpVectorExtractDynamic = 77, + OpVectorInsertDynamic = 78, + OpVectorShuffle = 79, + OpCompositeConstruct = 80, + OpCompositeExtract = 81, + OpCompositeInsert = 82, + OpCopyObject = 83, + OpTranspose = 84, + OpSampledImage = 86, + OpImageSampleImplicitLod = 87, + OpImageSampleExplicitLod = 88, + OpImageSampleDrefImplicitLod = 89, + OpImageSampleDrefExplicitLod = 90, + OpImageSampleProjImplicitLod = 91, + OpImageSampleProjExplicitLod = 92, + OpImageSampleProjDrefImplicitLod = 93, + OpImageSampleProjDrefExplicitLod = 94, + OpImageFetch = 95, + OpImageGather = 96, + OpImageDrefGather = 97, + OpImageRead = 98, + OpImageWrite = 99, + OpImage = 100, + OpImageQueryFormat = 101, + OpImageQueryOrder = 102, + OpImageQuerySizeLod = 103, + OpImageQuerySize = 104, + OpImageQueryLod = 105, + OpImageQueryLevels = 106, + OpImageQuerySamples = 107, + OpConvertFToU = 109, + OpConvertFToS = 110, + OpConvertSToF = 111, + OpConvertUToF = 112, + OpUConvert = 113, + OpSConvert = 114, + OpFConvert = 115, + OpQuantizeToF16 = 116, + OpConvertPtrToU = 117, + OpSatConvertSToU = 118, + OpSatConvertUToS = 119, + OpConvertUToPtr = 120, + OpPtrCastToGeneric = 121, + OpGenericCastToPtr = 122, + OpGenericCastToPtrExplicit = 123, + OpBitcast = 124, + OpSNegate = 126, + OpFNegate = 127, + OpIAdd = 128, + OpFAdd = 129, + OpISub = 130, + OpFSub = 131, + OpIMul = 132, + OpFMul = 133, + OpUDiv = 134, + OpSDiv = 135, + OpFDiv = 136, + OpUMod = 137, + OpSRem = 138, + OpSMod = 139, + OpFRem = 140, + OpFMod = 141, + OpVectorTimesScalar = 142, + OpMatrixTimesScalar = 143, + OpVectorTimesMatrix = 144, + OpMatrixTimesVector = 145, + OpMatrixTimesMatrix = 146, + OpOuterProduct = 147, + OpDot = 148, + OpIAddCarry = 149, + OpISubBorrow = 150, + OpUMulExtended = 151, + OpSMulExtended = 152, + OpAny = 154, + OpAll = 155, + OpIsNan = 156, + OpIsInf = 157, + OpIsFinite = 158, + OpIsNormal = 159, + OpSignBitSet = 160, + OpLessOrGreater = 161, + OpOrdered = 162, + OpUnordered = 163, + OpLogicalEqual = 164, + OpLogicalNotEqual = 165, + OpLogicalOr = 166, + OpLogicalAnd = 167, + OpLogicalNot = 168, + OpSelect = 169, + OpIEqual = 170, + OpINotEqual = 171, + OpUGreaterThan = 172, + OpSGreaterThan = 173, + OpUGreaterThanEqual = 174, + OpSGreaterThanEqual = 175, + OpULessThan = 176, + OpSLessThan = 177, + OpULessThanEqual = 178, + OpSLessThanEqual = 179, + OpFOrdEqual = 180, + OpFUnordEqual = 181, + OpFOrdNotEqual = 182, + OpFUnordNotEqual = 183, + OpFOrdLessThan = 184, + OpFUnordLessThan = 185, + OpFOrdGreaterThan = 186, + OpFUnordGreaterThan = 187, + OpFOrdLessThanEqual = 188, + OpFUnordLessThanEqual = 189, + OpFOrdGreaterThanEqual = 190, + OpFUnordGreaterThanEqual = 191, + OpShiftRightLogical = 194, + OpShiftRightArithmetic = 195, + OpShiftLeftLogical = 196, + OpBitwiseOr = 197, + OpBitwiseXor = 198, + OpBitwiseAnd = 199, + OpNot = 200, + OpBitFieldInsert = 201, + OpBitFieldSExtract = 202, + OpBitFieldUExtract = 203, + OpBitReverse = 204, + OpBitCount = 205, + OpDPdx = 207, + OpDPdy = 208, + OpFwidth = 209, + OpDPdxFine = 210, + OpDPdyFine = 211, + OpFwidthFine = 212, + OpDPdxCoarse = 213, + OpDPdyCoarse = 214, + OpFwidthCoarse = 215, + OpEmitVertex = 218, + OpEndPrimitive = 219, + OpEmitStreamVertex = 220, + OpEndStreamPrimitive = 221, + OpControlBarrier = 224, + OpMemoryBarrier = 225, + OpAtomicLoad = 227, + OpAtomicStore = 228, + OpAtomicExchange = 229, + OpAtomicCompareExchange = 230, + OpAtomicCompareExchangeWeak = 231, + OpAtomicIIncrement = 232, + OpAtomicIDecrement = 233, + OpAtomicIAdd = 234, + OpAtomicISub = 235, + OpAtomicSMin = 236, + OpAtomicUMin = 237, + OpAtomicSMax = 238, + OpAtomicUMax = 239, + OpAtomicAnd = 240, + OpAtomicOr = 241, + OpAtomicXor = 242, + OpPhi = 245, + OpLoopMerge = 246, + OpSelectionMerge = 247, + OpLabel = 248, + OpBranch = 249, + OpBranchConditional = 250, + OpSwitch = 251, + OpKill = 252, + OpReturn = 253, + OpReturnValue = 254, + OpUnreachable = 255, + OpLifetimeStart = 256, + OpLifetimeStop = 257, + OpGroupAsyncCopy = 259, + OpGroupWaitEvents = 260, + OpGroupAll = 261, + OpGroupAny = 262, + OpGroupBroadcast = 263, + OpGroupIAdd = 264, + OpGroupFAdd = 265, + OpGroupFMin = 266, + OpGroupUMin = 267, + OpGroupSMin = 268, + OpGroupFMax = 269, + OpGroupUMax = 270, + OpGroupSMax = 271, + OpReadPipe = 274, + OpWritePipe = 275, + OpReservedReadPipe = 276, + OpReservedWritePipe = 277, + OpReserveReadPipePackets = 278, + OpReserveWritePipePackets = 279, + OpCommitReadPipe = 280, + OpCommitWritePipe = 281, + OpIsValidReserveId = 282, + OpGetNumPipePackets = 283, + OpGetMaxPipePackets = 284, + OpGroupReserveReadPipePackets = 285, + OpGroupReserveWritePipePackets = 286, + OpGroupCommitReadPipe = 287, + OpGroupCommitWritePipe = 288, + OpEnqueueMarker = 291, + OpEnqueueKernel = 292, + OpGetKernelNDrangeSubGroupCount = 293, + OpGetKernelNDrangeMaxSubGroupSize = 294, + OpGetKernelWorkGroupSize = 295, + OpGetKernelPreferredWorkGroupSizeMultiple = 296, + OpRetainEvent = 297, + OpReleaseEvent = 298, + OpCreateUserEvent = 299, + OpIsValidEvent = 300, + OpSetUserEventStatus = 301, + OpCaptureEventProfilingInfo = 302, + OpGetDefaultQueue = 303, + OpBuildNDRange = 304, + OpImageSparseSampleImplicitLod = 305, + OpImageSparseSampleExplicitLod = 306, + OpImageSparseSampleDrefImplicitLod = 307, + OpImageSparseSampleDrefExplicitLod = 308, + OpImageSparseSampleProjImplicitLod = 309, + OpImageSparseSampleProjExplicitLod = 310, + OpImageSparseSampleProjDrefImplicitLod = 311, + OpImageSparseSampleProjDrefExplicitLod = 312, + OpImageSparseFetch = 313, + OpImageSparseGather = 314, + OpImageSparseDrefGather = 315, + OpImageSparseTexelsResident = 316, + OpNoLine = 317, + OpAtomicFlagTestAndSet = 318, + OpAtomicFlagClear = 319, + OpImageSparseRead = 320, + OpSizeOf = 321, + OpTypePipeStorage = 322, + OpConstantPipeStorage = 323, + OpCreatePipeFromPipeStorage = 324, + OpGetKernelLocalSizeForSubgroupCount = 325, + OpGetKernelMaxNumSubgroups = 326, + OpTypeNamedBarrier = 327, + OpNamedBarrierInitialize = 328, + OpMemoryNamedBarrier = 329, + OpModuleProcessed = 330, + OpExecutionModeId = 331, + OpDecorateId = 332, + OpGroupNonUniformElect = 333, + OpGroupNonUniformAll = 334, + OpGroupNonUniformAny = 335, + OpGroupNonUniformAllEqual = 336, + OpGroupNonUniformBroadcast = 337, + OpGroupNonUniformBroadcastFirst = 338, + OpGroupNonUniformBallot = 339, + OpGroupNonUniformInverseBallot = 340, + OpGroupNonUniformBallotBitExtract = 341, + OpGroupNonUniformBallotBitCount = 342, + OpGroupNonUniformBallotFindLSB = 343, + OpGroupNonUniformBallotFindMSB = 344, + OpGroupNonUniformShuffle = 345, + OpGroupNonUniformShuffleXor = 346, + OpGroupNonUniformShuffleUp = 347, + OpGroupNonUniformShuffleDown = 348, + OpGroupNonUniformIAdd = 349, + OpGroupNonUniformFAdd = 350, + OpGroupNonUniformIMul = 351, + OpGroupNonUniformFMul = 352, + OpGroupNonUniformSMin = 353, + OpGroupNonUniformUMin = 354, + OpGroupNonUniformFMin = 355, + OpGroupNonUniformSMax = 356, + OpGroupNonUniformUMax = 357, + OpGroupNonUniformFMax = 358, + OpGroupNonUniformBitwiseAnd = 359, + OpGroupNonUniformBitwiseOr = 360, + OpGroupNonUniformBitwiseXor = 361, + OpGroupNonUniformLogicalAnd = 362, + OpGroupNonUniformLogicalOr = 363, + OpGroupNonUniformLogicalXor = 364, + OpGroupNonUniformQuadBroadcast = 365, + OpGroupNonUniformQuadSwap = 366, + OpCopyLogical = 400, + OpPtrEqual = 401, + OpPtrNotEqual = 402, + OpPtrDiff = 403, + OpSubgroupBallotKHR = 4421, + OpSubgroupFirstInvocationKHR = 4422, + OpSubgroupAllKHR = 4428, + OpSubgroupAnyKHR = 4429, + OpSubgroupAllEqualKHR = 4430, + OpSubgroupReadInvocationKHR = 4432, + OpTypeRayQueryProvisionalKHR = 4472, + OpRayQueryInitializeKHR = 4473, + OpRayQueryTerminateKHR = 4474, + OpRayQueryGenerateIntersectionKHR = 4475, + OpRayQueryConfirmIntersectionKHR = 4476, + OpRayQueryProceedKHR = 4477, + OpRayQueryGetIntersectionTypeKHR = 4479, + OpGroupIAddNonUniformAMD = 5000, + OpGroupFAddNonUniformAMD = 5001, + OpGroupFMinNonUniformAMD = 5002, + OpGroupUMinNonUniformAMD = 5003, + OpGroupSMinNonUniformAMD = 5004, + OpGroupFMaxNonUniformAMD = 5005, + OpGroupUMaxNonUniformAMD = 5006, + OpGroupSMaxNonUniformAMD = 5007, + OpFragmentMaskFetchAMD = 5011, + OpFragmentFetchAMD = 5012, + OpReadClockKHR = 5056, + OpImageSampleFootprintNV = 5283, + OpGroupNonUniformPartitionNV = 5296, + OpWritePackedPrimitiveIndices4x8NV = 5299, + OpReportIntersectionKHR = 5334, + OpReportIntersectionNV = 5334, + OpIgnoreIntersectionKHR = 5335, + OpIgnoreIntersectionNV = 5335, + OpTerminateRayKHR = 5336, + OpTerminateRayNV = 5336, + OpTraceNV = 5337, + OpTraceRayKHR = 5337, + OpTypeAccelerationStructureKHR = 5341, + OpTypeAccelerationStructureNV = 5341, + OpExecuteCallableKHR = 5344, + OpExecuteCallableNV = 5344, + OpTypeCooperativeMatrixNV = 5358, + OpCooperativeMatrixLoadNV = 5359, + OpCooperativeMatrixStoreNV = 5360, + OpCooperativeMatrixMulAddNV = 5361, + OpCooperativeMatrixLengthNV = 5362, + OpBeginInvocationInterlockEXT = 5364, + OpEndInvocationInterlockEXT = 5365, + OpDemoteToHelperInvocationEXT = 5380, + OpIsHelperInvocationEXT = 5381, + OpSubgroupShuffleINTEL = 5571, + OpSubgroupShuffleDownINTEL = 5572, + OpSubgroupShuffleUpINTEL = 5573, + OpSubgroupShuffleXorINTEL = 5574, + OpSubgroupBlockReadINTEL = 5575, + OpSubgroupBlockWriteINTEL = 5576, + OpSubgroupImageBlockReadINTEL = 5577, + OpSubgroupImageBlockWriteINTEL = 5578, + OpSubgroupImageMediaBlockReadINTEL = 5580, + OpSubgroupImageMediaBlockWriteINTEL = 5581, + OpUCountLeadingZerosINTEL = 5585, + OpUCountTrailingZerosINTEL = 5586, + OpAbsISubINTEL = 5587, + OpAbsUSubINTEL = 5588, + OpIAddSatINTEL = 5589, + OpUAddSatINTEL = 5590, + OpIAverageINTEL = 5591, + OpUAverageINTEL = 5592, + OpIAverageRoundedINTEL = 5593, + OpUAverageRoundedINTEL = 5594, + OpISubSatINTEL = 5595, + OpUSubSatINTEL = 5596, + OpIMul32x16INTEL = 5597, + OpUMul32x16INTEL = 5598, + OpDecorateString = 5632, + OpDecorateStringGOOGLE = 5632, + OpMemberDecorateString = 5633, + OpMemberDecorateStringGOOGLE = 5633, + OpVmeImageINTEL = 5699, + OpTypeVmeImageINTEL = 5700, + OpTypeAvcImePayloadINTEL = 5701, + OpTypeAvcRefPayloadINTEL = 5702, + OpTypeAvcSicPayloadINTEL = 5703, + OpTypeAvcMcePayloadINTEL = 5704, + OpTypeAvcMceResultINTEL = 5705, + OpTypeAvcImeResultINTEL = 5706, + OpTypeAvcImeResultSingleReferenceStreamoutINTEL = 5707, + OpTypeAvcImeResultDualReferenceStreamoutINTEL = 5708, + OpTypeAvcImeSingleReferenceStreaminINTEL = 5709, + OpTypeAvcImeDualReferenceStreaminINTEL = 5710, + OpTypeAvcRefResultINTEL = 5711, + OpTypeAvcSicResultINTEL = 5712, + OpSubgroupAvcMceGetDefaultInterBaseMultiReferencePenaltyINTEL = 5713, + OpSubgroupAvcMceSetInterBaseMultiReferencePenaltyINTEL = 5714, + OpSubgroupAvcMceGetDefaultInterShapePenaltyINTEL = 5715, + OpSubgroupAvcMceSetInterShapePenaltyINTEL = 5716, + OpSubgroupAvcMceGetDefaultInterDirectionPenaltyINTEL = 5717, + OpSubgroupAvcMceSetInterDirectionPenaltyINTEL = 5718, + OpSubgroupAvcMceGetDefaultIntraLumaShapePenaltyINTEL = 5719, + OpSubgroupAvcMceGetDefaultInterMotionVectorCostTableINTEL = 5720, + OpSubgroupAvcMceGetDefaultHighPenaltyCostTableINTEL = 5721, + OpSubgroupAvcMceGetDefaultMediumPenaltyCostTableINTEL = 5722, + OpSubgroupAvcMceGetDefaultLowPenaltyCostTableINTEL = 5723, + OpSubgroupAvcMceSetMotionVectorCostFunctionINTEL = 5724, + OpSubgroupAvcMceGetDefaultIntraLumaModePenaltyINTEL = 5725, + OpSubgroupAvcMceGetDefaultNonDcLumaIntraPenaltyINTEL = 5726, + OpSubgroupAvcMceGetDefaultIntraChromaModeBasePenaltyINTEL = 5727, + OpSubgroupAvcMceSetAcOnlyHaarINTEL = 5728, + OpSubgroupAvcMceSetSourceInterlacedFieldPolarityINTEL = 5729, + OpSubgroupAvcMceSetSingleReferenceInterlacedFieldPolarityINTEL = 5730, + OpSubgroupAvcMceSetDualReferenceInterlacedFieldPolaritiesINTEL = 5731, + OpSubgroupAvcMceConvertToImePayloadINTEL = 5732, + OpSubgroupAvcMceConvertToImeResultINTEL = 5733, + OpSubgroupAvcMceConvertToRefPayloadINTEL = 5734, + OpSubgroupAvcMceConvertToRefResultINTEL = 5735, + OpSubgroupAvcMceConvertToSicPayloadINTEL = 5736, + OpSubgroupAvcMceConvertToSicResultINTEL = 5737, + OpSubgroupAvcMceGetMotionVectorsINTEL = 5738, + OpSubgroupAvcMceGetInterDistortionsINTEL = 5739, + OpSubgroupAvcMceGetBestInterDistortionsINTEL = 5740, + OpSubgroupAvcMceGetInterMajorShapeINTEL = 5741, + OpSubgroupAvcMceGetInterMinorShapeINTEL = 5742, + OpSubgroupAvcMceGetInterDirectionsINTEL = 5743, + OpSubgroupAvcMceGetInterMotionVectorCountINTEL = 5744, + OpSubgroupAvcMceGetInterReferenceIdsINTEL = 5745, + OpSubgroupAvcMceGetInterReferenceInterlacedFieldPolaritiesINTEL = 5746, + OpSubgroupAvcImeInitializeINTEL = 5747, + OpSubgroupAvcImeSetSingleReferenceINTEL = 5748, + OpSubgroupAvcImeSetDualReferenceINTEL = 5749, + OpSubgroupAvcImeRefWindowSizeINTEL = 5750, + OpSubgroupAvcImeAdjustRefOffsetINTEL = 5751, + OpSubgroupAvcImeConvertToMcePayloadINTEL = 5752, + OpSubgroupAvcImeSetMaxMotionVectorCountINTEL = 5753, + OpSubgroupAvcImeSetUnidirectionalMixDisableINTEL = 5754, + OpSubgroupAvcImeSetEarlySearchTerminationThresholdINTEL = 5755, + OpSubgroupAvcImeSetWeightedSadINTEL = 5756, + OpSubgroupAvcImeEvaluateWithSingleReferenceINTEL = 5757, + OpSubgroupAvcImeEvaluateWithDualReferenceINTEL = 5758, + OpSubgroupAvcImeEvaluateWithSingleReferenceStreaminINTEL = 5759, + OpSubgroupAvcImeEvaluateWithDualReferenceStreaminINTEL = 5760, + OpSubgroupAvcImeEvaluateWithSingleReferenceStreamoutINTEL = 5761, + OpSubgroupAvcImeEvaluateWithDualReferenceStreamoutINTEL = 5762, + OpSubgroupAvcImeEvaluateWithSingleReferenceStreaminoutINTEL = 5763, + OpSubgroupAvcImeEvaluateWithDualReferenceStreaminoutINTEL = 5764, + OpSubgroupAvcImeConvertToMceResultINTEL = 5765, + OpSubgroupAvcImeGetSingleReferenceStreaminINTEL = 5766, + OpSubgroupAvcImeGetDualReferenceStreaminINTEL = 5767, + OpSubgroupAvcImeStripSingleReferenceStreamoutINTEL = 5768, + OpSubgroupAvcImeStripDualReferenceStreamoutINTEL = 5769, + OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeMotionVectorsINTEL = 5770, + OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeDistortionsINTEL = 5771, + OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeReferenceIdsINTEL = 5772, + OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeMotionVectorsINTEL = 5773, + OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeDistortionsINTEL = 5774, + OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeReferenceIdsINTEL = 5775, + OpSubgroupAvcImeGetBorderReachedINTEL = 5776, + OpSubgroupAvcImeGetTruncatedSearchIndicationINTEL = 5777, + OpSubgroupAvcImeGetUnidirectionalEarlySearchTerminationINTEL = 5778, + OpSubgroupAvcImeGetWeightingPatternMinimumMotionVectorINTEL = 5779, + OpSubgroupAvcImeGetWeightingPatternMinimumDistortionINTEL = 5780, + OpSubgroupAvcFmeInitializeINTEL = 5781, + OpSubgroupAvcBmeInitializeINTEL = 5782, + OpSubgroupAvcRefConvertToMcePayloadINTEL = 5783, + OpSubgroupAvcRefSetBidirectionalMixDisableINTEL = 5784, + OpSubgroupAvcRefSetBilinearFilterEnableINTEL = 5785, + OpSubgroupAvcRefEvaluateWithSingleReferenceINTEL = 5786, + OpSubgroupAvcRefEvaluateWithDualReferenceINTEL = 5787, + OpSubgroupAvcRefEvaluateWithMultiReferenceINTEL = 5788, + OpSubgroupAvcRefEvaluateWithMultiReferenceInterlacedINTEL = 5789, + OpSubgroupAvcRefConvertToMceResultINTEL = 5790, + OpSubgroupAvcSicInitializeINTEL = 5791, + OpSubgroupAvcSicConfigureSkcINTEL = 5792, + OpSubgroupAvcSicConfigureIpeLumaINTEL = 5793, + OpSubgroupAvcSicConfigureIpeLumaChromaINTEL = 5794, + OpSubgroupAvcSicGetMotionVectorMaskINTEL = 5795, + OpSubgroupAvcSicConvertToMcePayloadINTEL = 5796, + OpSubgroupAvcSicSetIntraLumaShapePenaltyINTEL = 5797, + OpSubgroupAvcSicSetIntraLumaModeCostFunctionINTEL = 5798, + OpSubgroupAvcSicSetIntraChromaModeCostFunctionINTEL = 5799, + OpSubgroupAvcSicSetBilinearFilterEnableINTEL = 5800, + OpSubgroupAvcSicSetSkcForwardTransformEnableINTEL = 5801, + OpSubgroupAvcSicSetBlockBasedRawSkipSadINTEL = 5802, + OpSubgroupAvcSicEvaluateIpeINTEL = 5803, + OpSubgroupAvcSicEvaluateWithSingleReferenceINTEL = 5804, + OpSubgroupAvcSicEvaluateWithDualReferenceINTEL = 5805, + OpSubgroupAvcSicEvaluateWithMultiReferenceINTEL = 5806, + OpSubgroupAvcSicEvaluateWithMultiReferenceInterlacedINTEL = 5807, + OpSubgroupAvcSicConvertToMceResultINTEL = 5808, + OpSubgroupAvcSicGetIpeLumaShapeINTEL = 5809, + OpSubgroupAvcSicGetBestIpeLumaDistortionINTEL = 5810, + OpSubgroupAvcSicGetBestIpeChromaDistortionINTEL = 5811, + OpSubgroupAvcSicGetPackedIpeLumaModesINTEL = 5812, + OpSubgroupAvcSicGetIpeChromaModeINTEL = 5813, + OpSubgroupAvcSicGetPackedSkcLumaCountThresholdINTEL = 5814, + OpSubgroupAvcSicGetPackedSkcLumaSumThresholdINTEL = 5815, + OpSubgroupAvcSicGetInterRawSadsINTEL = 5816, + OpRayQueryGetRayTMinKHR = 6016, + OpRayQueryGetRayFlagsKHR = 6017, + OpRayQueryGetIntersectionTKHR = 6018, + OpRayQueryGetIntersectionInstanceCustomIndexKHR = 6019, + OpRayQueryGetIntersectionInstanceIdKHR = 6020, + OpRayQueryGetIntersectionInstanceShaderBindingTableRecordOffsetKHR = 6021, + OpRayQueryGetIntersectionGeometryIndexKHR = 6022, + OpRayQueryGetIntersectionPrimitiveIndexKHR = 6023, + OpRayQueryGetIntersectionBarycentricsKHR = 6024, + OpRayQueryGetIntersectionFrontFaceKHR = 6025, + OpRayQueryGetIntersectionCandidateAABBOpaqueKHR = 6026, + OpRayQueryGetIntersectionObjectRayDirectionKHR = 6027, + OpRayQueryGetIntersectionObjectRayOriginKHR = 6028, + OpRayQueryGetWorldRayDirectionKHR = 6029, + OpRayQueryGetWorldRayOriginKHR = 6030, + OpRayQueryGetIntersectionObjectToWorldKHR = 6031, + OpRayQueryGetIntersectionWorldToObjectKHR = 6032, + } + } +} + diff --git a/SpvGen/Instruction.cs b/SpvGen/Instruction.cs new file mode 100644 index 000000000..f93fb34fb --- /dev/null +++ b/SpvGen/Instruction.cs @@ -0,0 +1,211 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; + +namespace Spv.Generator +{ + public sealed class Instruction : Operand, IEquatable + { + public const uint InvalidId = uint.MaxValue; + + public Specification.Op Opcode { get; private set; } + private Instruction _resultType; + public List _operands; + + public uint Id { get; set; } + + public Instruction(Specification.Op opcode, uint id = InvalidId, Instruction resultType = null) + { + Opcode = opcode; + Id = id; + _resultType = resultType; + + _operands = new List(); + } + + public void SetId(uint id) + { + Id = id; + } + + public OperandType Type => OperandType.Instruction; + + public ushort GetTotalWordCount() + { + ushort result = WordCount; + + if (Id != InvalidId) + { + result++; + } + + if (_resultType != null) + { + result += _resultType.WordCount; + } + + foreach (Operand operand in _operands) + { + result += operand.WordCount; + } + + return result; + } + + public ushort WordCount => 1; + + private void AddOperand(Operand value) + { + Debug.Assert(value != null); + _operands.Add(value); + } + + public void AddOperand(Operand[] value) + { + foreach (Operand instruction in value) + { + AddOperand(instruction); + } + } + + public void AddOperand(LiteralInteger[] value) + { + foreach (LiteralInteger instruction in value) + { + AddOperand(instruction); + } + } + + public void AddOperand(LiteralInteger value) + { + AddOperand((Operand)value); + } + + public void AddOperand(Instruction[] value) + { + foreach (Instruction instruction in value) + { + AddOperand(instruction); + } + } + + public void AddOperand(Instruction value) + { + AddOperand((Operand)value); + } + + public void AddOperand(string value) + { + AddOperand(new LiteralString(value)); + } + + public void AddOperand(T value) where T: struct + { + if (!typeof(T).IsPrimitive && !typeof(T).IsEnum) + { + throw new InvalidOperationException(); + } + + AddOperand(LiteralInteger.CreateForEnum(value)); + } + + public void Write(Stream stream) + { + BinaryWriter writer = new BinaryWriter(stream); + + // Word 0 + writer.Write((ushort)Opcode); + writer.Write(GetTotalWordCount()); + + _resultType?.WriteOperand(stream); + + if (Id != InvalidId) + { + writer.Write(Id); + } + + foreach (Operand operand in _operands) + { + operand.WriteOperand(stream); + } + } + + public void WriteOperand(Stream stream) + { + Debug.Assert(Id != InvalidId); + + if (Id == InvalidId) + { + string methodToCall; + + if (Opcode == Specification.Op.OpVariable) + { + methodToCall = "AddLocalVariable or AddGlobalVariable"; + } + else if (Opcode == Specification.Op.OpLabel) + { + methodToCall = "AddLabel"; + } + else + { + throw new InvalidOperationException("Internal error"); + } + + throw new InvalidOperationException($"Id wasn't bound to the module, please make sure to call {methodToCall}"); + } + + stream.Write(BitConverter.GetBytes(Id)); + } + + public override bool Equals(object obj) + { + return obj is Instruction instruction && Equals(instruction); + } + + public bool Equals(Instruction cmpObj) + { + bool result = Type == cmpObj.Type && Id == cmpObj.Id; + + if (result) + { + if (_resultType != null && cmpObj._resultType != null) + { + result &= _resultType.Equals(cmpObj._resultType); + } + else if (_resultType != null || cmpObj._resultType != null) + { + return false; + } + } + + if (result) + { + result &= EqualsContent(cmpObj); + } + + return result; + } + + public bool EqualsResultType(Instruction cmpObj) + { + return _resultType.Opcode == cmpObj._resultType.Opcode && _resultType.EqualsContent(cmpObj._resultType); + } + + public bool EqualsContent(Instruction cmpObj) + { + return _operands.SequenceEqual(cmpObj._operands); + } + + public override int GetHashCode() + { + return HashCode.Combine(Opcode, Id, _resultType, _operands); + } + + public bool Equals(Operand obj) + { + return obj is Instruction instruction && Equals(instruction); + } + } +} diff --git a/SpvGen/LICENSE b/SpvGen/LICENSE new file mode 100644 index 000000000..31aa79387 --- /dev/null +++ b/SpvGen/LICENSE @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/SpvGen/LiteralInteger.cs b/SpvGen/LiteralInteger.cs new file mode 100644 index 000000000..7568cc1bc --- /dev/null +++ b/SpvGen/LiteralInteger.cs @@ -0,0 +1,81 @@ +using System; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Spv.Generator +{ + public class LiteralInteger : Operand, IEquatable + { + public OperandType Type => OperandType.Number; + + private enum IntegerType + { + UInt32, + Int32, + UInt64, + Int64, + Float32, + Float64, + } + + private IntegerType _integerType; + + private byte[] _data; + + private LiteralInteger(byte[] data, IntegerType integerType) + { + _data = data; + _integerType = integerType; + } + + public static implicit operator LiteralInteger(int value) => Create(value, IntegerType.Int32); + public static implicit operator LiteralInteger(uint value) => Create(value, IntegerType.UInt32); + public static implicit operator LiteralInteger(long value) => Create(value, IntegerType.Int64); + public static implicit operator LiteralInteger(ulong value) => Create(value, IntegerType.UInt64); + public static implicit operator LiteralInteger(float value) => Create(value, IntegerType.Float32); + public static implicit operator LiteralInteger(double value) => Create(value, IntegerType.Float64); + public static implicit operator LiteralInteger(Enum value) => Create((int)Convert.ChangeType(value, typeof(int)), IntegerType.Int32); + + // NOTE: this is not in the standard, but this is some syntax sugar useful in some instructions (TypeInt ect) + public static implicit operator LiteralInteger(bool value) => Create(Convert.ToInt32(value), IntegerType.Int32); + + + public static LiteralInteger CreateForEnum(T value) where T : struct + { + return Create(value, IntegerType.Int32); + } + + private static LiteralInteger Create(T value, IntegerType integerType) where T: struct + { + return new LiteralInteger(MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1)).ToArray(), integerType); + } + + public ushort WordCount => (ushort)(_data.Length / 4); + + public void WriteOperand(Stream stream) + { + stream.Write(_data); + } + + public override bool Equals(object obj) + { + return obj is LiteralInteger literalInteger && Equals(literalInteger); + } + + public bool Equals(LiteralInteger cmpObj) + { + return Type == cmpObj.Type && _integerType == cmpObj._integerType && _data.SequenceEqual(cmpObj._data); + } + + public override int GetHashCode() + { + return HashCode.Combine(Type, _data); + } + + public bool Equals(Operand obj) + { + return obj is LiteralInteger literalInteger && Equals(literalInteger); + } + } +} diff --git a/SpvGen/LiteralString.cs b/SpvGen/LiteralString.cs new file mode 100644 index 000000000..8d27e407e --- /dev/null +++ b/SpvGen/LiteralString.cs @@ -0,0 +1,51 @@ +using System; +using System.IO; +using System.Text; + +namespace Spv.Generator +{ + public class LiteralString : Operand, IEquatable + { + public OperandType Type => OperandType.String; + + private string _value; + + public LiteralString(string value) + { + _value = value; + } + + public ushort WordCount => (ushort)(_value.Length / 4 + 1); + + public void WriteOperand(Stream stream) + { + byte[] rawValue = Encoding.ASCII.GetBytes(_value); + + stream.Write(rawValue); + + int paddingSize = 4 - (rawValue.Length % 4); + + stream.Write(new byte[paddingSize]); + } + + public override bool Equals(object obj) + { + return obj is LiteralString literalString && Equals(literalString); + } + + public bool Equals(LiteralString cmpObj) + { + return Type == cmpObj.Type && _value.Equals(cmpObj._value); + } + + public override int GetHashCode() + { + return HashCode.Combine(Type, _value); + } + + public bool Equals(Operand obj) + { + return obj is LiteralString literalString && Equals(literalString); + } + } +} diff --git a/SpvGen/Module.cs b/SpvGen/Module.cs new file mode 100644 index 000000000..9b7e2060d --- /dev/null +++ b/SpvGen/Module.cs @@ -0,0 +1,321 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using static Spv.Specification; + +namespace Spv.Generator +{ + public partial class Module + { + // TODO: register to SPIR-V registry + private const int GeneratorId = 0; + + private readonly uint _version; + + private uint _bound; + + // Follow spec order here why keeping it as dumb as possible. + private List _capabilities; + private List _extensions; + private List _extInstImports; + private AddressingModel _addressingModel; + private MemoryModel _memoryModel; + + private List _entrypoints; + private List _executionModes; + private List _debug; + private List _annotations; + + // In the declaration block. + private List _typeDeclarations; + // In the declaration block. + private List _globals; + // In the declaration block, for function that aren't defined in the module. + private List _functionsDeclarations; + + private List _functionsDefinitions; + + public Module(uint version) + { + _version = version; + _bound = 1; + _capabilities = new List(); + _extensions = new List(); + _extInstImports = new List(); + _addressingModel = AddressingModel.Logical; + _memoryModel = MemoryModel.Simple; + _entrypoints = new List(); + _executionModes = new List(); + _debug = new List(); + _annotations = new List(); + _typeDeclarations = new List(); + _globals = new List(); + _functionsDeclarations = new List(); + _functionsDefinitions = new List(); + } + + private uint GetNewId() + { + return _bound++; + } + + public void AddCapability(Capability capability) + { + _capabilities.Add(capability); + } + + public void AddExtension(string extension) + { + _extensions.Add(extension); + } + + public Instruction AddExtInstImport(string import) + { + Instruction instruction = new Instruction(Op.OpExtInstImport); + instruction.AddOperand(import); + + foreach (Instruction extInstImport in _extInstImports) + { + if (extInstImport.Opcode == Op.OpExtInstImport && extInstImport.EqualsContent(instruction)) + { + // update the duplicate instance to use the good id so it ends up being encoded right. + return extInstImport; + } + } + + instruction.SetId(GetNewId()); + + _extInstImports.Add(instruction); + + return instruction; + } + + private void AddTypeDeclaration(Instruction instruction, bool forceIdAllocation) + { + if (!forceIdAllocation) + { + foreach (Instruction typeDeclaration in _typeDeclarations) + { + if (typeDeclaration.Opcode == instruction.Opcode && typeDeclaration.EqualsContent(instruction)) + { + // update the duplicate instance to use the good id so it ends up being encoded right. + instruction.SetId(typeDeclaration.Id); + + return; + } + } + } + + instruction.SetId(GetNewId()); + + _typeDeclarations.Add(instruction); + } + + public void AddEntryPoint(ExecutionModel executionModel, Instruction function, string name, params Instruction[] interfaces) + { + Debug.Assert(function.Opcode == Op.OpFunction); + + Instruction entryPoint = new Instruction(Op.OpEntryPoint); + + entryPoint.AddOperand(executionModel); + entryPoint.AddOperand(function); + entryPoint.AddOperand(name); + entryPoint.AddOperand(interfaces); + + _entrypoints.Add(entryPoint); + } + + public void AddExecutionMode(Instruction function, ExecutionMode mode, params Operand[] parameters) + { + Debug.Assert(function.Opcode == Op.OpFunction); + + Instruction executionModeInstruction = new Instruction(Op.OpExecutionMode); + + executionModeInstruction.AddOperand(function); + executionModeInstruction.AddOperand(mode); + executionModeInstruction.AddOperand(parameters); + + _executionModes.Add(executionModeInstruction); + } + + private void AddToFunctionDefinitions(Instruction instruction) + { + Debug.Assert(instruction.Opcode != Op.OpTypeInt); + _functionsDefinitions.Add(instruction); + } + + private void AddAnnotation(Instruction annotation) + { + _annotations.Add(annotation); + } + + private void AddDebug(Instruction debug) + { + _debug.Add(debug); + } + + public void AddLabel(Instruction label) + { + Debug.Assert(label.Opcode == Op.OpLabel); + + label.SetId(GetNewId()); + + AddToFunctionDefinitions(label); + } + + public void AddLocalVariable(Instruction variable) + { + // TODO: ensure it has the local modifier + Debug.Assert(variable.Opcode == Op.OpVariable); + + variable.SetId(GetNewId()); + + AddToFunctionDefinitions(variable); + } + + public void AddGlobalVariable(Instruction variable) + { + // TODO: ensure it has the global modifier + // TODO: all constants opcodes (OpSpecXXX and the rest of the OpConstantXXX) + Debug.Assert(variable.Opcode == Op.OpVariable); + + variable.SetId(GetNewId()); + + _globals.Add(variable); + } + + private void AddConstant(Instruction constant) + { + Debug.Assert(constant.Opcode == Op.OpConstant || + constant.Opcode == Op.OpConstantFalse || + constant.Opcode == Op.OpConstantTrue || + constant.Opcode == Op.OpConstantNull || + constant.Opcode == Op.OpConstantComposite); + + foreach (Instruction global in _globals) + { + if (global.Opcode == constant.Opcode && global.EqualsResultType(constant) && global.EqualsContent(constant)) + { + // update the duplicate instance to use the good id so it ends up being encoded right. + constant.SetId(global.Id); + + return; + } + } + + constant.SetId(GetNewId()); + + _globals.Add(constant); + } + + public void SetMemoryModel(AddressingModel addressingModel, MemoryModel memoryModel) + { + _addressingModel = addressingModel; + _memoryModel = memoryModel; + } + + protected virtual void Construct() + { + throw new NotSupportedException("Construct should be overriden."); + } + + public byte[] Generate() + { + Construct(); + + using (MemoryStream stream = new MemoryStream()) + { + BinaryWriter writer = new BinaryWriter(stream); + + // Header + writer.Write(MagicNumber); + writer.Write(_version); + writer.Write(GeneratorId); + writer.Write(_bound); + writer.Write(0u); + + // 1. + foreach (Capability capability in _capabilities) + { + Instruction capabilityInstruction = new Instruction(Op.OpCapability); + + capabilityInstruction.AddOperand(capability); + capabilityInstruction.Write(stream); + } + + // 2. + foreach (string extension in _extensions) + { + Instruction extensionInstruction = new Instruction(Op.OpExtension); + + extensionInstruction.AddOperand(extension); + extensionInstruction.Write(stream); + } + + // 3. + foreach (Instruction extInstImport in _extInstImports) + { + extInstImport.Write(stream); + } + + // 4. + Instruction memoryModelInstruction = new Instruction(Op.OpMemoryModel); + memoryModelInstruction.AddOperand(_addressingModel); + memoryModelInstruction.AddOperand(_memoryModel); + memoryModelInstruction.Write(stream); + + // 5. + foreach (Instruction entrypoint in _entrypoints) + { + entrypoint.Write(stream); + } + + // 6. + foreach (Instruction executionMode in _executionModes) + { + executionMode.Write(stream); + } + + // 7. + // TODO: order debug information correclty. + foreach (Instruction debug in _debug) + { + debug.Write(stream); + } + + // 8. + foreach (Instruction annotation in _annotations) + { + annotation.Write(stream); + } + + // Ensure that everything is in the right order in the declarations section + List declarations = new List(); + declarations.AddRange(_typeDeclarations); + declarations.AddRange(_globals); + declarations.Sort((Instruction x, Instruction y) => x.Id.CompareTo(y.Id)); + + // 9. + foreach (Instruction declaration in declarations) + { + declaration.Write(stream); + } + + // 10. + foreach (Instruction functionDeclaration in _functionsDeclarations) + { + functionDeclaration.Write(stream); + } + + // 11. + foreach (Instruction functionDefinition in _functionsDefinitions) + { + functionDefinition.Write(stream); + } + + return stream.ToArray(); + } + } + } +} diff --git a/SpvGen/Operand.cs b/SpvGen/Operand.cs new file mode 100644 index 000000000..b180105a0 --- /dev/null +++ b/SpvGen/Operand.cs @@ -0,0 +1,14 @@ +using System; +using System.IO; + +namespace Spv.Generator +{ + public interface Operand : IEquatable + { + OperandType Type { get; } + + ushort WordCount { get; } + + void WriteOperand(Stream stream); + } +} diff --git a/SpvGen/OperandType.cs b/SpvGen/OperandType.cs new file mode 100644 index 000000000..06e8e1fb3 --- /dev/null +++ b/SpvGen/OperandType.cs @@ -0,0 +1,10 @@ +namespace Spv.Generator +{ + public enum OperandType + { + Invalid, + Number, + String, + Instruction, + } +} diff --git a/SpvGen/Spv.Generator.csproj b/SpvGen/Spv.Generator.csproj new file mode 100644 index 000000000..5bd2638c6 --- /dev/null +++ b/SpvGen/Spv.Generator.csproj @@ -0,0 +1,12 @@ + + + + Library + netcoreapp3.1 + 8.0 + win-x64;osx-x64;linux-x64 + + + + +