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 System.Numerics; 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 _programColorBlitMs; 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 _programColorCopyShortening; private readonly IProgram _programColorCopyToNonMs; private readonly IProgram _programColorCopyWidening; private readonly IProgram _programColorDrawToMs; private readonly IProgram _programDepthBlit; private readonly IProgram _programDepthBlitMs; private readonly IProgram _programStencilBlit; private readonly IProgram _programStencilBlitMs; 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 blitVertexBindings = new ShaderBindings( new[] { 1 }, Array.Empty(), Array.Empty(), Array.Empty()); var blitFragmentBindings = new ShaderBindings( Array.Empty(), Array.Empty(), new[] { 0 }, Array.Empty()); _programColorBlit = gd.CreateProgramWithMinimalLayout(new[] { new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, blitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv), new ShaderSource(ShaderBinaries.ColorBlitFragmentShaderSource, blitFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv), }); _programColorBlitMs = gd.CreateProgramWithMinimalLayout(new[] { new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, blitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv), new ShaderSource(ShaderBinaries.ColorBlitMsFragmentShaderSource, blitFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv), }); _programColorBlitClearAlpha = gd.CreateProgramWithMinimalLayout(new[] { new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, blitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv), new ShaderSource(ShaderBinaries.ColorBlitClearAlphaFragmentShaderSource, blitFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv), }); var colorClearFragmentBindings = new ShaderBindings( Array.Empty(), Array.Empty(), Array.Empty(), Array.Empty()); _programColorClearF = gd.CreateProgramWithMinimalLayout(new[] { new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, blitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv), new ShaderSource(ShaderBinaries.ColorClearFFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv), }); _programColorClearSI = gd.CreateProgramWithMinimalLayout(new[] { new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, blitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv), new ShaderSource(ShaderBinaries.ColorClearSIFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv), }); _programColorClearUI = gd.CreateProgramWithMinimalLayout(new[] { new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, blitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv), new ShaderSource(ShaderBinaries.ColorClearUIFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv), }); var strideChangeBindings = new ShaderBindings( new[] { 0 }, new[] { 1, 2 }, Array.Empty(), Array.Empty()); _programStrideChange = gd.CreateProgramWithMinimalLayout(new[] { new ShaderSource(ShaderBinaries.ChangeBufferStrideShaderSource, strideChangeBindings, ShaderStage.Compute, TargetLanguage.Spirv), }); var colorCopyBindings = new ShaderBindings( new[] { 0 }, Array.Empty(), new[] { 0 }, new[] { 0 }); _programColorCopyShortening = gd.CreateProgramWithMinimalLayout(new[] { new ShaderSource(ShaderBinaries.ColorCopyShorteningComputeShaderSource, colorCopyBindings, ShaderStage.Compute, TargetLanguage.Spirv), }); _programColorCopyToNonMs = gd.CreateProgramWithMinimalLayout(new[] { new ShaderSource(ShaderBinaries.ColorCopyToNonMsComputeShaderSource, colorCopyBindings, ShaderStage.Compute, TargetLanguage.Spirv), }); _programColorCopyWidening = gd.CreateProgramWithMinimalLayout(new[] { new ShaderSource(ShaderBinaries.ColorCopyWideningComputeShaderSource, colorCopyBindings, ShaderStage.Compute, TargetLanguage.Spirv), }); var colorDrawToMsVertexBindings = new ShaderBindings( Array.Empty(), Array.Empty(), Array.Empty(), Array.Empty()); var colorDrawToMsFragmentBindings = new ShaderBindings( new[] { 0 }, Array.Empty(), new[] { 0 }, Array.Empty()); _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(), Array.Empty()); _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(), Array.Empty()); _programConvertIndirectData = gd.CreateProgramWithMinimalLayout(new[] { new ShaderSource(ShaderBinaries.ConvertIndirectDataShaderSource, convertIndirectDataBindings, ShaderStage.Compute, TargetLanguage.Spirv), }); _programDepthBlit = gd.CreateProgramWithMinimalLayout(new[] { new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, blitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv), new ShaderSource(ShaderBinaries.DepthBlitFragmentShaderSource, blitFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv), }); _programDepthBlitMs = gd.CreateProgramWithMinimalLayout(new[] { new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, blitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv), new ShaderSource(ShaderBinaries.DepthBlitMsFragmentShaderSource, blitFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv), }); if (gd.Capabilities.SupportsShaderStencilExport) { _programStencilBlit = gd.CreateProgramWithMinimalLayout(new[] { new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, blitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv), new ShaderSource(ShaderBinaries.StencilBlitFragmentShaderSource, blitFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv), }); _programStencilBlitMs = gd.CreateProgramWithMinimalLayout(new[] { new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, blitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv), new ShaderSource(ShaderBinaries.StencilBlitMsFragmentShaderSource, blitFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv), }); } } public void Blit( VulkanRenderer gd, TextureView src, TextureView dst, Extents2D srcRegion, Extents2D dstRegion, int layers, int levels, bool isDepthOrStencil, bool linearFilter, bool clearAlpha = false) { gd.FlushAllCommands(); using var cbs = gd.CommandBufferPool.Rent(); var dstFormat = dst.VkFormat; var dstSamples = dst.Info.Samples; for (int l = 0; l < levels; l++) { int srcWidth = Math.Max(1, src.Width >> l); int srcHeight = Math.Max(1, src.Height >> l); int dstWidth = Math.Max(1, dst.Width >> l); int dstHeight = Math.Max(1, dst.Height >> l); var mipSrcRegion = new Extents2D( srcRegion.X1 >> l, srcRegion.Y1 >> l, srcRegion.X2 >> l, srcRegion.Y2 >> l); var mipDstRegion = new Extents2D( dstRegion.X1 >> l, dstRegion.Y1 >> l, dstRegion.X2 >> l, dstRegion.Y2 >> l); for (int z = 0; z < layers; z++) { var srcView = Create2DLayerView(src, z, l); var dstView = Create2DLayerView(dst, z, l); if (isDepthOrStencil) { BlitDepthStencil( gd, cbs, srcView, dst.GetImageViewForAttachment(), dstWidth, dstHeight, dstSamples, dstFormat, mipSrcRegion, mipDstRegion); } else { BlitColor( gd, cbs, srcView, dst.GetImageViewForAttachment(), dstWidth, dstHeight, dstSamples, dstFormat, false, mipSrcRegion, mipDstRegion, linearFilter, clearAlpha); } if (srcView != src) { srcView.Release(); } if (dstView != dst) { dstView.Release(); } } } } public void CopyColor( VulkanRenderer gd, CommandBufferScoped cbs, TextureView src, TextureView dst, int srcLayer, int dstLayer, int srcLevel, int dstLevel, int depth, int levels) { for (int l = 0; l < levels; l++) { int mipSrcLevel = srcLevel + l; int mipDstLevel = dstLevel + l; int srcWidth = Math.Max(1, src.Width >> mipSrcLevel); int srcHeight = Math.Max(1, src.Height >> mipSrcLevel); int dstWidth = Math.Max(1, dst.Width >> mipDstLevel); int dstHeight = Math.Max(1, dst.Height >> mipDstLevel); var extents = new Extents2D( 0, 0, Math.Min(srcWidth, dstWidth), Math.Min(srcHeight, dstHeight)); for (int z = 0; z < depth; z++) { var srcView = Create2DLayerView(src, srcLayer + z, mipSrcLevel); var dstView = Create2DLayerView(dst, dstLayer + z, mipDstLevel); BlitColor( gd, cbs, srcView, dstView.GetImageViewForAttachment(), dstView.Width, dstView.Height, dstView.Info.Samples, dstView.VkFormat, dstView.Info.Format.IsDepthOrStencil(), extents, extents, false); if (srcView != src) { srcView.Release(); } if (dstView != dst) { dstView.Release(); } } } } public void BlitColor( VulkanRenderer gd, CommandBufferScoped cbs, TextureView src, Auto dst, int dstWidth, int dstHeight, int dstSamples, VkFormat dstFormat, bool dstIsDepthOrStencil, 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 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); gd.BufferManager.SetData(bufferHandle, 0, region); _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, new BufferRange(bufferHandle, 0, RegionBufferSize)) }); Span viewports = stackalloc GAL.Viewport[1]; var rect = new Rectangle( 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> scissors = stackalloc Rectangle[1]; scissors[0] = new Rectangle(0, 0, dstWidth, dstHeight); if (dstIsDepthOrStencil) { _pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programDepthBlitMs : _programDepthBlit); _pipeline.SetDepthTest(new DepthTestDescriptor(true, true, GAL.CompareOp.Always)); } else if (src.Info.Target.IsMultisample()) { _pipeline.SetProgram(_programColorBlitMs); } else if (clearAlpha) { _pipeline.SetProgram(_programColorBlitClearAlpha); } else { _pipeline.SetProgram(_programColorBlit); } _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, (uint)dstSamples, dstIsDepthOrStencil, 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); if (dstIsDepthOrStencil) { _pipeline.SetDepthTest(new DepthTestDescriptor(false, false, GAL.CompareOp.Always)); } _pipeline.Finish(gd, cbs); gd.BufferManager.Delete(bufferHandle); } private void BlitDepthStencil( VulkanRenderer gd, CommandBufferScoped cbs, TextureView src, Auto dst, int dstWidth, int dstHeight, int dstSamples, VkFormat dstFormat, Extents2D srcRegion, Extents2D dstRegion) { _pipeline.SetCommandBuffer(cbs); const int RegionBufferSize = 16; Span region = stackalloc float[RegionBufferSize / sizeof(float)]; region[0] = (float)srcRegion.X1 / src.Width; region[1] = (float)srcRegion.X2 / src.Width; region[2] = (float)srcRegion.Y1 / src.Height; region[3] = (float)srcRegion.Y2 / src.Height; 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); gd.BufferManager.SetData(bufferHandle, 0, region); _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, new BufferRange(bufferHandle, 0, RegionBufferSize)) }); Span viewports = stackalloc GAL.Viewport[1]; var rect = new Rectangle( 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> scissors = stackalloc Rectangle[1]; scissors[0] = new Rectangle(0, 0, dstWidth, dstHeight); _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, (uint)dstSamples, true, dstFormat); _pipeline.SetScissors(scissors); _pipeline.SetViewports(viewports, false); _pipeline.SetPrimitiveTopology(GAL.PrimitiveTopology.TriangleStrip); var aspectFlags = src.Info.Format.ConvertAspectFlags(); if (aspectFlags.HasFlag(ImageAspectFlags.DepthBit)) { var depthTexture = CreateDepthOrStencilView(src, DepthStencilMode.Depth); BlitDepthStencilDraw(depthTexture, isDepth: true); if (depthTexture != src) { depthTexture.Release(); } } if (aspectFlags.HasFlag(ImageAspectFlags.StencilBit) && _programStencilBlit != null) { var stencilTexture = CreateDepthOrStencilView(src, DepthStencilMode.Stencil); BlitDepthStencilDraw(stencilTexture, isDepth: false); if (stencilTexture != src) { stencilTexture.Release(); } } _pipeline.Finish(gd, cbs); gd.BufferManager.Delete(bufferHandle); } private static TextureView CreateDepthOrStencilView(TextureView depthStencilTexture, DepthStencilMode depthStencilMode) { if (depthStencilTexture.Info.DepthStencilMode == depthStencilMode) { return depthStencilTexture; } return (TextureView)depthStencilTexture.CreateView(new TextureCreateInfo( depthStencilTexture.Info.Width, depthStencilTexture.Info.Height, depthStencilTexture.Info.Depth, depthStencilTexture.Info.Levels, depthStencilTexture.Info.Samples, depthStencilTexture.Info.BlockWidth, depthStencilTexture.Info.BlockHeight, depthStencilTexture.Info.BytesPerPixel, depthStencilTexture.Info.Format, depthStencilMode, depthStencilTexture.Info.Target, SwizzleComponent.Red, SwizzleComponent.Green, SwizzleComponent.Blue, SwizzleComponent.Alpha), 0, 0); } private void BlitDepthStencilDraw(TextureView src, bool isDepth) { _pipeline.SetTextureAndSampler(ShaderStage.Fragment, 0, src, _samplerNearest); if (isDepth) { _pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programDepthBlitMs : _programDepthBlit); _pipeline.SetDepthTest(new DepthTestDescriptor(true, true, GAL.CompareOp.Always)); } else { _pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programStencilBlitMs : _programStencilBlit); _pipeline.SetStencilTest(CreateStencilTestDescriptor(true)); } _pipeline.Draw(4, 1, 0, 0); if (isDepth) { _pipeline.SetDepthTest(new DepthTestDescriptor(false, false, GAL.CompareOp.Always)); } else { _pipeline.SetStencilTest(CreateStencilTestDescriptor(false)); } } private static StencilTestDescriptor CreateStencilTestDescriptor(bool enabled) { return new StencilTestDescriptor( enabled, GAL.CompareOp.Always, GAL.StencilOp.Replace, GAL.StencilOp.Replace, GAL.StencilOp.Replace, 0, 0xff, 0xff, GAL.CompareOp.Always, GAL.StencilOp.Replace, GAL.StencilOp.Replace, GAL.StencilOp.Replace, 0, 0xff, 0xff); } public void Clear( VulkanRenderer gd, Auto dst, ReadOnlySpan clearColor, uint componentMask, int dstWidth, int dstHeight, VkFormat dstFormat, ComponentType type, Rectangle scissor) { const int ClearColorBufferSize = 16; gd.FlushAllCommands(); using var cbs = gd.CommandBufferPool.Rent(); _pipeline.SetCommandBuffer(cbs); var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ClearColorBufferSize); gd.BufferManager.SetData(bufferHandle, 0, clearColor); _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, new BufferRange(bufferHandle, 0, ClearColorBufferSize)) }); Span viewports = stackalloc GAL.Viewport[1]; viewports[0] = new GAL.Viewport( new Rectangle(0, 0, dstWidth, dstHeight), ViewportSwizzle.PositiveX, ViewportSwizzle.PositiveY, ViewportSwizzle.PositiveZ, ViewportSwizzle.PositiveW, 0f, 1f); Span> scissors = stackalloc Rectangle[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 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); gd.BufferManager.SetData(bufferHandle, 0, region); pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, new BufferRange(bufferHandle, 0, RegionBufferSize)) }); Span viewports = stackalloc GAL.Viewport[1]; var rect = new Rectangle( 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> scissors = stackalloc Rectangle[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 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); gd.BufferManager.SetData(bufferHandle, 0, shaderParams); _pipeline.SetCommandBuffer(cbs); _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(bufferHandle, 0, ParamsBufferSize)) }); Span> sbRanges = new Auto[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(); 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 CopyIncompatibleFormats( VulkanRenderer gd, CommandBufferScoped cbs, TextureView src, TextureView dst, int srcLayer, int dstLayer, int srcLevel, int dstLevel, int depth, int levels) { const int ParamsBufferSize = 4; Span shaderParams = stackalloc int[sizeof(int)]; int srcBpp = src.Info.BytesPerPixel; int dstBpp = dst.Info.BytesPerPixel; int ratio = srcBpp < dstBpp ? dstBpp / srcBpp : srcBpp / dstBpp; shaderParams[0] = BitOperations.Log2((uint)ratio); var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize); gd.BufferManager.SetData(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 + srcLevel, depth, levels); _pipeline.SetCommandBuffer(cbs); _pipeline.SetProgram(srcBpp < dstBpp ? _programColorCopyWidening : _programColorCopyShortening); // Calculate ideal component size, given our constraints: // - Component size must not exceed bytes per pixel of source and destination image formats. // - Maximum component size is 4 (R32). int componentSize = Math.Min(Math.Min(srcBpp, dstBpp), 4); var srcFormat = GetFormat(componentSize, srcBpp / componentSize); var dstFormat = GetFormat(componentSize, dstBpp / componentSize); _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(bufferHandle, 0, ParamsBufferSize)) }); for (int l = 0; l < levels; l++) { for (int z = 0; z < depth; z++) { var srcView = Create2DLayerView(src, srcLayer + z, srcLevel + l, srcFormat); var dstView = Create2DLayerView(dst, dstLayer + z, dstLevel + l); _pipeline.SetTextureAndSampler(ShaderStage.Compute, 0, srcView, null); _pipeline.SetImage(0, dstView, dstFormat); int dispatchX = (Math.Min(srcView.Info.Width, dstView.Info.Width) + 31) / 32; int dispatchY = (Math.Min(srcView.Info.Height, dstView.Info.Height) + 31) / 32; _pipeline.DispatchCompute(dispatchX, dispatchY, 1); if (srcView != src) { srcView.Release(); } if (dstView != dst) { dstView.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 + dstLevel, depth, levels); } public void CopyMSToNonMS(VulkanRenderer gd, CommandBufferScoped cbs, TextureView src, TextureView dst, int srcLayer, int dstLayer, int depth) { const int ParamsBufferSize = 16; Span 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); gd.BufferManager.SetData(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)) }); for (int z = 0; z < depth; z++) { var srcView = Create2DLayerView(src, srcLayer + z, 0, format); var dstView = Create2DLayerView(dst, dstLayer + z, 0); _pipeline.SetTextureAndSampler(ShaderStage.Compute, 0, srcView, null); _pipeline.SetImage(0, dstView, format); _pipeline.DispatchCompute(dispatchX, dispatchY, 1); if (srcView != src) { srcView.Release(); } if (dstView != dst) { dstView.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 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); gd.BufferManager.SetData(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 viewports = stackalloc GAL.Viewport[1]; var rect = new Rectangle(0, 0, dst.Width, dst.Height); viewports[0] = new GAL.Viewport( rect, ViewportSwizzle.PositiveX, ViewportSwizzle.PositiveY, ViewportSwizzle.PositiveZ, ViewportSwizzle.PositiveW, 0f, 1f); Span> scissors = stackalloc Rectangle[1]; scissors[0] = new Rectangle(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); for (int z = 0; z < depth; z++) { var srcView = Create2DLayerView(src, srcLayer + z, 0, format); var dstView = Create2DLayerView(dst, dstLayer + z, 0); _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); if (srcView != src) { srcView.Release(); } if (dstView != dst) { dstView.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 TextureView Create2DLayerView(TextureView from, int layer, int level, GAL.Format? format = null) { if (from.Info.Target == Target.Texture2D && level == 0 && (format == null || format.Value == from.Info.Format)) { return from; } var target = from.Info.Target switch { Target.Texture1DArray => Target.Texture1D, Target.Texture2DMultisampleArray => Target.Texture2DMultisample, _ => Target.Texture2D }; 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.CreateViewImpl(info, layer, level); } 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}.") }; } private static GAL.Format GetFormat(int componentSize, int componentsCount) { if (componentSize == 1) { return componentsCount switch { 1 => GAL.Format.R8Uint, 2 => GAL.Format.R8G8Uint, 4 => GAL.Format.R8G8B8A8Uint, _ => throw new ArgumentException($"Invalid components count {componentsCount}.") }; } else if (componentSize == 2) { return componentsCount switch { 1 => GAL.Format.R16Uint, 2 => GAL.Format.R16G16Uint, 4 => GAL.Format.R16G16B16A16Uint, _ => throw new ArgumentException($"Invalid components count {componentsCount}.") }; } else if (componentSize == 4) { return componentsCount switch { 1 => GAL.Format.R32Uint, 2 => GAL.Format.R32G32Uint, 4 => GAL.Format.R32G32B32A32Uint, _ => throw new ArgumentException($"Invalid components count {componentsCount}.") }; } else { throw new ArgumentException($"Invalid component size {componentSize}."); } } 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 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, out var patternBuffer); var patternBufferAuto = patternBuffer.GetBuffer(); gd.BufferManager.SetData(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(); _programColorBlitMs.Dispose(); _programColorClearF.Dispose(); _programColorClearSI.Dispose(); _programColorClearUI.Dispose(); _programStrideChange.Dispose(); _programConvertIndexBuffer.Dispose(); _programConvertIndirectData.Dispose(); _programColorCopyShortening.Dispose(); _programColorCopyToNonMs.Dispose(); _programColorCopyWidening.Dispose(); _programColorDrawToMs.Dispose(); _programDepthBlit.Dispose(); _programDepthBlitMs.Dispose(); _programStencilBlit?.Dispose(); _programStencilBlitMs?.Dispose(); _samplerNearest.Dispose(); _samplerLinear.Dispose(); _pipeline.Dispose(); } } public void Dispose() { Dispose(true); } } }