Ryujinx/Ryujinx.Graphics.Vulkan/PipelineConverter.cs
riperiperi f502cfaf62
Vulkan: Zero blend state when disabled or write mask is 0 (#3719)
* Zero blend state when disabled or write mask is 0

Any difference in the blend state when blend is disabled is meaningless, but Ryujinx would compare different disabled blends and compile them as separate pipelines. This change ensures that all pipelines where blend state is meaningless record it as such, which avoids compiling a bunch of pipelines that are essentially identical.

The NVIDIA driver is pretty forgiving when it comes to silly pipeline misses like this, but other drivers don't offer the same level of kindness.

This should reduce stuttering on those drivers, and might improve overall performance very slightly due to less pipeline variants being in the hash table.

* Fix blend possibly being wrong when an attachment is unmasked
2022-09-29 12:32:49 -03:00

300 lines
12 KiB
C#

using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan;
using System;
namespace Ryujinx.Graphics.Vulkan
{
static class PipelineConverter
{
public static unsafe DisposableRenderPass ToRenderPass(this ProgramPipelineState state, VulkanRenderer gd, Device device)
{
const int MaxAttachments = Constants.MaxRenderTargets + 1;
AttachmentDescription[] attachmentDescs = null;
var subpass = new SubpassDescription()
{
PipelineBindPoint = PipelineBindPoint.Graphics
};
AttachmentReference* attachmentReferences = stackalloc AttachmentReference[MaxAttachments];
Span<int> attachmentIndices = stackalloc int[MaxAttachments];
Span<Silk.NET.Vulkan.Format> attachmentFormats = stackalloc Silk.NET.Vulkan.Format[MaxAttachments];
int attachmentCount = 0;
int colorCount = 0;
int maxColorAttachmentIndex = 0;
for (int i = 0; i < state.AttachmentEnable.Length; i++)
{
if (state.AttachmentEnable[i])
{
maxColorAttachmentIndex = i;
attachmentFormats[attachmentCount] = gd.FormatCapabilities.ConvertToVkFormat(state.AttachmentFormats[i]);
attachmentIndices[attachmentCount++] = i;
colorCount++;
}
}
if (state.DepthStencilEnable)
{
attachmentFormats[attachmentCount++] = gd.FormatCapabilities.ConvertToVkFormat(state.DepthStencilFormat);
}
if (attachmentCount != 0)
{
attachmentDescs = new AttachmentDescription[attachmentCount];
for (int i = 0; i < attachmentCount; i++)
{
int bindIndex = attachmentIndices[i];
attachmentDescs[i] = new AttachmentDescription(
0,
attachmentFormats[i],
TextureStorage.ConvertToSampleCountFlags((uint)state.SamplesCount),
AttachmentLoadOp.Load,
AttachmentStoreOp.Store,
AttachmentLoadOp.Load,
AttachmentStoreOp.Store,
ImageLayout.General,
ImageLayout.General);
}
int colorAttachmentsCount = colorCount;
if (colorAttachmentsCount > MaxAttachments - 1)
{
colorAttachmentsCount = MaxAttachments - 1;
}
if (colorAttachmentsCount != 0)
{
int maxAttachmentIndex = Constants.MaxRenderTargets - 1;
subpass.ColorAttachmentCount = (uint)maxAttachmentIndex + 1;
subpass.PColorAttachments = &attachmentReferences[0];
// Fill with VK_ATTACHMENT_UNUSED to cover any gaps.
for (int i = 0; i <= maxAttachmentIndex; i++)
{
subpass.PColorAttachments[i] = new AttachmentReference(Vk.AttachmentUnused, ImageLayout.Undefined);
}
for (int i = 0; i < colorAttachmentsCount; i++)
{
int bindIndex = attachmentIndices[i];
subpass.PColorAttachments[bindIndex] = new AttachmentReference((uint)i, ImageLayout.General);
}
}
if (state.DepthStencilEnable)
{
uint dsIndex = (uint)attachmentCount - 1;
subpass.PDepthStencilAttachment = &attachmentReferences[MaxAttachments - 1];
*subpass.PDepthStencilAttachment = new AttachmentReference(dsIndex, ImageLayout.General);
}
}
var subpassDependency = new SubpassDependency(
0,
0,
PipelineStageFlags.PipelineStageAllGraphicsBit,
PipelineStageFlags.PipelineStageAllGraphicsBit,
AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit,
AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit,
0);
fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs)
{
var renderPassCreateInfo = new RenderPassCreateInfo()
{
SType = StructureType.RenderPassCreateInfo,
PAttachments = pAttachmentDescs,
AttachmentCount = attachmentDescs != null ? (uint)attachmentDescs.Length : 0,
PSubpasses = &subpass,
SubpassCount = 1,
PDependencies = &subpassDependency,
DependencyCount = 1
};
gd.Api.CreateRenderPass(device, renderPassCreateInfo, null, out var renderPass).ThrowOnError();
return new DisposableRenderPass(gd.Api, device, renderPass);
}
}
public static PipelineState ToVulkanPipelineState(this ProgramPipelineState state, VulkanRenderer gd)
{
PipelineState pipeline = new PipelineState();
pipeline.Initialize();
// It is assumed that Dynamic State is enabled when this conversion is used.
pipeline.BlendConstantA = state.BlendDescriptors[0].BlendConstant.Alpha;
pipeline.BlendConstantB = state.BlendDescriptors[0].BlendConstant.Blue;
pipeline.BlendConstantG = state.BlendDescriptors[0].BlendConstant.Green;
pipeline.BlendConstantR = state.BlendDescriptors[0].BlendConstant.Red;
pipeline.CullMode = state.CullEnable ? state.CullMode.Convert() : CullModeFlags.CullModeNone;
pipeline.DepthBoundsTestEnable = false; // Not implemented.
pipeline.DepthClampEnable = state.DepthClampEnable;
pipeline.DepthTestEnable = state.DepthTest.TestEnable;
pipeline.DepthWriteEnable = state.DepthTest.WriteEnable;
pipeline.DepthCompareOp = state.DepthTest.Func.Convert();
pipeline.FrontFace = state.FrontFace.Convert();
pipeline.HasDepthStencil = state.DepthStencilEnable;
pipeline.LineWidth = state.LineWidth;
pipeline.LogicOpEnable = state.LogicOpEnable;
pipeline.LogicOp = state.LogicOp.Convert();
pipeline.MinDepthBounds = 0f; // Not implemented.
pipeline.MaxDepthBounds = 0f; // Not implemented.
pipeline.PatchControlPoints = state.PatchControlPoints;
pipeline.PolygonMode = Silk.NET.Vulkan.PolygonMode.Fill; // Not implemented.
pipeline.PrimitiveRestartEnable = state.PrimitiveRestartEnable;
pipeline.RasterizerDiscardEnable = state.RasterizerDiscard;
pipeline.SamplesCount = (uint)state.SamplesCount;
if (gd.Capabilities.SupportsMultiView)
{
pipeline.ScissorsCount = Constants.MaxViewports;
pipeline.ViewportsCount = Constants.MaxViewports;
}
else
{
pipeline.ScissorsCount = 1;
pipeline.ViewportsCount = 1;
}
pipeline.DepthBiasEnable = state.BiasEnable != 0;
// Stencil masks and ref are dynamic, so are 0 in the Vulkan pipeline.
pipeline.StencilFrontFailOp = state.StencilTest.FrontSFail.Convert();
pipeline.StencilFrontPassOp = state.StencilTest.FrontDpPass.Convert();
pipeline.StencilFrontDepthFailOp = state.StencilTest.FrontDpFail.Convert();
pipeline.StencilFrontCompareOp = state.StencilTest.FrontFunc.Convert();
pipeline.StencilFrontCompareMask = 0;
pipeline.StencilFrontWriteMask = 0;
pipeline.StencilFrontReference = 0;
pipeline.StencilBackFailOp = state.StencilTest.BackSFail.Convert();
pipeline.StencilBackPassOp = state.StencilTest.BackDpPass.Convert();
pipeline.StencilBackDepthFailOp = state.StencilTest.BackDpFail.Convert();
pipeline.StencilBackCompareOp = state.StencilTest.BackFunc.Convert();
pipeline.StencilBackCompareMask = 0;
pipeline.StencilBackWriteMask = 0;
pipeline.StencilBackReference = 0;
pipeline.StencilTestEnable = state.StencilTest.TestEnable;
pipeline.Topology = gd.TopologyRemap(state.Topology).Convert();
int vaCount = Math.Min(Constants.MaxVertexAttributes, state.VertexAttribCount);
int vbCount = Math.Min(Constants.MaxVertexBuffers, state.VertexBufferCount);
Span<int> vbScalarSizes = stackalloc int[vbCount];
for (int i = 0; i < vaCount; i++)
{
var attribute = state.VertexAttribs[i];
var bufferIndex = attribute.IsZero ? 0 : attribute.BufferIndex + 1;
pipeline.Internal.VertexAttributeDescriptions[i] = new VertexInputAttributeDescription(
(uint)i,
(uint)bufferIndex,
gd.FormatCapabilities.ConvertToVertexVkFormat(attribute.Format),
(uint)attribute.Offset);
if (!attribute.IsZero && bufferIndex < vbCount)
{
vbScalarSizes[bufferIndex - 1] = Math.Max(attribute.Format.GetScalarSize(), vbScalarSizes[bufferIndex - 1]);
}
}
int descriptorIndex = 1;
pipeline.Internal.VertexBindingDescriptions[0] = new VertexInputBindingDescription(0, 0, VertexInputRate.Vertex);
for (int i = 0; i < vbCount; i++)
{
var vertexBuffer = state.VertexBuffers[i];
if (vertexBuffer.Enable)
{
var inputRate = vertexBuffer.Divisor != 0 ? VertexInputRate.Instance : VertexInputRate.Vertex;
int alignedStride = vertexBuffer.Stride;
if (gd.NeedsVertexBufferAlignment(vbScalarSizes[i], out int alignment))
{
alignedStride = (vertexBuffer.Stride + (alignment - 1)) & -alignment;
}
// TODO: Support divisor > 1
pipeline.Internal.VertexBindingDescriptions[descriptorIndex++] = new VertexInputBindingDescription(
(uint)i + 1,
(uint)alignedStride,
inputRate);
}
}
pipeline.VertexBindingDescriptionsCount = (uint)descriptorIndex;
// NOTE: Viewports, Scissors are dynamic.
for (int i = 0; i < 8; i++)
{
var blend = state.BlendDescriptors[i];
if (blend.Enable && state.ColorWriteMask[i] != 0)
{
pipeline.Internal.ColorBlendAttachmentState[i] = new PipelineColorBlendAttachmentState(
blend.Enable,
blend.ColorSrcFactor.Convert(),
blend.ColorDstFactor.Convert(),
blend.ColorOp.Convert(),
blend.AlphaSrcFactor.Convert(),
blend.AlphaDstFactor.Convert(),
blend.AlphaOp.Convert(),
(ColorComponentFlags)state.ColorWriteMask[i]);
}
else
{
pipeline.Internal.ColorBlendAttachmentState[i] = new PipelineColorBlendAttachmentState(
colorWriteMask: (ColorComponentFlags)state.ColorWriteMask[i]);
}
}
int maxAttachmentIndex = 0;
for (int i = 0; i < 8; i++)
{
if (state.AttachmentEnable[i])
{
pipeline.Internal.AttachmentFormats[maxAttachmentIndex++] = gd.FormatCapabilities.ConvertToVkFormat(state.AttachmentFormats[i]);
}
}
if (state.DepthStencilEnable)
{
pipeline.Internal.AttachmentFormats[maxAttachmentIndex++] = gd.FormatCapabilities.ConvertToVkFormat(state.DepthStencilFormat);
}
pipeline.ColorBlendAttachmentStateCount = 8;
pipeline.VertexAttributeDescriptionsCount = (uint)Math.Min(Constants.MaxVertexAttributes, state.VertexAttribCount);
return pipeline;
}
}
}