mirror of
https://github.com/Ryujinx/Ryujinx.git
synced 2024-12-29 17:36:06 +00:00
8fa248ceb4
* Add MVK basics. * Use appropriate output attribute types * 4kb vertex alignment, bunch of fixes * Add reduced shader precision mode for mvk. * Disable ASTC on MVK for now * Only request robustnes2 when it is available. * It's just the one feature actually * Add triangle fan conversion * Allow NullDescriptor on MVK for some reason. * Force safe blit on MoltenVK * Use ASTC only when formats are all available. * Disable multilevel 3d texture views * Filter duplicate render targets (on backend) * Add Automatic MoltenVK Configuration * Do not create color attachment views with formats that are not RT compatible * Make sure that the host format matches the vertex shader input types for invalid/unknown guest formats * FIx rebase for Vertex Attrib State * Fix 4b alignment for vertex * Use asynchronous queue submits for MVK * Ensure color clear shader has correct output type * Update MoltenVK config * Always use MoltenVK workarounds on MacOS * Make MVK supersede all vendors * Fix rebase * Various fixes on rebase * Get portability flags from extension * Fix some minor rebasing issues * Style change * Use LibraryImport for MVKConfiguration * Rename MoltenVK vendor to Apple Intel and AMD GPUs on moltenvk report with the those vendors - only apple silicon reports with vendor 0x106B. * Fix features2 rebase conflict * Rename fragment output type * Add missing check for fragment output types Might have caused the crash in MK8 * Only do fragment output specialization on MoltenVK * Avoid copy when passing capabilities * Self feedback * Address feedback Co-authored-by: gdk <gab.dark.100@gmail.com> Co-authored-by: nastys <nastys@users.noreply.github.com>
1008 lines
38 KiB
C#
1008 lines
38 KiB
C#
using Ryujinx.Graphics.GAL;
|
|
using Ryujinx.Graphics.Shader;
|
|
using Ryujinx.Graphics.Shader.Translation;
|
|
using Ryujinx.Graphics.Vulkan.Shaders;
|
|
using Silk.NET.Vulkan;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using VkFormat = Silk.NET.Vulkan.Format;
|
|
|
|
namespace Ryujinx.Graphics.Vulkan
|
|
{
|
|
enum ComponentType
|
|
{
|
|
Float,
|
|
SignedInteger,
|
|
UnsignedInteger
|
|
}
|
|
|
|
class HelperShader : IDisposable
|
|
{
|
|
private const int UniformBufferAlignment = 256;
|
|
|
|
private readonly PipelineHelperShader _pipeline;
|
|
private readonly ISampler _samplerLinear;
|
|
private readonly ISampler _samplerNearest;
|
|
private readonly IProgram _programColorBlit;
|
|
private readonly IProgram _programColorBlitClearAlpha;
|
|
private readonly IProgram _programColorClearF;
|
|
private readonly IProgram _programColorClearSI;
|
|
private readonly IProgram _programColorClearUI;
|
|
private readonly IProgram _programStrideChange;
|
|
private readonly IProgram _programConvertIndexBuffer;
|
|
private readonly IProgram _programConvertIndirectData;
|
|
private readonly IProgram _programColorCopyToNonMs;
|
|
private readonly IProgram _programColorDrawToMs;
|
|
|
|
public HelperShader(VulkanRenderer gd, Device device)
|
|
{
|
|
_pipeline = new PipelineHelperShader(gd, device);
|
|
_pipeline.Initialize();
|
|
|
|
_samplerLinear = gd.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
|
_samplerNearest = gd.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Nearest, MagFilter.Nearest));
|
|
|
|
var colorBlitVertexBindings = new ShaderBindings(
|
|
new[] { 1 },
|
|
Array.Empty<int>(),
|
|
Array.Empty<int>(),
|
|
Array.Empty<int>());
|
|
|
|
var colorBlitFragmentBindings = new ShaderBindings(
|
|
Array.Empty<int>(),
|
|
Array.Empty<int>(),
|
|
new[] { 0 },
|
|
Array.Empty<int>());
|
|
|
|
_programColorBlit = gd.CreateProgramWithMinimalLayout(new[]
|
|
{
|
|
new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, colorBlitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
|
|
new ShaderSource(ShaderBinaries.ColorBlitFragmentShaderSource, colorBlitFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
|
|
});
|
|
|
|
_programColorBlitClearAlpha = gd.CreateProgramWithMinimalLayout(new[]
|
|
{
|
|
new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, colorBlitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
|
|
new ShaderSource(ShaderBinaries.ColorBlitClearAlphaFragmentShaderSource, colorBlitFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
|
|
});
|
|
|
|
var colorClearFragmentBindings = new ShaderBindings(
|
|
Array.Empty<int>(),
|
|
Array.Empty<int>(),
|
|
Array.Empty<int>(),
|
|
Array.Empty<int>());
|
|
|
|
_programColorClearF = gd.CreateProgramWithMinimalLayout(new[]
|
|
{
|
|
new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, colorBlitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
|
|
new ShaderSource(ShaderBinaries.ColorClearFFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
|
|
});
|
|
|
|
_programColorClearSI = gd.CreateProgramWithMinimalLayout(new[]
|
|
{
|
|
new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, colorBlitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
|
|
new ShaderSource(ShaderBinaries.ColorClearSIFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
|
|
});
|
|
|
|
_programColorClearUI = gd.CreateProgramWithMinimalLayout(new[]
|
|
{
|
|
new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, colorBlitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
|
|
new ShaderSource(ShaderBinaries.ColorClearUIFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
|
|
});
|
|
|
|
var strideChangeBindings = new ShaderBindings(
|
|
new[] { 0 },
|
|
new[] { 1, 2 },
|
|
Array.Empty<int>(),
|
|
Array.Empty<int>());
|
|
|
|
_programStrideChange = gd.CreateProgramWithMinimalLayout(new[]
|
|
{
|
|
new ShaderSource(ShaderBinaries.ChangeBufferStrideShaderSource, strideChangeBindings, ShaderStage.Compute, TargetLanguage.Spirv),
|
|
});
|
|
|
|
var colorCopyToNonMsBindings = new ShaderBindings(
|
|
new[] { 0 },
|
|
Array.Empty<int>(),
|
|
new[] { 0 },
|
|
new[] { 0 });
|
|
|
|
_programColorCopyToNonMs = gd.CreateProgramWithMinimalLayout(new[]
|
|
{
|
|
new ShaderSource(ShaderBinaries.ColorCopyToNonMsComputeShaderSource, colorCopyToNonMsBindings, ShaderStage.Compute, TargetLanguage.Spirv),
|
|
});
|
|
|
|
var colorDrawToMsVertexBindings = new ShaderBindings(
|
|
Array.Empty<int>(),
|
|
Array.Empty<int>(),
|
|
Array.Empty<int>(),
|
|
Array.Empty<int>());
|
|
|
|
var colorDrawToMsFragmentBindings = new ShaderBindings(
|
|
new[] { 0 },
|
|
Array.Empty<int>(),
|
|
new[] { 0 },
|
|
Array.Empty<int>());
|
|
|
|
_programColorDrawToMs = gd.CreateProgramWithMinimalLayout(new[]
|
|
{
|
|
new ShaderSource(ShaderBinaries.ColorDrawToMsVertexShaderSource, colorDrawToMsVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
|
|
new ShaderSource(ShaderBinaries.ColorDrawToMsFragmentShaderSource, colorDrawToMsFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
|
|
});
|
|
|
|
var convertIndexBufferBindings = new ShaderBindings(
|
|
new[] { 0 },
|
|
new[] { 1, 2 },
|
|
Array.Empty<int>(),
|
|
Array.Empty<int>());
|
|
|
|
_programConvertIndexBuffer = gd.CreateProgramWithMinimalLayout(new[]
|
|
{
|
|
new ShaderSource(ShaderBinaries.ConvertIndexBufferShaderSource, convertIndexBufferBindings, ShaderStage.Compute, TargetLanguage.Spirv),
|
|
});
|
|
|
|
var convertIndirectDataBindings = new ShaderBindings(
|
|
new[] { 0 },
|
|
new[] { 1, 2, 3 },
|
|
Array.Empty<int>(),
|
|
Array.Empty<int>());
|
|
|
|
_programConvertIndirectData = gd.CreateProgramWithMinimalLayout(new[]
|
|
{
|
|
new ShaderSource(ShaderBinaries.ConvertIndirectDataShaderSource, convertIndirectDataBindings, ShaderStage.Compute, TargetLanguage.Spirv),
|
|
});
|
|
}
|
|
|
|
public void Blit(
|
|
VulkanRenderer 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(
|
|
VulkanRenderer 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(ShaderStage.Fragment, 0, 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;
|
|
|
|
if (dstRegion.X1 > dstRegion.X2)
|
|
{
|
|
(region[0], region[1]) = (region[1], region[0]);
|
|
}
|
|
|
|
if (dstRegion.Y1 > dstRegion.Y2)
|
|
{
|
|
(region[2], region[3]) = (region[3], region[2]);
|
|
}
|
|
|
|
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, RegionBufferSize, false);
|
|
|
|
gd.BufferManager.SetData<float>(bufferHandle, 0, region);
|
|
|
|
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, new BufferRange(bufferHandle, 0, RegionBufferSize)) });
|
|
|
|
Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
|
|
|
|
var rect = new Rectangle<float>(
|
|
MathF.Min(dstRegion.X1, dstRegion.X2),
|
|
MathF.Min(dstRegion.Y1, dstRegion.Y2),
|
|
MathF.Abs(dstRegion.X2 - dstRegion.X1),
|
|
MathF.Abs(dstRegion.Y2 - dstRegion.Y1));
|
|
|
|
viewports[0] = new GAL.Viewport(
|
|
rect,
|
|
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 ? _programColorBlitClearAlpha : _programColorBlit);
|
|
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, false, dstFormat);
|
|
_pipeline.SetRenderTargetColorMasks(new uint[] { 0xf });
|
|
_pipeline.SetScissors(scissors);
|
|
|
|
if (clearAlpha)
|
|
{
|
|
_pipeline.ClearRenderTargetColor(0, 0, 1, new ColorF(0f, 0f, 0f, 1f));
|
|
}
|
|
|
|
_pipeline.SetViewports(viewports, false);
|
|
_pipeline.SetPrimitiveTopology(GAL.PrimitiveTopology.TriangleStrip);
|
|
_pipeline.Draw(4, 1, 0, 0);
|
|
_pipeline.Finish(gd, cbs);
|
|
|
|
gd.BufferManager.Delete(bufferHandle);
|
|
}
|
|
|
|
public void Clear(
|
|
VulkanRenderer gd,
|
|
Auto<DisposableImageView> dst,
|
|
ReadOnlySpan<float> clearColor,
|
|
uint componentMask,
|
|
int dstWidth,
|
|
int dstHeight,
|
|
VkFormat dstFormat,
|
|
ComponentType type,
|
|
Rectangle<int> scissor)
|
|
{
|
|
const int ClearColorBufferSize = 16;
|
|
|
|
gd.FlushAllCommands();
|
|
|
|
using var cbs = gd.CommandBufferPool.Rent();
|
|
|
|
_pipeline.SetCommandBuffer(cbs);
|
|
|
|
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ClearColorBufferSize, false);
|
|
|
|
gd.BufferManager.SetData<float>(bufferHandle, 0, clearColor);
|
|
|
|
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, new BufferRange(bufferHandle, 0, ClearColorBufferSize)) });
|
|
|
|
Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
|
|
|
|
viewports[0] = new GAL.Viewport(
|
|
new Rectangle<float>(0, 0, dstWidth, dstHeight),
|
|
ViewportSwizzle.PositiveX,
|
|
ViewportSwizzle.PositiveY,
|
|
ViewportSwizzle.PositiveZ,
|
|
ViewportSwizzle.PositiveW,
|
|
0f,
|
|
1f);
|
|
|
|
Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];
|
|
|
|
scissors[0] = scissor;
|
|
|
|
IProgram program;
|
|
|
|
if (type == ComponentType.SignedInteger)
|
|
{
|
|
program = _programColorClearSI;
|
|
}
|
|
else if (type == ComponentType.UnsignedInteger)
|
|
{
|
|
program = _programColorClearUI;
|
|
}
|
|
else
|
|
{
|
|
program = _programColorClearF;
|
|
}
|
|
|
|
_pipeline.SetProgram(program);
|
|
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, false, dstFormat);
|
|
_pipeline.SetRenderTargetColorMasks(new uint[] { componentMask });
|
|
_pipeline.SetViewports(viewports, false);
|
|
_pipeline.SetScissors(scissors);
|
|
_pipeline.SetPrimitiveTopology(GAL.PrimitiveTopology.TriangleStrip);
|
|
_pipeline.Draw(4, 1, 0, 0);
|
|
_pipeline.Finish();
|
|
|
|
gd.BufferManager.Delete(bufferHandle);
|
|
}
|
|
|
|
public void DrawTexture(
|
|
VulkanRenderer gd,
|
|
PipelineBase pipeline,
|
|
TextureView src,
|
|
ISampler srcSampler,
|
|
Extents2DF srcRegion,
|
|
Extents2DF dstRegion)
|
|
{
|
|
const int RegionBufferSize = 16;
|
|
|
|
pipeline.SetTextureAndSampler(ShaderStage.Fragment, 0, src, srcSampler);
|
|
|
|
Span<float> region = stackalloc float[RegionBufferSize / sizeof(float)];
|
|
|
|
region[0] = srcRegion.X1 / src.Width;
|
|
region[1] = srcRegion.X2 / src.Width;
|
|
region[2] = srcRegion.Y1 / src.Height;
|
|
region[3] = srcRegion.Y2 / src.Height;
|
|
|
|
if (dstRegion.X1 > dstRegion.X2)
|
|
{
|
|
(region[0], region[1]) = (region[1], region[0]);
|
|
}
|
|
|
|
if (dstRegion.Y1 > dstRegion.Y2)
|
|
{
|
|
(region[2], region[3]) = (region[3], region[2]);
|
|
}
|
|
|
|
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, RegionBufferSize, false);
|
|
|
|
gd.BufferManager.SetData<float>(bufferHandle, 0, region);
|
|
|
|
pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, new BufferRange(bufferHandle, 0, RegionBufferSize)) });
|
|
|
|
Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
|
|
|
|
var rect = new Rectangle<float>(
|
|
MathF.Min(dstRegion.X1, dstRegion.X2),
|
|
MathF.Min(dstRegion.Y1, dstRegion.Y2),
|
|
MathF.Abs(dstRegion.X2 - dstRegion.X1),
|
|
MathF.Abs(dstRegion.Y2 - dstRegion.Y1));
|
|
|
|
viewports[0] = new GAL.Viewport(
|
|
rect,
|
|
ViewportSwizzle.PositiveX,
|
|
ViewportSwizzle.PositiveY,
|
|
ViewportSwizzle.PositiveZ,
|
|
ViewportSwizzle.PositiveW,
|
|
0f,
|
|
1f);
|
|
|
|
Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];
|
|
|
|
pipeline.SetProgram(_programColorBlit);
|
|
pipeline.SetViewports(viewports, false);
|
|
pipeline.SetPrimitiveTopology(GAL.PrimitiveTopology.TriangleStrip);
|
|
pipeline.Draw(4, 1, 0, 0);
|
|
|
|
gd.BufferManager.Delete(bufferHandle);
|
|
}
|
|
|
|
public unsafe void ConvertI8ToI16(VulkanRenderer gd, CommandBufferScoped cbs, BufferHolder src, BufferHolder dst, int srcOffset, int size)
|
|
{
|
|
ChangeStride(gd, cbs, src, dst, srcOffset, size, 1, 2);
|
|
}
|
|
|
|
public unsafe void ChangeStride(VulkanRenderer gd, CommandBufferScoped cbs, BufferHolder src, BufferHolder dst, int srcOffset, int size, int stride, int newStride)
|
|
{
|
|
bool supportsUint8 = gd.Capabilities.SupportsShaderInt8;
|
|
|
|
int elems = size / stride;
|
|
int newSize = elems * newStride;
|
|
|
|
var srcBufferAuto = src.GetBuffer();
|
|
var dstBufferAuto = dst.GetBuffer();
|
|
|
|
var srcBuffer = srcBufferAuto.Get(cbs, srcOffset, size).Value;
|
|
var dstBuffer = dstBufferAuto.Get(cbs, 0, newSize).Value;
|
|
|
|
var access = supportsUint8 ? AccessFlags.ShaderWriteBit : AccessFlags.TransferWriteBit;
|
|
var stage = supportsUint8 ? PipelineStageFlags.ComputeShaderBit : PipelineStageFlags.TransferBit;
|
|
|
|
BufferHolder.InsertBufferBarrier(
|
|
gd,
|
|
cbs.CommandBuffer,
|
|
dstBuffer,
|
|
BufferHolder.DefaultAccessFlags,
|
|
access,
|
|
PipelineStageFlags.AllCommandsBit,
|
|
stage,
|
|
0,
|
|
newSize);
|
|
|
|
if (supportsUint8)
|
|
{
|
|
const int ParamsBufferSize = 16;
|
|
|
|
Span<int> shaderParams = stackalloc int[ParamsBufferSize / sizeof(int)];
|
|
|
|
shaderParams[0] = stride;
|
|
shaderParams[1] = newStride;
|
|
shaderParams[2] = size;
|
|
shaderParams[3] = srcOffset;
|
|
|
|
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize, false);
|
|
|
|
gd.BufferManager.SetData<int>(bufferHandle, 0, shaderParams);
|
|
|
|
_pipeline.SetCommandBuffer(cbs);
|
|
|
|
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(bufferHandle, 0, ParamsBufferSize)) });
|
|
|
|
Span<Auto<DisposableBuffer>> sbRanges = new Auto<DisposableBuffer>[2];
|
|
|
|
sbRanges[0] = srcBufferAuto;
|
|
sbRanges[1] = dstBufferAuto;
|
|
|
|
_pipeline.SetStorageBuffers(1, sbRanges);
|
|
|
|
_pipeline.SetProgram(_programStrideChange);
|
|
_pipeline.DispatchCompute(1, 1, 1);
|
|
|
|
gd.BufferManager.Delete(bufferHandle);
|
|
|
|
_pipeline.Finish(gd, cbs);
|
|
}
|
|
else
|
|
{
|
|
gd.Api.CmdFillBuffer(cbs.CommandBuffer, dstBuffer, 0, Vk.WholeSize, 0);
|
|
|
|
var bufferCopy = new BufferCopy[elems];
|
|
|
|
for (ulong i = 0; i < (ulong)elems; i++)
|
|
{
|
|
bufferCopy[i] = new BufferCopy((ulong)srcOffset + i * (ulong)stride, i * (ulong)newStride, (ulong)stride);
|
|
}
|
|
|
|
fixed (BufferCopy* pBufferCopy = bufferCopy)
|
|
{
|
|
gd.Api.CmdCopyBuffer(cbs.CommandBuffer, srcBuffer, dstBuffer, (uint)elems, pBufferCopy);
|
|
}
|
|
}
|
|
|
|
BufferHolder.InsertBufferBarrier(
|
|
gd,
|
|
cbs.CommandBuffer,
|
|
dstBuffer,
|
|
access,
|
|
BufferHolder.DefaultAccessFlags,
|
|
stage,
|
|
PipelineStageFlags.AllCommandsBit,
|
|
0,
|
|
newSize);
|
|
}
|
|
|
|
public unsafe void ConvertIndexBuffer(VulkanRenderer gd,
|
|
CommandBufferScoped cbs,
|
|
BufferHolder src,
|
|
BufferHolder dst,
|
|
IndexBufferPattern pattern,
|
|
int indexSize,
|
|
int srcOffset,
|
|
int indexCount)
|
|
{
|
|
// TODO: Support conversion with primitive restart enabled.
|
|
// TODO: Convert with a compute shader?
|
|
|
|
int convertedCount = pattern.GetConvertedCount(indexCount);
|
|
int outputIndexSize = 4;
|
|
|
|
var srcBuffer = src.GetBuffer().Get(cbs, srcOffset, indexCount * indexSize).Value;
|
|
var dstBuffer = dst.GetBuffer().Get(cbs, 0, convertedCount * outputIndexSize).Value;
|
|
|
|
gd.Api.CmdFillBuffer(cbs.CommandBuffer, dstBuffer, 0, Vk.WholeSize, 0);
|
|
|
|
var bufferCopy = new List<BufferCopy>();
|
|
int outputOffset = 0;
|
|
|
|
// Try to merge copies of adjacent indices to reduce copy count.
|
|
int sequenceStart = 0;
|
|
int sequenceLength = 0;
|
|
|
|
foreach (var index in pattern.GetIndexMapping(indexCount))
|
|
{
|
|
if (sequenceLength > 0)
|
|
{
|
|
if (index == sequenceStart + sequenceLength && indexSize == outputIndexSize)
|
|
{
|
|
sequenceLength++;
|
|
continue;
|
|
}
|
|
|
|
// Commit the copy so far.
|
|
bufferCopy.Add(new BufferCopy((ulong)(srcOffset + sequenceStart * indexSize), (ulong)outputOffset, (ulong)(indexSize * sequenceLength)));
|
|
outputOffset += outputIndexSize * sequenceLength;
|
|
}
|
|
|
|
sequenceStart = index;
|
|
sequenceLength = 1;
|
|
}
|
|
|
|
if (sequenceLength > 0)
|
|
{
|
|
// Commit final pending copy.
|
|
bufferCopy.Add(new BufferCopy((ulong)(srcOffset + sequenceStart * indexSize), (ulong)outputOffset, (ulong)(indexSize * sequenceLength)));
|
|
}
|
|
|
|
var bufferCopyArray = bufferCopy.ToArray();
|
|
|
|
BufferHolder.InsertBufferBarrier(
|
|
gd,
|
|
cbs.CommandBuffer,
|
|
dstBuffer,
|
|
BufferHolder.DefaultAccessFlags,
|
|
AccessFlags.TransferWriteBit,
|
|
PipelineStageFlags.AllCommandsBit,
|
|
PipelineStageFlags.TransferBit,
|
|
0,
|
|
convertedCount * outputIndexSize);
|
|
|
|
fixed (BufferCopy* pBufferCopy = bufferCopyArray)
|
|
{
|
|
gd.Api.CmdCopyBuffer(cbs.CommandBuffer, srcBuffer, dstBuffer, (uint)bufferCopyArray.Length, pBufferCopy);
|
|
}
|
|
|
|
BufferHolder.InsertBufferBarrier(
|
|
gd,
|
|
cbs.CommandBuffer,
|
|
dstBuffer,
|
|
AccessFlags.TransferWriteBit,
|
|
BufferHolder.DefaultAccessFlags,
|
|
PipelineStageFlags.TransferBit,
|
|
PipelineStageFlags.AllCommandsBit,
|
|
0,
|
|
convertedCount * outputIndexSize);
|
|
}
|
|
|
|
public void CopyMSToNonMS(VulkanRenderer gd, CommandBufferScoped cbs, TextureView src, TextureView dst, int srcLayer, int dstLayer, int depth)
|
|
{
|
|
const int ParamsBufferSize = 16;
|
|
|
|
Span<int> shaderParams = stackalloc int[ParamsBufferSize / sizeof(int)];
|
|
|
|
int samples = src.Info.Samples;
|
|
|
|
// X and Y are the expected texture samples.
|
|
// Z and W are the actual texture samples used.
|
|
// They may differ if the GPU does not support the samples count requested and we had to use a lower amount.
|
|
(shaderParams[0], shaderParams[1]) = GetSampleCountXYLog2(samples);
|
|
(shaderParams[2], shaderParams[3]) = GetSampleCountXYLog2((int)TextureStorage.ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)samples));
|
|
|
|
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize, false);
|
|
|
|
gd.BufferManager.SetData<int>(bufferHandle, 0, shaderParams);
|
|
|
|
TextureView.InsertImageBarrier(
|
|
gd.Api,
|
|
cbs.CommandBuffer,
|
|
src.GetImage().Get(cbs).Value,
|
|
TextureStorage.DefaultAccessMask,
|
|
AccessFlags.ShaderReadBit,
|
|
PipelineStageFlags.AllCommandsBit,
|
|
PipelineStageFlags.ComputeShaderBit,
|
|
ImageAspectFlags.ColorBit,
|
|
src.FirstLayer + srcLayer,
|
|
src.FirstLevel,
|
|
depth,
|
|
1);
|
|
|
|
_pipeline.SetCommandBuffer(cbs);
|
|
|
|
_pipeline.SetProgram(_programColorCopyToNonMs);
|
|
|
|
var format = GetFormat(src.Info.BytesPerPixel);
|
|
|
|
int dispatchX = (dst.Info.Width + 31) / 32;
|
|
int dispatchY = (dst.Info.Height + 31) / 32;
|
|
|
|
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(bufferHandle, 0, ParamsBufferSize)) });
|
|
|
|
if (src.Info.Target == Target.Texture2DMultisampleArray ||
|
|
dst.Info.Target == Target.Texture2DMultisampleArray)
|
|
{
|
|
for (int z = 0; z < depth; z++)
|
|
{
|
|
var srcView = Create2DLayerView(src, srcLayer + z, format);
|
|
var dstView = Create2DLayerView(dst, dstLayer + z);
|
|
|
|
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 0, srcView, null);
|
|
_pipeline.SetImage(0, dstView, format);
|
|
|
|
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
|
|
|
srcView.Release();
|
|
dstView.Release();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var srcView = Create2DLayerView(src, srcLayer, format);
|
|
|
|
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 0, srcView, null);
|
|
_pipeline.SetImage(0, dst, format);
|
|
|
|
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
|
|
|
srcView.Release();
|
|
}
|
|
|
|
gd.BufferManager.Delete(bufferHandle);
|
|
|
|
_pipeline.Finish(gd, cbs);
|
|
|
|
TextureView.InsertImageBarrier(
|
|
gd.Api,
|
|
cbs.CommandBuffer,
|
|
dst.GetImage().Get(cbs).Value,
|
|
AccessFlags.ShaderWriteBit,
|
|
TextureStorage.DefaultAccessMask,
|
|
PipelineStageFlags.ComputeShaderBit,
|
|
PipelineStageFlags.AllCommandsBit,
|
|
ImageAspectFlags.ColorBit,
|
|
dst.FirstLayer + dstLayer,
|
|
dst.FirstLevel,
|
|
depth,
|
|
1);
|
|
}
|
|
|
|
public void CopyNonMSToMS(VulkanRenderer gd, CommandBufferScoped cbs, TextureView src, TextureView dst, int srcLayer, int dstLayer, int depth)
|
|
{
|
|
const int ParamsBufferSize = 16;
|
|
|
|
Span<int> shaderParams = stackalloc int[ParamsBufferSize / sizeof(int)];
|
|
|
|
int samples = dst.Info.Samples;
|
|
|
|
// X and Y are the expected texture samples.
|
|
// Z and W are the actual texture samples used.
|
|
// They may differ if the GPU does not support the samples count requested and we had to use a lower amount.
|
|
(shaderParams[0], shaderParams[1]) = GetSampleCountXYLog2(samples);
|
|
(shaderParams[2], shaderParams[3]) = GetSampleCountXYLog2((int)TextureStorage.ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)samples));
|
|
|
|
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize, false);
|
|
|
|
gd.BufferManager.SetData<int>(bufferHandle, 0, shaderParams);
|
|
|
|
TextureView.InsertImageBarrier(
|
|
gd.Api,
|
|
cbs.CommandBuffer,
|
|
src.GetImage().Get(cbs).Value,
|
|
TextureStorage.DefaultAccessMask,
|
|
AccessFlags.ShaderReadBit,
|
|
PipelineStageFlags.AllCommandsBit,
|
|
PipelineStageFlags.FragmentShaderBit,
|
|
ImageAspectFlags.ColorBit,
|
|
src.FirstLayer + srcLayer,
|
|
src.FirstLevel,
|
|
depth,
|
|
1);
|
|
|
|
_pipeline.SetCommandBuffer(cbs);
|
|
|
|
_pipeline.SetProgram(_programColorDrawToMs);
|
|
|
|
Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
|
|
|
|
var rect = new Rectangle<float>(0, 0, dst.Width, dst.Height);
|
|
|
|
viewports[0] = new GAL.Viewport(
|
|
rect,
|
|
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, dst.Width, dst.Height);
|
|
|
|
_pipeline.SetRenderTargetColorMasks(new uint[] { 0xf });
|
|
_pipeline.SetScissors(scissors);
|
|
_pipeline.SetViewports(viewports, false);
|
|
_pipeline.SetPrimitiveTopology(GAL.PrimitiveTopology.TriangleStrip);
|
|
|
|
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(bufferHandle, 0, ParamsBufferSize)) });
|
|
|
|
var format = GetFormat(src.Info.BytesPerPixel);
|
|
var vkFormat = FormatTable.GetFormat(format);
|
|
|
|
if (src.Info.Target == Target.Texture2DMultisampleArray ||
|
|
dst.Info.Target == Target.Texture2DMultisampleArray)
|
|
{
|
|
for (int z = 0; z < depth; z++)
|
|
{
|
|
var srcView = Create2DLayerView(src, srcLayer + z, format);
|
|
var dstView = Create2DLayerView(dst, dstLayer + z);
|
|
|
|
_pipeline.SetTextureAndSampler(ShaderStage.Fragment, 0, srcView, null);
|
|
_pipeline.SetRenderTarget(
|
|
((TextureView)dstView).GetView(format).GetImageViewForAttachment(),
|
|
(uint)dst.Width,
|
|
(uint)dst.Height,
|
|
(uint)samples,
|
|
false,
|
|
vkFormat);
|
|
|
|
_pipeline.Draw(4, 1, 0, 0);
|
|
|
|
srcView.Release();
|
|
dstView.Release();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var srcView = Create2DLayerView(src, srcLayer, format);
|
|
|
|
_pipeline.SetTextureAndSampler(ShaderStage.Fragment, 0, srcView, null);
|
|
_pipeline.SetRenderTarget(
|
|
dst.GetView(format).GetImageViewForAttachment(),
|
|
(uint)dst.Width,
|
|
(uint)dst.Height,
|
|
(uint)samples,
|
|
false,
|
|
vkFormat);
|
|
|
|
_pipeline.Draw(4, 1, 0, 0);
|
|
|
|
srcView.Release();
|
|
}
|
|
|
|
gd.BufferManager.Delete(bufferHandle);
|
|
|
|
_pipeline.Finish(gd, cbs);
|
|
|
|
TextureView.InsertImageBarrier(
|
|
gd.Api,
|
|
cbs.CommandBuffer,
|
|
dst.GetImage().Get(cbs).Value,
|
|
AccessFlags.ColorAttachmentWriteBit,
|
|
TextureStorage.DefaultAccessMask,
|
|
PipelineStageFlags.FragmentShaderBit,
|
|
PipelineStageFlags.AllCommandsBit,
|
|
ImageAspectFlags.ColorBit,
|
|
dst.FirstLayer + dstLayer,
|
|
dst.FirstLevel,
|
|
depth,
|
|
1);
|
|
}
|
|
|
|
private static (int, int) GetSampleCountXYLog2(int samples)
|
|
{
|
|
int samplesInXLog2 = 0;
|
|
int samplesInYLog2 = 0;
|
|
|
|
switch (samples)
|
|
{
|
|
case 2: // 2x1
|
|
samplesInXLog2 = 1;
|
|
break;
|
|
case 4: // 2x2
|
|
samplesInXLog2 = 1;
|
|
samplesInYLog2 = 1;
|
|
break;
|
|
case 8: // 4x2
|
|
samplesInXLog2 = 2;
|
|
samplesInYLog2 = 1;
|
|
break;
|
|
case 16: // 4x4
|
|
samplesInXLog2 = 2;
|
|
samplesInYLog2 = 2;
|
|
break;
|
|
case 32: // 8x4
|
|
samplesInXLog2 = 3;
|
|
samplesInYLog2 = 2;
|
|
break;
|
|
case 64: // 8x8
|
|
samplesInXLog2 = 3;
|
|
samplesInYLog2 = 3;
|
|
break;
|
|
}
|
|
|
|
return (samplesInXLog2, samplesInYLog2);
|
|
}
|
|
|
|
private static ITexture Create2DLayerView(TextureView from, int layer, GAL.Format? format = null)
|
|
{
|
|
var target = from.Info.Target switch
|
|
{
|
|
Target.Texture1DArray => Target.Texture1D,
|
|
Target.Texture2DArray => Target.Texture2D,
|
|
Target.Texture2DMultisampleArray => Target.Texture2DMultisample,
|
|
_ => from.Info.Target
|
|
};
|
|
|
|
var info = new TextureCreateInfo(
|
|
from.Info.Width,
|
|
from.Info.Height,
|
|
from.Info.Depth,
|
|
1,
|
|
from.Info.Samples,
|
|
from.Info.BlockWidth,
|
|
from.Info.BlockHeight,
|
|
from.Info.BytesPerPixel,
|
|
format ?? from.Info.Format,
|
|
from.Info.DepthStencilMode,
|
|
target,
|
|
from.Info.SwizzleR,
|
|
from.Info.SwizzleG,
|
|
from.Info.SwizzleB,
|
|
from.Info.SwizzleA);
|
|
|
|
return from.CreateView(info, layer, 0);
|
|
}
|
|
|
|
private static GAL.Format GetFormat(int bytesPerPixel)
|
|
{
|
|
return bytesPerPixel switch
|
|
{
|
|
1 => GAL.Format.R8Uint,
|
|
2 => GAL.Format.R16Uint,
|
|
4 => GAL.Format.R32Uint,
|
|
8 => GAL.Format.R32G32Uint,
|
|
16 => GAL.Format.R32G32B32A32Uint,
|
|
_ => throw new ArgumentException($"Invalid bytes per pixel {bytesPerPixel}.")
|
|
};
|
|
}
|
|
|
|
public void ConvertIndexBufferIndirect(
|
|
VulkanRenderer gd,
|
|
CommandBufferScoped cbs,
|
|
BufferHolder srcIndirectBuffer,
|
|
BufferHolder dstIndirectBuffer,
|
|
BufferRange drawCountBuffer,
|
|
BufferHolder srcIndexBuffer,
|
|
BufferHolder dstIndexBuffer,
|
|
IndexBufferPattern pattern,
|
|
int indexSize,
|
|
int srcIndexBufferOffset,
|
|
int srcIndexBufferSize,
|
|
int srcIndirectBufferOffset,
|
|
bool hasDrawCount,
|
|
int maxDrawCount,
|
|
int indirectDataStride)
|
|
{
|
|
// TODO: Support conversion with primitive restart enabled.
|
|
|
|
BufferRange drawCountBufferAligned = new BufferRange(
|
|
drawCountBuffer.Handle,
|
|
drawCountBuffer.Offset & ~(UniformBufferAlignment - 1),
|
|
UniformBufferAlignment);
|
|
|
|
int indirectDataSize = maxDrawCount * indirectDataStride;
|
|
|
|
int indexCount = srcIndexBufferSize / indexSize;
|
|
int primitivesCount = pattern.GetPrimitiveCount(indexCount);
|
|
int convertedCount = pattern.GetConvertedCount(indexCount);
|
|
int outputIndexSize = 4;
|
|
|
|
var srcBuffer = srcIndexBuffer.GetBuffer().Get(cbs, srcIndexBufferOffset, indexCount * indexSize).Value;
|
|
var dstBuffer = dstIndexBuffer.GetBuffer().Get(cbs, 0, convertedCount * outputIndexSize).Value;
|
|
|
|
const int ParamsBufferSize = 24 * sizeof(int);
|
|
const int ParamsIndirectDispatchOffset = 16 * sizeof(int);
|
|
const int ParamsIndirectDispatchSize = 3 * sizeof(int);
|
|
|
|
Span<int> shaderParams = stackalloc int[ParamsBufferSize / sizeof(int)];
|
|
|
|
shaderParams[8] = pattern.PrimitiveVertices;
|
|
shaderParams[9] = pattern.PrimitiveVerticesOut;
|
|
shaderParams[10] = indexSize;
|
|
shaderParams[11] = outputIndexSize;
|
|
shaderParams[12] = pattern.BaseIndex;
|
|
shaderParams[13] = pattern.IndexStride;
|
|
shaderParams[14] = srcIndexBufferOffset;
|
|
shaderParams[15] = primitivesCount;
|
|
shaderParams[16] = 1;
|
|
shaderParams[17] = 1;
|
|
shaderParams[18] = 1;
|
|
shaderParams[19] = hasDrawCount ? 1 : 0;
|
|
shaderParams[20] = maxDrawCount;
|
|
shaderParams[21] = (drawCountBuffer.Offset & (UniformBufferAlignment - 1)) / 4;
|
|
shaderParams[22] = indirectDataStride / 4;
|
|
shaderParams[23] = srcIndirectBufferOffset / 4;
|
|
|
|
pattern.OffsetIndex.CopyTo(shaderParams.Slice(0, pattern.OffsetIndex.Length));
|
|
|
|
var patternBufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize, false, out var patternBuffer);
|
|
var patternBufferAuto = patternBuffer.GetBuffer();
|
|
|
|
gd.BufferManager.SetData<int>(patternBufferHandle, 0, shaderParams);
|
|
|
|
_pipeline.SetCommandBuffer(cbs);
|
|
|
|
BufferHolder.InsertBufferBarrier(
|
|
gd,
|
|
cbs.CommandBuffer,
|
|
srcIndirectBuffer.GetBuffer().Get(cbs, srcIndirectBufferOffset, indirectDataSize).Value,
|
|
BufferHolder.DefaultAccessFlags,
|
|
AccessFlags.ShaderReadBit,
|
|
PipelineStageFlags.AllCommandsBit,
|
|
PipelineStageFlags.ComputeShaderBit,
|
|
srcIndirectBufferOffset,
|
|
indirectDataSize);
|
|
|
|
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, drawCountBufferAligned) });
|
|
_pipeline.SetStorageBuffers(1, new[] { srcIndirectBuffer.GetBuffer(), dstIndirectBuffer.GetBuffer(), patternBuffer.GetBuffer() });
|
|
|
|
_pipeline.SetProgram(_programConvertIndirectData);
|
|
_pipeline.DispatchCompute(1, 1, 1);
|
|
|
|
BufferHolder.InsertBufferBarrier(
|
|
gd,
|
|
cbs.CommandBuffer,
|
|
patternBufferAuto.Get(cbs, ParamsIndirectDispatchOffset, ParamsIndirectDispatchSize).Value,
|
|
AccessFlags.ShaderWriteBit,
|
|
AccessFlags.IndirectCommandReadBit,
|
|
PipelineStageFlags.ComputeShaderBit,
|
|
PipelineStageFlags.DrawIndirectBit,
|
|
ParamsIndirectDispatchOffset,
|
|
ParamsIndirectDispatchSize);
|
|
|
|
BufferHolder.InsertBufferBarrier(
|
|
gd,
|
|
cbs.CommandBuffer,
|
|
dstBuffer,
|
|
BufferHolder.DefaultAccessFlags,
|
|
AccessFlags.TransferWriteBit,
|
|
PipelineStageFlags.AllCommandsBit,
|
|
PipelineStageFlags.TransferBit,
|
|
0,
|
|
convertedCount * outputIndexSize);
|
|
|
|
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(patternBufferHandle, 0, ParamsBufferSize)) });
|
|
_pipeline.SetStorageBuffers(1, new[] { srcIndexBuffer.GetBuffer(), dstIndexBuffer.GetBuffer() });
|
|
|
|
_pipeline.SetProgram(_programConvertIndexBuffer);
|
|
_pipeline.DispatchComputeIndirect(patternBufferAuto, ParamsIndirectDispatchOffset);
|
|
|
|
BufferHolder.InsertBufferBarrier(
|
|
gd,
|
|
cbs.CommandBuffer,
|
|
dstBuffer,
|
|
AccessFlags.TransferWriteBit,
|
|
BufferHolder.DefaultAccessFlags,
|
|
PipelineStageFlags.TransferBit,
|
|
PipelineStageFlags.AllCommandsBit,
|
|
0,
|
|
convertedCount * outputIndexSize);
|
|
|
|
gd.BufferManager.Delete(patternBufferHandle);
|
|
|
|
_pipeline.Finish(gd, cbs);
|
|
}
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
_programColorBlitClearAlpha.Dispose();
|
|
_programColorBlit.Dispose();
|
|
_programColorClearF.Dispose();
|
|
_programColorClearSI.Dispose();
|
|
_programColorClearUI.Dispose();
|
|
_programStrideChange.Dispose();
|
|
_programConvertIndexBuffer.Dispose();
|
|
_programConvertIndirectData.Dispose();
|
|
_programColorCopyToNonMs.Dispose();
|
|
_programColorDrawToMs.Dispose();
|
|
_samplerNearest.Dispose();
|
|
_samplerLinear.Dispose();
|
|
_pipeline.Dispose();
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
}
|
|
}
|
|
}
|