WIP Vulkan implementation

This commit is contained in:
gdkchan 2021-08-12 03:09:56 -03:00 committed by riperiperi
parent 68f9091870
commit 4513e8b226
165 changed files with 23123 additions and 399 deletions

BIN
Ryujinx.Ava/entryStorage Normal file

Binary file not shown.

BIN
Ryujinx.Ava/nodeStorage Normal file

Binary file not shown.

View file

@ -0,0 +1,8 @@
namespace Ryujinx.Common.Configuration
{
public enum GraphicsBackend
{
Vulkan,
OpenGl
}
}

View file

@ -165,6 +165,120 @@ namespace Ryujinx.Graphics.GAL
public static class FormatExtensions
{
/// <summary>
/// Checks if the texture format is valid to use as image format.
/// </summary>
/// <param name="format">Texture format</param>
/// <returns>True if the texture can be used as image, false otherwise</returns>
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;
}
/// <summary>
/// Checks if the texture format is valid to use as render target color format.
/// </summary>
/// <param name="format">Texture format</param>
/// <returns>True if the texture can be used as render target, false otherwise</returns>
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;
}
/// <summary>
/// Checks if the texture format is an ASTC format.
/// </summary>

View file

@ -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;
}
}
}

View file

@ -77,15 +77,13 @@ namespace Ryujinx.Graphics.GAL
void SetRenderTargetColorMasks(ReadOnlySpan<uint> 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<Rectangle<int>> regions);
void SetStencilTest(StencilTestDescriptor stencilTest);
void SetStorageBuffers(int first, ReadOnlySpan<BufferRange> buffers);
void SetTexture(int binding, ITexture texture);
void SetTextureAndSampler(int binding, ITexture texture, ISampler sampler);
void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers);
void SetUniformBuffers(int first, ReadOnlySpan<BufferRange> buffers);

View file

@ -30,6 +30,7 @@ namespace Ryujinx.Graphics.GAL
ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size);
Capabilities GetCapabilities();
HardwareInfo GetHardwareInfo();
IProgram LoadProgramBinary(byte[] programBinary, bool hasFragmentShader, ShaderInfo info);

View file

@ -199,14 +199,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
SetRenderTargetScaleCommand.Run(ref GetCommand<SetRenderTargetScaleCommand>(memory), threaded, renderer);
_lookup[(int)CommandType.SetRenderTargets] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
SetRenderTargetsCommand.Run(ref GetCommand<SetRenderTargetsCommand>(memory), threaded, renderer);
_lookup[(int)CommandType.SetSampler] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
SetSamplerCommand.Run(ref GetCommand<SetSamplerCommand>(memory), threaded, renderer);
_lookup[(int)CommandType.SetScissor] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
SetScissorCommand.Run(ref GetCommand<SetScissorCommand>(memory), threaded, renderer);
SetScissorsCommand.Run(ref GetCommand<SetScissorsCommand>(memory), threaded, renderer);
_lookup[(int)CommandType.SetStencilTest] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
SetStencilTestCommand.Run(ref GetCommand<SetStencilTestCommand>(memory), threaded, renderer);
_lookup[(int)CommandType.SetTexture] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
SetTextureCommand.Run(ref GetCommand<SetTextureCommand>(memory), threaded, renderer);
_lookup[(int)CommandType.SetTextureAndSampler] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
SetTextureAndSamplerCommand.Run(ref GetCommand<SetTextureAndSamplerCommand>(memory), threaded, renderer);
_lookup[(int)CommandType.SetUserClipDistance] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
SetUserClipDistanceCommand.Run(ref GetCommand<SetUserClipDistanceCommand>(memory), threaded, renderer);
_lookup[(int)CommandType.SetVertexAttribs] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>

View file

@ -81,10 +81,9 @@
SetRenderTargetColorMasks,
SetRenderTargetScale,
SetRenderTargets,
SetSampler,
SetScissor,
SetStencilTest,
SetTexture,
SetTextureAndSampler,
SetUserClipDistance,
SetVertexAttribs,
SetVertexBuffers,

View file

@ -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<ISampler> _sampler;
public void Set(int index, TableRef<ISampler> sampler)
{
_index = index;
_sampler = sampler;
}
public static void Run(ref SetSamplerCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
renderer.Pipeline.SetSampler(command._index, command._sampler.GetAs<ThreadedSampler>(threaded)?.Base);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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<Rectangle<int>> _scissors;
public void Set(SpanRef<Rectangle<int>> 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);
}
}
}

View file

@ -0,0 +1,25 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{
struct SetTextureAndSamplerCommand : IGALCommand
{
public CommandType CommandType => CommandType.SetTextureAndSampler;
private int _binding;
private TableRef<ITexture> _texture;
private TableRef<ISampler> _sampler;
public void Set(int binding, TableRef<ITexture> texture, TableRef<ISampler> 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<ThreadedTexture>(threaded)?.Base, command._sampler.GetAs<ThreadedSampler>(threaded)?.Base);
}
}
}

View file

@ -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<ITexture> _texture;
public void Set(int binding, TableRef<ITexture> texture)
{
_binding = binding;
_texture = texture;
}
public static void Run(ref SetTextureCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
renderer.Pipeline.SetTexture(command._binding, command._texture.GetAs<ThreadedTexture>(threaded)?.Base);
}
}
}

View file

@ -244,15 +244,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand();
}
public void SetSampler(int binding, ISampler sampler)
public void SetScissors(ReadOnlySpan<Rectangle<int>> scissors)
{
_renderer.New<SetSamplerCommand>().Set(binding, Ref(sampler));
_renderer.QueueCommand();
}
public void SetScissor(int index, bool enable, int x, int y, int width, int height)
{
_renderer.New<SetScissorCommand>().Set(index, enable, x, y, width, height);
_renderer.New<SetScissorsCommand>().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<SetTextureCommand>().Set(binding, Ref(texture));
_renderer.New<SetTextureAndSamplerCommand>().Set(binding, Ref(texture), Ref(sampler));
_renderer.QueueCommand();
}

View file

@ -337,6 +337,11 @@ namespace Ryujinx.Graphics.GAL.Multithreading
return box.Result;
}
public HardwareInfo GetHardwareInfo()
{
return _baseRenderer.GetHardwareInfo();
}
/// <summary>
/// Initialize the base renderer. Must be called on the render thread.
/// </summary>

View file

@ -0,0 +1,18 @@
namespace Ryujinx.Graphics.GAL
{
public struct Rectangle<T> 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;
}
}
}

View file

@ -0,0 +1,30 @@
using System.Collections.Generic;
namespace Ryujinx.Graphics.GAL
{
public struct ShaderBindings
{
public IReadOnlyCollection<int> UniformBufferBindings { get; }
public IReadOnlyCollection<int> StorageBufferBindings { get; }
public IReadOnlyCollection<int> TextureBindings { get; }
public IReadOnlyCollection<int> ImageBindings { get; }
public IReadOnlyCollection<int> BufferTextureBindings { get; }
public IReadOnlyCollection<int> BufferImageBindings { get; }
public ShaderBindings(
IReadOnlyCollection<int> uniformBufferBindings,
IReadOnlyCollection<int> storageBufferBindings,
IReadOnlyCollection<int> textureBindings,
IReadOnlyCollection<int> imageBindings,
IReadOnlyCollection<int> bufferTextureBindings,
IReadOnlyCollection<int> bufferImageBindings)
{
UniformBufferBindings = uniformBufferBindings;
StorageBufferBindings = storageBufferBindings;
TextureBindings = textureBindings;
ImageBindings = imageBindings;
BufferTextureBindings = bufferTextureBindings;
BufferImageBindings = bufferImageBindings;
}
}
}

View file

@ -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)
{
}
}

View file

@ -2,7 +2,7 @@ namespace Ryujinx.Graphics.GAL
{
public struct Viewport
{
public RectangleF Region { get; }
public Rectangle<float> Region { get; }
public ViewportSwizzle SwizzleX { get; }
public ViewportSwizzle SwizzleY { get; }
@ -13,7 +13,7 @@ namespace Ryujinx.Graphics.GAL
public float DepthFar { get; }
public Viewport(
RectangleF region,
Rectangle<float> region,
ViewportSwizzle swizzleX,
ViewportSwizzle swizzleY,
ViewportSwizzle swizzleZ,

View file

@ -40,6 +40,22 @@ namespace Ryujinx.Graphics.Gpu
/// </summary>
public const int TotalTransformFeedbackBuffers = 4;
/// <summary>
/// Maximum number of textures on a single shader stage.
/// </summary>
/// <remarks>
/// The maximum number of textures is API limited, the hardware supports a unlimited amount.
/// </remarks>
public const int TotalTextures = 32;
/// <summary>
/// Maximum number of images on a single shader stage.
/// </summary>
/// <remarks>
/// The maximum number of images is API limited, the hardware supports a unlimited amount.
/// </remarks>
public const int TotalImages = 8;
/// <summary>
/// Maximum number of render target color buffers.
/// </summary>

View file

@ -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<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];
scissors[0] = new Rectangle<int>(scissorX, scissorY, scissorW, scissorH);
_context.Renderer.Pipeline.SetScissors(scissors);
}
if (clipMismatch)

View file

@ -493,11 +493,21 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary>
public void UpdateScissorState()
{
const int MinX = 0;
const int MinY = 0;
const int MaxW = 0xffff;
const int MaxH = 0xffff;
Span<Rectangle<int>> regions = stackalloc Rectangle<int>[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<int>(x, y, width, height);
}
else
{
_context.Renderer.Pipeline.SetScissor(index, false, 0, 0, 0, 0);
regions[index] = new Rectangle<int>(MinX, MinY, MaxW, MaxH);
}
}
_context.Renderer.Pipeline.SetScissors(regions);
}
/// <summary>
@ -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<float>(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<float> region = new Rectangle<float>(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;
}
/// <summary>

View file

@ -311,6 +311,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
{
return Attribute & 0x3fe00000;
}
/// <summary>
/// Unpacks the Maxwell attribute component type.
/// </summary>
/// <returns>Attribute component type</returns>
public uint UnpackType()
{
return (Attribute >> 27) & 7;
}
}
/// <summary>

View file

@ -56,5 +56,10 @@ namespace Ryujinx.Graphics.Gpu
/// Enables or disables the shader cache.
/// </summary>
public static bool EnableShaderCache;
/// <summary>
/// Enables or disables shader SPIR-V compilation.
/// </summary>
public static bool EnableSpirvCompilation;
}
}

View file

@ -435,6 +435,25 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
/// <summary>
/// Counts the total number of texture bindings used by all shader stages.
/// </summary>
/// <returns>The total amount of textures used</returns>
private int GetTextureBindingsCount()
{
int count = 0;
for (int i = 0; i < _textureBindings.Length; i++)
{
if (_textureBindings[i] != null)
{
count += _textureBindings[i].Length;
}
}
return count;
}
/// <summary>
/// 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;
}
}

View file

@ -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);
}
}

View file

@ -252,6 +252,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
Write(va, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1)));
}
public void WriteUntracked<T>(ulong va, T value) where T : unmanaged
{
WriteUntracked(va, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1)));
}
/// <summary>
/// Writes data to GPU mapped memory.
/// </summary>

View file

@ -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;

View file

@ -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<byte, ulong>(_data.Span.Slice((int)address));
}
/// <inheritdoc/>
public int QueryBindingConstantBuffer(int index)
{
return _resourceCounts.UniformBuffersCount++;
}
/// <inheritdoc/>
public int QueryBindingStorageBuffer(int index)
{
return _resourceCounts.StorageBuffersCount++;
}
/// <inheritdoc/>
public int QueryBindingTexture(int index)
{
return _resourceCounts.TexturesCount++;
}
/// <inheritdoc/>
public int QueryBindingImage(int index)
{
return _resourceCounts.ImagesCount++;
}
/// <inheritdoc/>
public int QueryComputeLocalSizeX() => _oldSpecState.ComputeState.LocalSizeX;

View file

@ -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();

View file

@ -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
/// <param name="context">GPU context</param>
/// <param name="channel">GPU channel</param>
/// <param name="state">Current GPU state</param>
/// <param name="attributeTypes">Type of the vertex attributes consumed by the shader</param>
/// <param name="stageIndex">Graphics shader stage index (0 = Vertex, 4 = Fragment)</param>
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
/// <param name="context">GPU context</param>
/// <param name="channel">GPU channel</param>
/// <param name="state">Current GPU state</param>
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
}
/// <inheritdoc/>
public int QueryBindingConstantBuffer(int index)
public AttributeType QueryAttributeType(int location)
{
return _state.ResourceCounts.UniformBuffersCount++;
if (_attributeTypes != null)
{
return _attributeTypes[location];
}
/// <inheritdoc/>
public int QueryBindingStorageBuffer(int index)
{
return _state.ResourceCounts.StorageBuffersCount++;
}
/// <inheritdoc/>
public int QueryBindingTexture(int index)
{
return _state.ResourceCounts.TexturesCount++;
}
/// <inheritdoc/>
public int QueryBindingImage(int index)
{
return _state.ResourceCounts.ImagesCount++;
return AttributeType.Float;
}
/// <inheritdoc/>
@ -123,6 +118,18 @@ namespace Ryujinx.Graphics.Gpu.Shader
return ConvertToInputTopology(_state.GraphicsState.Topology, _state.GraphicsState.TessellationMode);
}
/// <inheritdoc/>
public bool QueryProgramPointSize()
{
return _state.GraphicsState.ProgramPointSizeEnable;
}
/// <inheritdoc/>
public float QueryPointSize()
{
return _state.GraphicsState.PointSize;
}
/// <inheritdoc/>
public bool QueryTessCw()
{
@ -192,6 +199,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
}
/// <inheritdoc/>
public bool QueryTransformDepthMinusOneToOne()
{
return _state.GraphicsState.DepthMode;
}
/// <inheritdoc/>
public bool QueryTransformFeedbackEnabled()
{

View file

@ -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;
/// <summary>
/// Creates a new GPU accessor.
/// </summary>
/// <param name="context">GPU context</param>
public GpuAccessorBase(GpuContext context)
public GpuAccessorBase(GpuContext context, ResourceCounts resourceCounts, int stageIndex)
{
_context = context;
_resourceCounts = resourceCounts;
_stageIndex = stageIndex;
}
/// <inheritdoc/>
public int QueryBindingConstantBuffer(int index)
{
if (_context.Capabilities.Api == TargetApi.Vulkan)
{
return 1 + GetStageIndex() * 18 + index;
}
else
{
return _resourceCounts.UniformBuffersCount++;
}
}
/// <inheritdoc/>
public int QueryBindingStorageBuffer(int index)
{
if (_context.Capabilities.Api == TargetApi.Vulkan)
{
return GetStageIndex() * 16 + index;
}
else
{
return _resourceCounts.StorageBuffersCount++;
}
}
/// <inheritdoc/>
public int QueryBindingTexture(int index)
{
if (_context.Capabilities.Api == TargetApi.Vulkan)
{
return GetStageIndex() * 32 + index;
}
else
{
return _resourceCounts.TexturesCount++;
}
}
/// <inheritdoc/>
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
};
}
/// <summary>

View file

@ -30,6 +30,21 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary>
public readonly bool ViewportTransformDisable;
/// <summary>
/// Depth mode zero to one or minus one to one.
/// </summary>
public readonly bool DepthMode;
/// <summary>
/// Indicates if the point size is set on the shader or is fixed.
/// </summary>
public readonly bool ProgramPointSizeEnable;
/// <summary>
/// Point size if not set from shader.
/// </summary>
public readonly float PointSize;
/// <summary>
/// Creates a new GPU graphics state.
/// </summary>
@ -37,12 +52,25 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="topology">Primitive topology</param>
/// <param name="tessellationMode">Tessellation mode</param>
/// <param name="viewportTransformDisable">Indicates whenever the viewport transform is disabled</param>
public GpuChannelGraphicsState(bool earlyZForce, PrimitiveTopology topology, TessMode tessellationMode, bool viewportTransformDisable)
/// <param name="depthMode">Depth mode zero to one or minus one to one</param>
/// <param name="programPointSizeEnable">Indicates if the point size is set on the shader or is fixed</param>
/// <param name="pointSize">Point size if not set from shader</param>
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;
}
}
}

View file

@ -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
/// <returns>Shader source</returns>
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);
}
/// <summary>
@ -480,11 +495,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// Decode the binary Maxwell shader code to a translator context.
/// </summary>
/// <param name="gpuAccessor">GPU state accessor</param>
/// <param name="api">Graphics API that will be used with the shader</param>
/// <param name="gpuVa">GPU virtual address of the binary shader code</param>
/// <returns>The generated translator context</returns>
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.
/// </remarks>
/// <param name="gpuAccessor">GPU state accessor</param>
/// <param name="api">Graphics API that will be used with the shader</param>
/// <param name="flags">Flags that controls shader translation</param>
/// <param name="gpuVa">GPU virtual address of the shader code</param>
/// <returns>The generated translator context</returns>
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);
}
/// <summary>
/// Disposes the shader cache, deleting all the cached shaders.
/// It's an error to use the shader cache after disposal.

View file

@ -1,4 +1,4 @@
using Ryujinx.Common;
using Ryujinx.Common;
using System;
using System.Collections.Generic;
using System.Threading;

View file

@ -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));

View file

@ -1106,45 +1106,25 @@ namespace Ryujinx.Graphics.OpenGL
_framebuffer.SetDrawBuffers(colors.Length);
}
public void SetSampler(int binding, ISampler sampler)
public unsafe void SetScissors(ReadOnlySpan<Rectangle<int>> 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,22 +1175,24 @@ 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);
}
((Sampler)sampler).Bind(binding);
}
}
public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers)
{

View file

@ -1,5 +1,4 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using System;
using System.Threading;

View file

@ -87,6 +87,11 @@ namespace Ryujinx.Graphics.OpenGL
Buffer.Delete(buffer);
}
public HardwareInfo GetHardwareInfo()
{
return new HardwareInfo(GpuVendor, GpuRenderer);
}
public ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
{
return Buffer.GetData(this, buffer, offset, size);

View file

@ -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;

View file

@ -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}\".")
};
}
}
}

View file

@ -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())
@ -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};");
}
}

View file

@ -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;

View file

@ -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)
{

View file

@ -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<int, BuiltInAttribute> _builtInAttributes =
new Dictionary<int, BuiltInAttribute>()
private static Dictionary<int, BuiltInAttribute> _builtInAttributes = new Dictionary<int, BuiltInAttribute>()
{
{ 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);

View file

@ -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<int, Instruction> UniformBuffers { get; } = new Dictionary<int, Instruction>();
public Instruction StorageBuffersArray { get; set; }
public Instruction LocalMemory { get; set; }
public Instruction SharedMemory { get; set; }
public Dictionary<TextureMeta, (Instruction, Instruction, Instruction)> Samplers { get; } = new Dictionary<TextureMeta, (Instruction, Instruction, Instruction)>();
public Dictionary<TextureMeta, (Instruction, Instruction)> Images { get; } = new Dictionary<TextureMeta, (Instruction, Instruction)>();
public Dictionary<int, Instruction> Inputs { get; } = new Dictionary<int, Instruction>();
public Dictionary<int, Instruction> Outputs { get; } = new Dictionary<int, Instruction>();
private readonly Dictionary<AstOperand, Instruction> _locals = new Dictionary<AstOperand, Instruction>();
private readonly Dictionary<int, Instruction[]> _localForArgs = new Dictionary<int, Instruction[]>();
private readonly Dictionary<int, Instruction> _funcArgs = new Dictionary<int, Instruction>();
private readonly Dictionary<int, (StructuredFunction, Instruction)> _functions = new Dictionary<int, (StructuredFunction, Instruction)>();
private class BlockState
{
private int _entryCount;
private readonly List<Instruction> _labels = new List<Instruction>();
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<AstBlock, BlockState> _labels = new Dictionary<AstBlock, BlockState>();
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);
}
}
}

View file

@ -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<VariableType> 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<StructuredFunction> 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];
}
}
}

View file

@ -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}\".")
};
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -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;
}
}
}

View file

@ -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<AstBlock, (SpvInstruction, SpvInstruction)>();
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);
}
}
}
}
}

View file

@ -0,0 +1,35 @@
using System;
namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
struct TextureMeta : IEquatable<TextureMeta>
{
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);
}
}
}

View file

@ -34,6 +34,11 @@ namespace Ryujinx.Graphics.Shader
/// <returns>Span of the memory location</returns>
ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize);
AttributeType QueryAttributeType(int location)
{
return AttributeType.Float;
}
/// <summary>
/// Queries the binding number of a constant buffer.
/// </summary>
@ -218,6 +223,16 @@ namespace Ryujinx.Graphics.Shader
return true;
}
float QueryPointSize()
{
return 1f;
}
bool QueryProgramPointSize()
{
return true;
}
/// <summary>
/// Queries sampler type information.
/// </summary>
@ -291,6 +306,11 @@ namespace Ryujinx.Graphics.Shader
return TextureFormat.R8G8B8A8Unorm;
}
bool QueryTransformDepthMinusOneToOne()
{
return false;
}
/// <summary>
/// Queries transform feedback enable state.
/// </summary>

View file

@ -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;
}
}
}

View file

@ -10,6 +10,7 @@
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
<ProjectReference Include="..\SpvGen\Spv.Generator.csproj" />
</ItemGroup>
<ItemGroup>

View file

@ -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)
{

View file

@ -29,6 +29,7 @@ namespace Ryujinx.Graphics.Shader
public void Prepend(string line)
{
System.Console.WriteLine("prepend " + line);
Code = line + Environment.NewLine + Code;
}
}

View file

@ -32,6 +32,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
private LinkedList<IAstNode> _nodes;
public IAstNode First => _nodes.First?.Value;
public IAstNode Last => _nodes.Last?.Value;
public int Count => _nodes.Count;

View file

@ -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);

View file

@ -17,7 +17,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{
LinkedListNode<INode> 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;
}
}
}

View file

@ -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);
}

View file

@ -22,6 +22,9 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{
public List<StructuredFunction> Functions { get; }
public HashSet<int> Inputs { get; }
public HashSet<int> Outputs { get; }
public HelperFunctionsMask HelperFunctionsMask { get; set; }
public TransformFeedbackOutput[] TransformFeedbackOutputs { get; }
@ -30,6 +33,9 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{
Functions = new List<StructuredFunction>();
Inputs = new HashSet<int>();
Outputs = new HashSet<int>();
TransformFeedbackOutputs = new TransformFeedbackOutput[0xc0];
}
}

View file

@ -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
}
}

View file

@ -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;

View file

@ -0,0 +1,99 @@
using Ryujinx.Common;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.Translation
{
struct AttributeInfo
{
private static readonly Dictionary<int, AttributeInfo> BuiltInAttributes = new Dictionary<int, AttributeInfo>()
{
{ 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);
}
}
}

View file

@ -30,6 +30,19 @@ namespace Ryujinx.Graphics.Shader.Translation
IsNonMain = isNonMain;
_operations = new List<Operation>();
_labels = new Dictionary<ulong, Operand>();
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<T>() 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.
if (Config.GpuAccessor.QueryTransformDepthMinusOneToOne())
{
oldZLocal = Local();
this.Copy(oldYLocal, Attribute(AttributeConsts.PositionZ | AttributeConsts.LoadOutputMask));
}
else
{
oldZLocal = null;
}
PrepareForVertexReturn();
}

View file

@ -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);

View file

@ -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<T> : 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;
}
}
}
}

View file

@ -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;
}
}
}

View file

@ -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<DisposableBuffer> _buffer;
private readonly Auto<MemoryAllocation> _allocationAuto;
private readonly ulong _bufferHandle;
private CacheByRange<BufferHolder> _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<MemoryAllocation>(allocation);
_waitable = new MultiFenceHolder();
_buffer = new Auto<DisposableBuffer>(new DisposableBuffer(gd.Api, device, buffer), _waitable, _allocationAuto);
_bufferHandle = buffer.Handle;
Size = size;
_map = allocation.HostPointer;
}
public unsafe Auto<DisposableBufferView> 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<DisposableBufferView>(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<DisposableBuffer> GetBuffer()
{
return _buffer;
}
public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, bool isWrite = false)
{
if (isWrite)
{
_cachedConvertedIndexBuffers.Clear();
}
// InsertBarrier(commandBuffer, isWrite);
return _buffer;
}
public BufferHandle GetHandle()
{
var handle = _bufferHandle;
return Unsafe.As<ulong, BufferHandle>(ref handle);
}
public unsafe IntPtr Map(int offset, int mappingSize)
{
return _map;
}
public unsafe ReadOnlySpan<byte> GetData(int offset, int size)
{
return GetDataStorage(offset, size);
}
public unsafe Span<byte> GetDataStorage(int offset, int size)
{
int mappingSize = Math.Min(size, Size - offset);
if (_map != IntPtr.Zero)
{
return new Span<byte>((void*)(_map + offset), mappingSize);
}
throw new InvalidOperationException("The buffer is not host mapped.");
}
public unsafe void SetData(int offset, ReadOnlySpan<byte> 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<byte>((void*)(_map + offset), dataSize));
}
}
}
public unsafe void SetDataUnchecked(int offset, ReadOnlySpan<byte> data)
{
int dataSize = Math.Min(data.Length, Size - offset);
if (dataSize == 0)
{
return;
}
if (_map != IntPtr.Zero)
{
data.Slice(0, dataSize).CopyTo(new Span<byte>((void*)(_map + offset), dataSize));
}
}
public void SetDataInline(CommandBufferScoped cbs, Action endRenderPass, int dstOffset, ReadOnlySpan<byte> 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<byte> 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<DisposableBuffer> src,
Auto<DisposableBuffer> 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, &region);
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<DisposableBuffer> 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();
}
}
}

View file

@ -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<BufferHolder> _buffers;
public StagingBuffer StagingBuffer { get; }
public BufferManager(VulkanGraphicsDevice gd, PhysicalDevice physicalDevice, Device device)
{
_physicalDevice = physicalDevice;
_device = device;
_buffers = new List<BufferHolder>();
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<ulong, BufferHandle>(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<DisposableBufferView> 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<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, BufferHandle handle, bool isWrite)
{
if (TryGetBuffer(handle, out var holder))
{
return holder.GetBuffer(commandBuffer, isWrite);
}
return null;
}
public Auto<DisposableBuffer> 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<DisposableBuffer> 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<byte> GetData(BufferHandle handle, int offset, int size)
{
if (TryGetBuffer(handle, out var holder))
{
return holder.GetData(offset, size);
}
return ReadOnlySpan<byte>.Empty;
}
public void SetData<T>(BufferHandle handle, int offset, ReadOnlySpan<T> data) where T : unmanaged
{
SetData(handle, offset, MemoryMarshal.Cast<T, byte>(data), null, null);
}
public void SetData(BufferHandle handle, int offset, ReadOnlySpan<byte> 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<BufferHandle, ulong>(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);
}
}
}

View file

@ -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<Range>[] _ranges;
public void Initialize()
{
_ranges = new List<Range>[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<Range>
{
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<Range> 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;
}
}
}

View file

@ -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<DisposableBuffer> _buffer;
private readonly int _offset;
private readonly int _size;
private readonly ulong _stride;
private readonly IndexType _type;
public BufferState(Auto<DisposableBuffer> buffer, int offset, int size, IndexType type)
{
_buffer = buffer;
_offset = offset;
_size = size;
_stride = 0;
_type = type;
buffer?.IncrementReferenceCount();
}
public BufferState(Auto<DisposableBuffer> 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();
}
}
}

View file

@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Vulkan
{
struct CacheByRange<T> where T : IDisposable
{
private Dictionary<ulong, T> _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<ulong, T>();
}
}
private static ulong PackRange(int offset, int size)
{
return (uint)offset | ((ulong)size << 32);
}
public void Dispose()
{
Clear();
}
}
}

View file

@ -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<IAuto> Dependants;
public HashSet<MultiFenceHolder> Waitables;
public HashSet<SemaphoreHolder> 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<IAuto>();
Waitables = new HashSet<MultiFenceHolder>();
Dependencies = new HashSet<SemaphoreHolder>();
}
}
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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}

View file

@ -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<DescriptorBufferInfo> 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<DescriptorBufferInfo> 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<DescriptorBufferInfo> 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<DescriptorImageInfo> 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<DescriptorImageInfo> 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<BufferView> 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;
}
}
}

View file

@ -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<DescriptorSetLayout> layouts)
{
TryAllocateDescriptorSets(layouts, isTry: false, out var dsc);
return dsc;
}
public bool TryAllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts, out DescriptorSetCollection dsc)
{
return TryAllocateDescriptorSets(layouts, isTry: true, out dsc);
}
private unsafe bool TryAllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> 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<DescriptorSetCollection> AllocateDescriptorSet(Vk api, DescriptorSetLayout layout)
{
Span<DescriptorSetLayout> layouts = stackalloc DescriptorSetLayout[1];
layouts[0] = layout;
return AllocateDescriptorSets(api, layouts);
}
public Auto<DescriptorSetCollection> AllocateDescriptorSets(Vk api, ReadOnlySpan<DescriptorSetLayout> 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<DescriptorSetCollection>(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);
}
}
}

View file

@ -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<DisposableBuffer>[] _uniformBufferRefs;
private Auto<DisposableBuffer>[] _storageBufferRefs;
private Auto<DisposableImageView>[] _textureRefs;
private Auto<DisposableSampler>[] _samplerRefs;
private Auto<DisposableImageView>[] _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<DescriptorBufferInfo>();
_storageBuffers = Array.Empty<DescriptorBufferInfo>();
_textures = new DescriptorImageInfo[32 * 5];
_textureRefs = new Auto<DisposableImageView>[32 * 5];
_samplerRefs = new Auto<DisposableSampler>[32 * 5];
_images = Array.Empty<DescriptorImageInfo>();
_bufferTextures = Array.Empty<BufferView>();
_bufferImages = Array.Empty<BufferView>();
_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<BufferRange> 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<BufferRange> 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<DisposableBuffer> 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<DescriptorBufferInfo> 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<DescriptorBufferInfo> 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<DescriptorBufferInfo> 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<DescriptorImageInfo> 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<DescriptorImageInfo> 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<BufferView> 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<BufferView> 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<uint>.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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}
}

View file

@ -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<Fence> 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<Fence> 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<Fence> 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();
}
}
}

View file

@ -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<Fence> fences = stackalloc Fence[1];
fences[0] = _fence;
FenceHelper.WaitAllIndefinitely(_api, _device, fences);
}
public bool IsSignaled()
{
Span<Fence> fences = stackalloc Fence[1];
fences[0] = _fence;
return FenceHelper.AllSignaled(_api, _device, fences);
}
public void Dispose()
{
if (!_disposed)
{
Put();
_disposed = true;
}
}
}
}

View file

@ -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;
}
}
}

View file

@ -0,0 +1,49 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Vulkan
{
class FormatConverter
{
public unsafe static void ConvertD24S8ToD32FS8(Span<byte> output, ReadOnlySpan<byte> input)
{
const float UnormToFloat = 1f / 0xffffff;
Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output);
ReadOnlySpan<uint> inputUint = MemoryMarshal.Cast<byte, uint>(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<byte> output, ReadOnlySpan<byte> input)
{
Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output);
ReadOnlySpan<uint> inputUint = MemoryMarshal.Cast<byte, uint>(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;
}
}
}
}

View file

@ -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];
}
}
}

View file

@ -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<DisposableImageView>[] _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<DisposableImageView> 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<DisposableImageView>[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<DisposableFramebuffer> Create(Vk api, CommandBufferScoped cbs, Auto<DisposableRenderPass> 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<DisposableFramebuffer>(new DisposableFramebuffer(api, _device, framebuffer), null, _attachments);
}
}
}

View file

@ -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;
}
}
}

View file

@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Vulkan
{
interface IRefEquatable<T>
{
bool Equals(ref T other);
}
class HashTableSlim<K, V> where K : IRefEquatable<K>
{
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<K> Keys
{
get
{
foreach (Entry[] bucket in _hashTable)
{
foreach (Entry entry in bucket)
{
yield return entry.Key;
}
}
}
}
public IEnumerable<V> 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;
}
}
}

View file

@ -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<int>(),
Array.Empty<int>(),
Array.Empty<int>(),
Array.Empty<int>(),
Array.Empty<int>());
var fragmentBindings = new ShaderBindings(
Array.Empty<int>(),
Array.Empty<int>(),
new[] { 32 },
Array.Empty<int>(),
Array.Empty<int>(),
Array.Empty<int>());
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<DisposableImageView> 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<DisposableImageView> 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<float> 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<float>(bufferHandle, 0, region);
Span<BufferRange> bufferRanges = stackalloc BufferRange[1];
bufferRanges[0] = new BufferRange(bufferHandle, 0, RegionBufferSize);
_pipeline.SetUniformBuffers(1, bufferRanges);
Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
viewports[0] = new GAL.Viewport(
new Rectangle<float>(dstRegion.X1, dstRegion.Y1, dstRegion.X2 - dstRegion.X1, dstRegion.Y2 - dstRegion.Y1),
ViewportSwizzle.PositiveX,
ViewportSwizzle.PositiveY,
ViewportSwizzle.PositiveZ,
ViewportSwizzle.PositiveW,
0f,
1f);
Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];
scissors[0] = new Rectangle<int>(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);
}
}
}

Some files were not shown because too many files have changed in this diff Show more