Ryujinx/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
riperiperi a34fb0e939
Vulkan: Insert barriers before clears (#4596)
* Vulkan: Insert barriers before clears

Newer NVIDIA GPUs seem to be able to start clearing render targets before the last rasterization task is completed, which can cause it to clear a texture while it is being sampled.

This change adds a barrier from read to write when doing a clear, assuming it has been sampled in the past. It could be possible for this to be needed for sample into draw by some GPU, but it's not right now afaik.

This should fix visual artifacts on newer NVIDIA GPUs and driver combos. Contrary to popular belief, Tetris® Effect: Connected is not affected. Testing welcome, hopefully should fix most cases of this and not cost too much performance.

* Visual Studio Moment

* Address feedback

* Address Feedback 2
2023-03-26 12:51:02 +02:00

241 lines
8.1 KiB
C#

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;
private readonly TextureView[] _colors;
private readonly TextureView _depthStencil;
private uint _validColorAttachments;
public uint Width { get; }
public uint Height { get; }
public uint Layers { get; }
public uint[] AttachmentSamples { get; }
public VkFormat[] AttachmentFormats { get; }
public int[] AttachmentIndices { get; }
public int AttachmentsCount { get; }
public int MaxColorAttachmentIndex => AttachmentIndices.Length > 0 ? AttachmentIndices[AttachmentIndices.Length - 1] : -1;
public bool HasDepthStencil { get; }
public int ColorAttachmentsCount => AttachmentsCount - (HasDepthStencil ? 1 : 0);
public FramebufferParams(
Device device,
Auto<DisposableImageView> view,
uint width,
uint height,
uint samples,
bool isDepthStencil,
VkFormat format)
{
_device = device;
_attachments = new[] { view };
_validColorAttachments = isDepthStencil ? 0u : 1u;
Width = width;
Height = height;
Layers = 1;
AttachmentSamples = new[] { samples };
AttachmentFormats = new[] { format };
AttachmentIndices = isDepthStencil ? Array.Empty<int>() : 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];
_colors = new TextureView[colorsCount];
AttachmentSamples = new uint[count];
AttachmentFormats = new VkFormat[count];
AttachmentIndices = new int[colorsCount];
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();
_colors[index] = texture;
_validColorAttachments |= 1u << bindIndex;
AttachmentSamples[index] = (uint)texture.Info.Samples;
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();
_depthStencil = dsTexture;
AttachmentSamples[count - 1] = (uint)dsTexture.Info.Samples;
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;
}
public Auto<DisposableImageView> GetAttachment(int index)
{
if ((uint)index >= _attachments.Length)
{
return null;
}
return _attachments[index];
}
public ComponentType GetAttachmentComponentType(int index)
{
if (_colors != null && (uint)index < _colors.Length)
{
var format = _colors[index].Info.Format;
if (format.IsSint())
{
return ComponentType.SignedInteger;
}
else if (format.IsUint())
{
return ComponentType.UnsignedInteger;
}
}
return ComponentType.Float;
}
public bool IsValidColorAttachment(int bindIndex)
{
return (uint)bindIndex < Constants.MaxRenderTargets && (_validColorAttachments & (1u << bindIndex)) != 0;
}
private static bool IsValidTextureView(ITexture texture)
{
return texture is TextureView view && view.Valid;
}
public ClearRect GetClearRect(Rectangle<int> scissor, int layer, int layerCount)
{
int x = scissor.X;
int y = scissor.Y;
int width = Math.Min((int)Width - scissor.X, scissor.Width);
int height = Math.Min((int)Height - scissor.Y, scissor.Height);
return new ClearRect(new Rect2D(new Offset2D(x, y), new Extent2D((uint)width, (uint)height)), (uint)layer, (uint)layerCount);
}
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);
}
public void UpdateModifications()
{
if (_colors != null)
{
for (int index = 0; index < _colors.Length; index++)
{
_colors[index].Storage.SetModification(
AccessFlags.ColorAttachmentWriteBit,
PipelineStageFlags.ColorAttachmentOutputBit);
}
}
_depthStencil?.Storage.SetModification(
AccessFlags.DepthStencilAttachmentWriteBit,
PipelineStageFlags.ColorAttachmentOutputBit);
}
public void InsertClearBarrier(CommandBufferScoped cbs, int index)
{
if (_colors != null)
{
int realIndex = Array.IndexOf(AttachmentIndices, index);
if (realIndex != -1)
{
_colors[realIndex].Storage?.InsertReadToWriteBarrier(cbs, AccessFlags.ColorAttachmentWriteBit, PipelineStageFlags.ColorAttachmentOutputBit);
}
}
}
public void InsertClearBarrierDS(CommandBufferScoped cbs)
{
_depthStencil?.Storage?.InsertReadToWriteBarrier(cbs, AccessFlags.DepthStencilAttachmentWriteBit, PipelineStageFlags.EarlyFragmentTestsBit);
}
}
}