aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Graphics.Vulkan
diff options
context:
space:
mode:
authorriperiperi <rhy3756547@hotmail.com>2024-09-02 01:28:16 +0100
committerGitHub <noreply@github.com>2024-09-01 21:28:16 -0300
commitca59c3f4998e2d1beb3b0d0214611e3332238557 (patch)
tree38d1a2e2c0b4a906b258ee2e988256299e3e866d /src/Ryujinx.Graphics.Vulkan
parentfdd7ee791cd37546390856f38eab16ea78451742 (diff)
Vulkan: Feedback loop detection and barriers (#7226)
* Vulkan: Feedback loop improvements This PR allows the Vulkan backend to detect attachment feedback loops. These are currently used in the following ways: - Partial use of VK_EXT_attachment_feedback_loop_layout - All renderable textures have AttachmentFeedbackLoopBitExt - Compile pipelines with Color/DepthStencil feedback loop flags when present - Support using FragmentBarrier for feedback loops (fixes regressions from https://github.com/Ryujinx/Ryujinx/pull/7012 ) TODO: - AMD GPUs may need layout transitions for it to properly allow textures to be used in feedback loops. - Use dynamic state for feedback loops. The background pipeline will always miss since feedback loop state isn't known on the GPU project. - How is the barrier dependency flag used? (DXVK just ignores it, there's no vulkan validation...) - Improve subpass dependencies to fix validation errors * Mark field readonly * Add feedback loop dynamic state * fix: add MoltenVK resolver workaround fix: add MoltenVK resolver workaround * Formatting * Fix more complaints * RADV dcc workaround * Use dynamic state properly, cleanup. * Use aspects flags in more places
Diffstat (limited to 'src/Ryujinx.Graphics.Vulkan')
-rw-r--r--src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs39
-rw-r--r--src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs64
-rw-r--r--src/Ryujinx.Graphics.Vulkan/FeedbackLoopAspects.cs12
-rw-r--r--src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs21
-rw-r--r--src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs6
-rw-r--r--src/Ryujinx.Graphics.Vulkan/PipelineBase.cs122
-rw-r--r--src/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs34
-rw-r--r--src/Ryujinx.Graphics.Vulkan/PipelineFull.cs4
-rw-r--r--src/Ryujinx.Graphics.Vulkan/PipelineState.cs38
-rw-r--r--src/Ryujinx.Graphics.Vulkan/TextureStorage.cs74
-rw-r--r--src/Ryujinx.Graphics.Vulkan/TextureView.cs32
-rw-r--r--src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs54
-rw-r--r--src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs34
13 files changed, 480 insertions, 54 deletions
diff --git a/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs b/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs
index a6a006bb..bcfb3dbf 100644
--- a/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs
+++ b/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs
@@ -32,10 +32,12 @@ namespace Ryujinx.Graphics.Vulkan
CommandBuffer
}
+ private bool _feedbackLoopActive;
private PipelineStageFlags _incoherentBufferWriteStages;
private PipelineStageFlags _incoherentTextureWriteStages;
private PipelineStageFlags _extraStages;
private IncoherentBarrierType _queuedIncoherentBarrier;
+ private bool _queuedFeedbackLoopBarrier;
public BarrierBatch(VulkanRenderer gd)
{
@@ -53,17 +55,6 @@ namespace Ryujinx.Graphics.Vulkan
stages |= PipelineStageFlags.TransformFeedbackBitExt;
}
- if (!gd.IsTBDR)
- {
- // Desktop GPUs can transform image barriers into memory barriers.
-
- access |= AccessFlags.DepthStencilAttachmentWriteBit | AccessFlags.ColorAttachmentWriteBit;
- access |= AccessFlags.DepthStencilAttachmentReadBit | AccessFlags.ColorAttachmentReadBit;
-
- stages |= PipelineStageFlags.EarlyFragmentTestsBit | PipelineStageFlags.LateFragmentTestsBit;
- stages |= PipelineStageFlags.ColorAttachmentOutputBit;
- }
-
return (access, stages);
}
@@ -178,16 +169,34 @@ namespace Ryujinx.Graphics.Vulkan
}
_queuedIncoherentBarrier = IncoherentBarrierType.None;
+ _queuedFeedbackLoopBarrier = false;
}
+ else if (_feedbackLoopActive && _queuedFeedbackLoopBarrier)
+ {
+ // Feedback loop barrier.
+
+ MemoryBarrier barrier = new MemoryBarrier()
+ {
+ SType = StructureType.MemoryBarrier,
+ SrcAccessMask = AccessFlags.ShaderWriteBit,
+ DstAccessMask = AccessFlags.ShaderReadBit
+ };
+
+ QueueBarrier(barrier, PipelineStageFlags.FragmentShaderBit, PipelineStageFlags.AllGraphicsBit);
+
+ _queuedFeedbackLoopBarrier = false;
+ }
+
+ _feedbackLoopActive = false;
}
}
public unsafe void Flush(CommandBufferScoped cbs, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass)
{
- Flush(cbs, null, inRenderPass, rpHolder, endRenderPass);
+ Flush(cbs, null, false, inRenderPass, rpHolder, endRenderPass);
}
- public unsafe void Flush(CommandBufferScoped cbs, ShaderCollection program, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass)
+ public unsafe void Flush(CommandBufferScoped cbs, ShaderCollection program, bool feedbackLoopActive, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass)
{
if (program != null)
{
@@ -195,6 +204,8 @@ namespace Ryujinx.Graphics.Vulkan
_incoherentTextureWriteStages |= program.IncoherentTextureWriteStages;
}
+ _feedbackLoopActive |= feedbackLoopActive;
+
FlushMemoryBarrier(program, inRenderPass);
if (!inRenderPass && rpHolder != null)
@@ -406,6 +417,8 @@ namespace Ryujinx.Graphics.Vulkan
{
_queuedIncoherentBarrier = type;
}
+
+ _queuedFeedbackLoopBarrier = true;
}
public void QueueTextureBarrier()
diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs
index 563fdafd..298526d5 100644
--- a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs
+++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs
@@ -4,6 +4,7 @@ using Ryujinx.Graphics.Shader;
using Silk.NET.Vulkan;
using System;
using System.Buffers;
+using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using CompareOp = Ryujinx.Graphics.GAL.CompareOp;
@@ -42,15 +43,15 @@ namespace Ryujinx.Graphics.Vulkan
private record struct TextureRef
{
public ShaderStage Stage;
- public TextureStorage Storage;
- public Auto<DisposableImageView> View;
+ public TextureView View;
+ public Auto<DisposableImageView> ImageView;
public Auto<DisposableSampler> Sampler;
- public TextureRef(ShaderStage stage, TextureStorage storage, Auto<DisposableImageView> view, Auto<DisposableSampler> sampler)
+ public TextureRef(ShaderStage stage, TextureView view, Auto<DisposableImageView> imageView, Auto<DisposableSampler> sampler)
{
Stage = stage;
- Storage = storage;
View = view;
+ ImageView = imageView;
Sampler = sampler;
}
}
@@ -58,14 +59,14 @@ namespace Ryujinx.Graphics.Vulkan
private record struct ImageRef
{
public ShaderStage Stage;
- public TextureStorage Storage;
- public Auto<DisposableImageView> View;
+ public TextureView View;
+ public Auto<DisposableImageView> ImageView;
- public ImageRef(ShaderStage stage, TextureStorage storage, Auto<DisposableImageView> view)
+ public ImageRef(ShaderStage stage, TextureView view, Auto<DisposableImageView> imageView)
{
Stage = stage;
- Storage = storage;
View = view;
+ ImageView = imageView;
}
}
@@ -124,6 +125,8 @@ namespace Ryujinx.Graphics.Vulkan
private readonly TextureView _dummyTexture;
private readonly SamplerHolder _dummySampler;
+ public List<TextureView> FeedbackLoopHazards { get; private set; }
+
public DescriptorSetUpdater(VulkanRenderer gd, Device device)
{
_gd = gd;
@@ -209,10 +212,15 @@ namespace Ryujinx.Graphics.Vulkan
_templateUpdater = new();
}
- public void Initialize()
+ public void Initialize(bool isMainPipeline)
{
MemoryOwner<byte> dummyTextureData = MemoryOwner<byte>.RentCleared(4);
_dummyTexture.SetData(dummyTextureData);
+
+ if (isMainPipeline)
+ {
+ FeedbackLoopHazards = new();
+ }
}
private static bool BindingOverlaps(ref DescriptorBufferInfo info, int bindingOffset, int offset, int size)
@@ -275,6 +283,18 @@ namespace Ryujinx.Graphics.Vulkan
public void InsertBindingBarriers(CommandBufferScoped cbs)
{
+ if ((FeedbackLoopHazards?.Count ?? 0) > 0)
+ {
+ // Clear existing hazards - they will be rebuilt.
+
+ foreach (TextureView hazard in FeedbackLoopHazards)
+ {
+ hazard.DecrementHazardUses();
+ }
+
+ FeedbackLoopHazards.Clear();
+ }
+
foreach (ResourceBindingSegment segment in _program.BindingSegments[PipelineBase.TextureSetIndex])
{
if (segment.Type == ResourceType.TextureAndSampler)
@@ -284,7 +304,7 @@ namespace Ryujinx.Graphics.Vulkan
for (int i = 0; i < segment.Count; i++)
{
ref var texture = ref _textureRefs[segment.Binding + i];
- texture.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, texture.Stage.ConvertToPipelineStageFlags());
+ texture.View?.PrepareForUsage(cbs, texture.Stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards);
}
}
else
@@ -305,7 +325,7 @@ namespace Ryujinx.Graphics.Vulkan
for (int i = 0; i < segment.Count; i++)
{
ref var image = ref _imageRefs[segment.Binding + i];
- image.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, image.Stage.ConvertToPipelineStageFlags());
+ image.View?.PrepareForUsage(cbs, image.Stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards);
}
}
else
@@ -385,9 +405,12 @@ namespace Ryujinx.Graphics.Vulkan
}
else if (image is TextureView view)
{
- view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
+ ref ImageRef iRef = ref _imageRefs[binding];
- _imageRefs[binding] = new(stage, view.Storage, view.GetView(imageFormat).GetIdentityImageView());
+ iRef.View?.ClearUsage(FeedbackLoopHazards);
+ view?.PrepareForUsage(cbs, stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards);
+
+ iRef = new(stage, view, view.GetView(imageFormat).GetIdentityImageView());
}
else
{
@@ -486,9 +509,12 @@ namespace Ryujinx.Graphics.Vulkan
}
else if (texture is TextureView view)
{
- view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
+ ref TextureRef iRef = ref _textureRefs[binding];
+
+ iRef.View?.ClearUsage(FeedbackLoopHazards);
+ view?.PrepareForUsage(cbs, stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards);
- _textureRefs[binding] = new(stage, view.Storage, view.GetImageView(), ((SamplerHolder)sampler)?.GetSampler());
+ iRef = new(stage, view, view.GetImageView(), ((SamplerHolder)sampler)?.GetSampler());
}
else
{
@@ -510,7 +536,7 @@ namespace Ryujinx.Graphics.Vulkan
{
view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
- _textureRefs[binding] = new(stage, view.Storage, view.GetIdentityImageView(), ((SamplerHolder)sampler)?.GetSampler());
+ _textureRefs[binding] = new(stage, view, view.GetIdentityImageView(), ((SamplerHolder)sampler)?.GetSampler());
SignalDirty(DirtyFlags.Texture);
}
@@ -836,7 +862,7 @@ namespace Ryujinx.Graphics.Vulkan
ref var texture = ref textures[i];
ref var refs = ref _textureRefs[binding + i];
- texture.ImageView = refs.View?.Get(cbs).Value ?? default;
+ texture.ImageView = refs.ImageView?.Get(cbs).Value ?? default;
texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default;
if (texture.ImageView.Handle == 0)
@@ -886,7 +912,7 @@ namespace Ryujinx.Graphics.Vulkan
for (int i = 0; i < count; i++)
{
- images[i].ImageView = _imageRefs[binding + i].View?.Get(cbs).Value ?? default;
+ images[i].ImageView = _imageRefs[binding + i].ImageView?.Get(cbs).Value ?? default;
}
tu.Push<DescriptorImageInfo>(images[..count]);
@@ -957,7 +983,7 @@ namespace Ryujinx.Graphics.Vulkan
ref var texture = ref textures[i];
ref var refs = ref _textureRefs[binding + i];
- texture.ImageView = refs.View?.Get(cbs).Value ?? default;
+ texture.ImageView = refs.ImageView?.Get(cbs).Value ?? default;
texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default;
if (texture.ImageView.Handle == 0)
diff --git a/src/Ryujinx.Graphics.Vulkan/FeedbackLoopAspects.cs b/src/Ryujinx.Graphics.Vulkan/FeedbackLoopAspects.cs
new file mode 100644
index 00000000..22f73679
--- /dev/null
+++ b/src/Ryujinx.Graphics.Vulkan/FeedbackLoopAspects.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ [Flags]
+ internal enum FeedbackLoopAspects
+ {
+ None = 0,
+ Color = 1 << 0,
+ Depth = 1 << 1,
+ }
+}
diff --git a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
index 763d26eb..8d80e9d0 100644
--- a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
+++ b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
@@ -302,6 +302,27 @@ namespace Ryujinx.Graphics.Vulkan
_depthStencil?.Storage?.AddStoreOpUsage(true);
}
+ public void ClearBindings()
+ {
+ _depthStencil?.Storage.ClearBindings();
+
+ for (int i = 0; i < _colorsCanonical.Length; i++)
+ {
+ _colorsCanonical[i]?.Storage.ClearBindings();
+ }
+ }
+
+ public void AddBindings()
+ {
+ _depthStencil?.Storage.AddBinding(_depthStencil);
+
+ for (int i = 0; i < _colorsCanonical.Length; i++)
+ {
+ TextureView color = _colorsCanonical[i];
+ color?.Storage.AddBinding(color);
+ }
+ }
+
public (RenderPassHolder rpHolder, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
VulkanRenderer gd,
Device device,
diff --git a/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs b/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs
index b6694bcb..bd17867b 100644
--- a/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs
+++ b/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs
@@ -46,6 +46,8 @@ namespace Ryujinx.Graphics.Vulkan
public readonly bool SupportsViewportArray2;
public readonly bool SupportsHostImportedMemory;
public readonly bool SupportsDepthClipControl;
+ public readonly bool SupportsAttachmentFeedbackLoop;
+ public readonly bool SupportsDynamicAttachmentFeedbackLoop;
public readonly uint SubgroupSize;
public readonly SampleCountFlags SupportedSampleCounts;
public readonly PortabilitySubsetFlags PortabilitySubset;
@@ -84,6 +86,8 @@ namespace Ryujinx.Graphics.Vulkan
bool supportsViewportArray2,
bool supportsHostImportedMemory,
bool supportsDepthClipControl,
+ bool supportsAttachmentFeedbackLoop,
+ bool supportsDynamicAttachmentFeedbackLoop,
uint subgroupSize,
SampleCountFlags supportedSampleCounts,
PortabilitySubsetFlags portabilitySubset,
@@ -121,6 +125,8 @@ namespace Ryujinx.Graphics.Vulkan
SupportsViewportArray2 = supportsViewportArray2;
SupportsHostImportedMemory = supportsHostImportedMemory;
SupportsDepthClipControl = supportsDepthClipControl;
+ SupportsAttachmentFeedbackLoop = supportsAttachmentFeedbackLoop;
+ SupportsDynamicAttachmentFeedbackLoop = supportsDynamicAttachmentFeedbackLoop;
SubgroupSize = subgroupSize;
SupportedSampleCounts = supportedSampleCounts;
PortabilitySubset = portabilitySubset;
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
index 57fa5926..20c4b257 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
@@ -2,6 +2,7 @@ using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
using Silk.NET.Vulkan;
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
@@ -33,6 +34,7 @@ namespace Ryujinx.Graphics.Vulkan
public readonly Action EndRenderPassDelegate;
protected PipelineDynamicState DynamicState;
+ protected bool IsMainPipeline;
private PipelineState _newState;
private bool _graphicsStateDirty;
private bool _computeStateDirty;
@@ -85,6 +87,9 @@ namespace Ryujinx.Graphics.Vulkan
private bool _tfEnabled;
private bool _tfActive;
+ private FeedbackLoopAspects _feedbackLoop;
+ private bool _passWritesDepthStencil;
+
private readonly PipelineColorBlendAttachmentState[] _storedBlend;
public ulong DrawCount { get; private set; }
public bool RenderPassActive { get; private set; }
@@ -126,7 +131,7 @@ namespace Ryujinx.Graphics.Vulkan
public void Initialize()
{
- _descriptorSetUpdater.Initialize();
+ _descriptorSetUpdater.Initialize(IsMainPipeline);
QuadsToTrisPattern = new IndexBufferPattern(Gd, 4, 6, 0, new[] { 0, 1, 2, 0, 2, 3 }, 4, false);
TriFanToTrisPattern = new IndexBufferPattern(Gd, 3, 3, 2, new[] { int.MinValue, -1, 0 }, 1, true);
@@ -814,6 +819,8 @@ namespace Ryujinx.Graphics.Vulkan
_newState.DepthTestEnable = depthTest.TestEnable;
_newState.DepthWriteEnable = depthTest.WriteEnable;
_newState.DepthCompareOp = depthTest.Func.Convert();
+
+ UpdatePassDepthStencil();
SignalStateChange();
}
@@ -1079,6 +1086,8 @@ namespace Ryujinx.Graphics.Vulkan
_newState.StencilFrontPassOp = stencilTest.FrontDpPass.Convert();
_newState.StencilFrontDepthFailOp = stencilTest.FrontDpFail.Convert();
_newState.StencilFrontCompareOp = stencilTest.FrontFunc.Convert();
+
+ UpdatePassDepthStencil();
SignalStateChange();
}
@@ -1426,7 +1435,23 @@ namespace Ryujinx.Graphics.Vulkan
}
}
+ if (IsMainPipeline)
+ {
+ FramebufferParams?.ClearBindings();
+ }
+
FramebufferParams = new FramebufferParams(Device, colors, depthStencil);
+
+ if (IsMainPipeline)
+ {
+ FramebufferParams.AddBindings();
+
+ _newState.FeedbackLoopAspects = FeedbackLoopAspects.None;
+ _bindingBarriersDirty = true;
+ }
+
+ _passWritesDepthStencil = false;
+ UpdatePassDepthStencil();
UpdatePipelineAttachmentFormats();
}
@@ -1493,11 +1518,82 @@ namespace Ryujinx.Graphics.Vulkan
}
}
- Gd.Barriers.Flush(Cbs, _program, RenderPassActive, _rpHolder, EndRenderPassDelegate);
+ Gd.Barriers.Flush(Cbs, _program, _feedbackLoop != 0, RenderPassActive, _rpHolder, EndRenderPassDelegate);
_descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Compute);
}
+ private bool ChangeFeedbackLoop(FeedbackLoopAspects aspects)
+ {
+ if (_feedbackLoop != aspects)
+ {
+ if (Gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop)
+ {
+ DynamicState.SetFeedbackLoop(aspects);
+ }
+ else
+ {
+ _newState.FeedbackLoopAspects = aspects;
+ }
+
+ _feedbackLoop = aspects;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private bool UpdateFeedbackLoop()
+ {
+ List<TextureView> hazards = _descriptorSetUpdater.FeedbackLoopHazards;
+
+ if ((hazards?.Count ?? 0) > 0)
+ {
+ FeedbackLoopAspects aspects = 0;
+
+ foreach (TextureView view in hazards)
+ {
+ // May need to enforce feedback loop layout here in the future.
+ // Though technically, it should always work with the general layout.
+
+ if (view.Info.Format.IsDepthOrStencil())
+ {
+ if (_passWritesDepthStencil)
+ {
+ // If depth/stencil isn't written in the pass, it doesn't count as a feedback loop.
+
+ aspects |= FeedbackLoopAspects.Depth;
+ }
+ }
+ else
+ {
+ aspects |= FeedbackLoopAspects.Color;
+ }
+ }
+
+ return ChangeFeedbackLoop(aspects);
+ }
+ else if (_feedbackLoop != 0)
+ {
+ return ChangeFeedbackLoop(FeedbackLoopAspects.None);
+ }
+
+ return false;
+ }
+
+ private void UpdatePassDepthStencil()
+ {
+ if (!RenderPassActive)
+ {
+ _passWritesDepthStencil = false;
+ }
+
+ // Stencil test being enabled doesn't necessarily mean a write, but it's not critical to check.
+ _passWritesDepthStencil |= (_newState.DepthTestEnable && _newState.DepthWriteEnable) || _newState.StencilTestEnable;
+ }
+
private bool RecreateGraphicsPipelineIfNeeded()
{
if (AutoFlush.ShouldFlushDraw(DrawCount))
@@ -1505,7 +1601,7 @@ namespace Ryujinx.Graphics.Vulkan
Gd.FlushAllCommands();
}
- DynamicState.ReplayIfDirty(Gd.Api, CommandBuffer);
+ DynamicState.ReplayIfDirty(Gd, CommandBuffer);
if (_needsIndexBufferRebind && _indexBufferPattern == null)
{
@@ -1539,7 +1635,15 @@ namespace Ryujinx.Graphics.Vulkan
_vertexBufferUpdater.Commit(Cbs);
}
- if (_graphicsStateDirty || Pbp != PipelineBindPoint.Graphics)
+ if (_bindingBarriersDirty)
+ {
+ // Stale barriers may have been activated by switching program. Emit any that are relevant.
+ _descriptorSetUpdater.InsertBindingBarriers(Cbs);
+
+ _bindingBarriersDirty = false;
+ }
+
+ if (UpdateFeedbackLoop() || _graphicsStateDirty || Pbp != PipelineBindPoint.Graphics)
{
if (!CreatePipeline(PipelineBindPoint.Graphics))
{
@@ -1548,17 +1652,9 @@ namespace Ryujinx.Graphics.Vulkan
_graphicsStateDirty = false;
Pbp = PipelineBindPoint.Graphics;
-
- if (_bindingBarriersDirty)
- {
- // Stale barriers may have been activated by switching program. Emit any that are relevant.
- _descriptorSetUpdater.InsertBindingBarriers(Cbs);
-
- _bindingBarriersDirty = false;
- }
}
- Gd.Barriers.Flush(Cbs, _program, RenderPassActive, _rpHolder, EndRenderPassDelegate);
+ Gd.Barriers.Flush(Cbs, _program, _feedbackLoop != 0, RenderPassActive, _rpHolder, EndRenderPassDelegate);
_descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Graphics);
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs b/src/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs
index 1cc33f72..ad26ff7b 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs
@@ -1,5 +1,6 @@
using Ryujinx.Common.Memory;
using Silk.NET.Vulkan;
+using Silk.NET.Vulkan.Extensions.EXT;
namespace Ryujinx.Graphics.Vulkan
{
@@ -21,6 +22,8 @@ namespace Ryujinx.Graphics.Vulkan
private Array4<float> _blendConstants;
+ private FeedbackLoopAspects _feedbackLoopAspects;
+
public uint ViewportsCount;
public Array16<Viewport> Viewports;
@@ -32,7 +35,8 @@ namespace Ryujinx.Graphics.Vulkan
Scissor = 1 << 2,
Stencil = 1 << 3,
Viewport = 1 << 4,
- All = Blend | DepthBias | Scissor | Stencil | Viewport,
+ FeedbackLoop = 1 << 5,
+ All = Blend | DepthBias | Scissor | Stencil | Viewport | FeedbackLoop,
}
private DirtyFlags _dirty;
@@ -99,13 +103,22 @@ namespace Ryujinx.Graphics.Vulkan
}
}
+ public void SetFeedbackLoop(FeedbackLoopAspects aspects)
+ {
+ _feedbackLoopAspects = aspects;
+
+ _dirty |= DirtyFlags.FeedbackLoop;
+ }
+
public void ForceAllDirty()
{
_dirty = DirtyFlags.All;
}
- public void ReplayIfDirty(Vk api, CommandBuffer commandBuffer)
+ public void ReplayIfDirty(VulkanRenderer gd, CommandBuffer commandBuffer)
{
+ Vk api = gd.Api;
+
if (_dirty.HasFlag(DirtyFlags.Blend))
{
RecordBlend(api, commandBuffer);
@@ -131,6 +144,11 @@ namespace Ryujinx.Graphics.Vulkan
RecordViewport(api, commandBuffer);
}
+ if (_dirty.HasFlag(DirtyFlags.FeedbackLoop) && gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop)
+ {
+ RecordFeedbackLoop(gd.DynamicFeedbackLoopApi, commandBuffer);
+ }
+
_dirty = DirtyFlags.None;
}
@@ -169,5 +187,17 @@ namespace Ryujinx.Graphics.Vulkan
api.CmdSetViewport(commandBuffer, 0, ViewportsCount, Viewports.AsSpan());
}
}
+
+ private readonly void RecordFeedbackLoop(ExtAttachmentFeedbackLoopDynamicState api, CommandBuffer commandBuffer)
+ {
+ ImageAspectFlags aspects = (_feedbackLoopAspects & FeedbackLoopAspects.Color) != 0 ? ImageAspectFlags.ColorBit : 0;
+
+ if ((_feedbackLoopAspects & FeedbackLoopAspects.Depth) != 0)
+ {
+ aspects |= ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit;
+ }
+
+ api.CmdSetAttachmentFeedbackLoopEnable(commandBuffer, aspects);
+ }
}
}
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs b/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs
index cf65eefb..54d43bdb 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs
@@ -28,6 +28,8 @@ namespace Ryujinx.Graphics.Vulkan
_activeBufferMirrors = new();
CommandBuffer = (Cbs = gd.CommandBufferPool.Rent()).CommandBuffer;
+
+ IsMainPipeline = true;
}
private void CopyPendingQuery()
@@ -235,7 +237,7 @@ namespace Ryujinx.Graphics.Vulkan
if (Pipeline != null && Pbp == PipelineBindPoint.Graphics)
{
- DynamicState.ReplayIfDirty(Gd.Api, CommandBuffer);
+ DynamicState.ReplayIfDirty(Gd, CommandBuffer);
}
}
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineState.cs b/src/Ryujinx.Graphics.Vulkan/PipelineState.cs
index 6b6b46a9..a726b9ed 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineState.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineState.cs
@@ -8,6 +8,7 @@ namespace Ryujinx.Graphics.Vulkan
struct PipelineState : IDisposable
{
private const int RequiredSubgroupSize = 32;
+ private const int MaxDynamicStatesCount = 9;
public PipelineUid Internal;
@@ -299,6 +300,12 @@ namespace Ryujinx.Graphics.Vulkan
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFBF) | ((value ? 1UL : 0UL) << 6);
}
+ public FeedbackLoopAspects FeedbackLoopAspects
+ {
+ readonly get => (FeedbackLoopAspects)((Internal.Id8 >> 7) & 0x3);
+ set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFE7F) | (((ulong)value) << 7);
+ }
+
public bool HasTessellationControlShader;
public NativeArray<PipelineShaderStageCreateInfo> Stages;
public PipelineLayout PipelineLayout;
@@ -564,9 +571,11 @@ namespace Ryujinx.Graphics.Vulkan
}
bool supportsExtDynamicState = gd.Capabilities.SupportsExtendedDynamicState;
- int dynamicStatesCount = supportsExtDynamicState ? 8 : 7;
+ bool supportsFeedbackLoopDynamicState = gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop;
- DynamicState* dynamicStates = stackalloc DynamicState[dynamicStatesCount];
+ DynamicState* dynamicStates = stackalloc DynamicState[MaxDynamicStatesCount];
+
+ int dynamicStatesCount = 7;
dynamicStates[0] = DynamicState.Viewport;
dynamicStates[1] = DynamicState.Scissor;
@@ -578,7 +587,12 @@ namespace Ryujinx.Graphics.Vulkan
if (supportsExtDynamicState)
{
- dynamicStates[7] = DynamicState.VertexInputBindingStrideExt;
+ dynamicStates[dynamicStatesCount++] = DynamicState.VertexInputBindingStrideExt;
+ }
+
+ if (supportsFeedbackLoopDynamicState)
+ {
+ dynamicStates[dynamicStatesCount++] = DynamicState.AttachmentFeedbackLoopEnableExt;
}
var pipelineDynamicStateCreateInfo = new PipelineDynamicStateCreateInfo
@@ -588,9 +602,27 @@ namespace Ryujinx.Graphics.Vulkan
PDynamicStates = dynamicStates,
};
+ PipelineCreateFlags flags = 0;
+
+ if (gd.Capabilities.SupportsAttachmentFeedbackLoop)
+ {
+ FeedbackLoopAspects aspects = FeedbackLoopAspects;
+
+ if ((aspects & FeedbackLoopAspects.Color) != 0)
+ {
+ flags |= PipelineCreateFlags.CreateColorAttachmentFeedbackLoopBitExt;
+ }
+
+ if ((aspects & FeedbackLoopAspects.Depth) != 0)
+ {
+ flags |= PipelineCreateFlags.CreateDepthStencilAttachmentFeedbackLoopBitExt;
+ }
+ }
+
var pipelineCreateInfo = new GraphicsPipelineCreateInfo
{
SType = StructureType.GraphicsPipelineCreateInfo,
+ Flags = flags,
StageCount = StagesCount,
PStages = Stages.Pointer,
PVertexInputState = &vertexInputState,
diff --git a/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs b/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs
index f78b9ed4..10b36a3f 100644
--- a/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs
+++ b/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs
@@ -4,6 +4,7 @@ using Silk.NET.Vulkan;
using System;
using System.Collections.Generic;
using System.Numerics;
+using System.Runtime.CompilerServices;
using Format = Ryujinx.Graphics.GAL.Format;
using VkBuffer = Silk.NET.Vulkan.Buffer;
using VkFormat = Silk.NET.Vulkan.Format;
@@ -12,6 +13,11 @@ namespace Ryujinx.Graphics.Vulkan
{
class TextureStorage : IDisposable
{
+ private struct TextureSliceInfo
+ {
+ public int BindCount;
+ }
+
private const MemoryPropertyFlags DefaultImageMemoryFlags =
MemoryPropertyFlags.DeviceLocalBit;
@@ -43,6 +49,7 @@ namespace Ryujinx.Graphics.Vulkan
private readonly Image _image;
private readonly Auto<DisposableImage> _imageAuto;
private readonly Auto<MemoryAllocation> _allocationAuto;
+ private readonly int _depthOrLayers;
private Auto<MemoryAllocation> _foreignAllocationAuto;
private Dictionary<Format, TextureStorage> _aliasedStorages;
@@ -55,6 +62,9 @@ namespace Ryujinx.Graphics.Vulkan
private int _viewsCount;
private readonly ulong _size;
+ private int _bindCount;
+ private readonly TextureSliceInfo[] _slices;
+
public VkFormat VkFormat { get; }
public unsafe TextureStorage(
@@ -73,6 +83,7 @@ namespace Ryujinx.Graphics.Vulkan
var depth = (uint)(info.Target == Target.Texture3D ? info.Depth : 1);
VkFormat = format;
+ _depthOrLayers = info.GetDepthOrLayers();
var type = info.Target.Convert();
@@ -80,7 +91,7 @@ namespace Ryujinx.Graphics.Vulkan
var sampleCountFlags = ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)info.Samples);
- var usage = GetImageUsage(info.Format, info.Target, gd.Capabilities.SupportsShaderStorageImageMultisample);
+ var usage = GetImageUsage(info.Format, info.Target, gd.Capabilities);
var flags = ImageCreateFlags.CreateMutableFormatBit | ImageCreateFlags.CreateExtendedUsageBit;
@@ -148,6 +159,8 @@ namespace Ryujinx.Graphics.Vulkan
InitialTransition(ImageLayout.Preinitialized, ImageLayout.General);
}
+
+ _slices = new TextureSliceInfo[levels * _depthOrLayers];
}
public TextureStorage CreateAliasedColorForDepthStorageUnsafe(Format format)
@@ -292,7 +305,7 @@ namespace Ryujinx.Graphics.Vulkan
}
}
- public static ImageUsageFlags GetImageUsage(Format format, Target target, bool supportsMsStorage)
+ public static ImageUsageFlags GetImageUsage(Format format, Target target, in HardwareCapabilities capabilities)
{
var usage = DefaultUsageFlags;
@@ -305,11 +318,19 @@ namespace Ryujinx.Graphics.Vulkan
usage |= ImageUsageFlags.ColorAttachmentBit;
}
+ bool supportsMsStorage = capabilities.SupportsShaderStorageImageMultisample;
+
if (format.IsImageCompatible() && (supportsMsStorage || !target.IsMultisample()))
{
usage |= ImageUsageFlags.StorageBit;
}
+ if (capabilities.SupportsAttachmentFeedbackLoop &&
+ (usage & (ImageUsageFlags.DepthStencilAttachmentBit | ImageUsageFlags.ColorAttachmentBit)) != 0)
+ {
+ usage |= ImageUsageFlags.AttachmentFeedbackLoopBitExt;
+ }
+
return usage;
}
@@ -510,6 +531,55 @@ namespace Ryujinx.Graphics.Vulkan
}
}
+ public void AddBinding(TextureView view)
+ {
+ // Assumes a view only has a first level.
+
+ int index = view.FirstLevel * _depthOrLayers + view.FirstLayer;
+ int layers = view.Layers;
+
+ for (int i = 0; i < layers; i++)
+ {
+ ref TextureSliceInfo info = ref _slices[index++];
+
+ info.BindCount++;
+ }
+
+ _bindCount++;
+ }
+
+ public void ClearBindings()
+ {
+ if (_bindCount != 0)
+ {
+ Array.Clear(_slices, 0, _slices.Length);
+
+ _bindCount = 0;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool IsBound(TextureView view)
+ {
+ if (_bindCount != 0)
+ {
+ int index = view.FirstLevel * _depthOrLayers + view.FirstLayer;
+ int layers = view.Layers;
+
+ for (int i = 0; i < layers; i++)
+ {
+ ref TextureSliceInfo info = ref _slices[index++];
+
+ if (info.BindCount != 0)
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
public void IncrementViewsCount()
{
_viewsCount++;
diff --git a/src/Ryujinx.Graphics.Vulkan/TextureView.cs b/src/Ryujinx.Graphics.Vulkan/TextureView.cs
index c5453c0c..9b3f4666 100644
--- a/src/Ryujinx.Graphics.Vulkan/TextureView.cs
+++ b/src/Ryujinx.Graphics.Vulkan/TextureView.cs
@@ -23,6 +23,8 @@ namespace Ryujinx.Graphics.Vulkan
private readonly Auto<DisposableImageView> _imageView2dArray;
private Dictionary<Format, TextureView> _selfManagedViews;
+ private int _hazardUses;
+
private readonly TextureCreateInfo _info;
private HashTableSlim<RenderPassCacheKey, RenderPassHolder> _renderPasses;
@@ -60,7 +62,7 @@ namespace Ryujinx.Graphics.Vulkan
gd.Textures.Add(this);
var format = _gd.FormatCapabilities.ConvertToVkFormat(info.Format);
- var usage = TextureStorage.GetImageUsage(info.Format, info.Target, gd.Capabilities.SupportsShaderStorageImageMultisample);
+ var usage = TextureStorage.GetImageUsage(info.Format, info.Target, gd.Capabilities);
var levels = (uint)info.Levels;
var layers = (uint)info.GetLayers();
@@ -1034,6 +1036,34 @@ namespace Ryujinx.Graphics.Vulkan
throw new NotImplementedException();
}
+ public void PrepareForUsage(CommandBufferScoped cbs, PipelineStageFlags flags, List<TextureView> feedbackLoopHazards)
+ {
+ Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, flags);
+
+ if (feedbackLoopHazards != null && Storage.IsBound(this))
+ {
+ feedbackLoopHazards.Add(this);
+ _hazardUses++;
+ }
+ }
+
+ public void ClearUsage(List<TextureView> feedbackLoopHazards)
+ {
+ if (_hazardUses != 0 && feedbackLoopHazards != null)
+ {
+ feedbackLoopHazards.Remove(this);
+ _hazardUses--;
+ }
+ }
+
+ public void DecrementHazardUses()
+ {
+ if (_hazardUses != 0)
+ {
+ _hazardUses--;
+ }
+ }
+
public (RenderPassHolder rpHolder, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
VulkanRenderer gd,
Device device,
diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs
index 5a9844cb..2c327fdb 100644
--- a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs
+++ b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs
@@ -44,6 +44,8 @@ namespace Ryujinx.Graphics.Vulkan
"VK_EXT_4444_formats",
"VK_KHR_8bit_storage",
"VK_KHR_maintenance2",
+ "VK_EXT_attachment_feedback_loop_layout",
+ "VK_EXT_attachment_feedback_loop_dynamic_state",
};
private static readonly string[] _requiredExtensions = {
@@ -357,6 +359,28 @@ namespace Ryujinx.Graphics.Vulkan
features2.PNext = &supportedFeaturesDepthClipControl;
}
+ PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT supportedFeaturesAttachmentFeedbackLoopLayout = new()
+ {
+ SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesExt,
+ PNext = features2.PNext,
+ };
+
+ if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_layout"))
+ {
+ features2.PNext = &supportedFeaturesAttachmentFeedbackLoopLayout;
+ }
+
+ PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT supportedFeaturesDynamicAttachmentFeedbackLoopLayout = new()
+ {
+ SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesExt,
+ PNext = features2.PNext,
+ };
+
+ if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_dynamic_state"))
+ {
+ features2.PNext = &supportedFeaturesDynamicAttachmentFeedbackLoopLayout;
+ }
+
PhysicalDeviceVulkan12Features supportedPhysicalDeviceVulkan12Features = new()
{
SType = StructureType.PhysicalDeviceVulkan12Features,
@@ -531,6 +555,36 @@ namespace Ryujinx.Graphics.Vulkan
pExtendedFeatures = &featuresDepthClipControl;
}
+ PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT featuresAttachmentFeedbackLoopLayout;
+
+ if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_layout") &&
+ supportedFeaturesAttachmentFeedbackLoopLayout.AttachmentFeedbackLoopLayout)
+ {
+ featuresAttachmentFeedbackLoopLayout = new()
+ {
+ SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesExt,
+ PNext = pExtendedFeatures,
+ AttachmentFeedbackLoopLayout = true,
+ };
+
+ pExtendedFeatures = &featuresAttachmentFeedbackLoopLayout;
+ }
+
+ PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT featuresDynamicAttachmentFeedbackLoopLayout;
+
+ if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_dynamic_state") &&
+ supportedFeaturesDynamicAttachmentFeedbackLoopLayout.AttachmentFeedbackLoopDynamicState)
+ {
+ featuresDynamicAttachmentFeedbackLoopLayout = new()
+ {
+ SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesExt,
+ PNext = pExtendedFeatures,
+ AttachmentFeedbackLoopDynamicState = true,
+ };
+
+ pExtendedFeatures = &featuresDynamicAttachmentFeedbackLoopLayout;
+ }
+
var enabledExtensions = _requiredExtensions.Union(_desirableExtensions.Intersect(physicalDevice.DeviceExtensions)).ToArray();
IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length];
diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
index c9ce678b..33e41ab4 100644
--- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
+++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
@@ -38,6 +38,7 @@ namespace Ryujinx.Graphics.Vulkan
internal KhrPushDescriptor PushDescriptorApi { get; private set; }
internal ExtTransformFeedback TransformFeedbackApi { get; private set; }
internal KhrDrawIndirectCount DrawIndirectCountApi { get; private set; }
+ internal ExtAttachmentFeedbackLoopDynamicState DynamicFeedbackLoopApi { get; private set; }
internal uint QueueFamilyIndex { get; private set; }
internal Queue Queue { get; private set; }
@@ -149,6 +150,11 @@ namespace Ryujinx.Graphics.Vulkan
DrawIndirectCountApi = drawIndirectCountApi;
}
+ if (Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtAttachmentFeedbackLoopDynamicState dynamicFeedbackLoopApi))
+ {
+ DynamicFeedbackLoopApi = dynamicFeedbackLoopApi;
+ }
+
if (maxQueueCount >= 2)
{
Api.GetDeviceQueue(_device, queueFamilyIndex, 1, out var backgroundQueue);
@@ -243,6 +249,16 @@ namespace Ryujinx.Graphics.Vulkan
SType = StructureType.PhysicalDeviceDepthClipControlFeaturesExt,
};
+ PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT featuresAttachmentFeedbackLoop = new()
+ {
+ SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesExt,
+ };
+
+ PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT featuresDynamicAttachmentFeedbackLoop = new()
+ {
+ SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesExt,
+ };
+
PhysicalDevicePortabilitySubsetFeaturesKHR featuresPortabilitySubset = new()
{
SType = StructureType.PhysicalDevicePortabilitySubsetFeaturesKhr,
@@ -279,6 +295,22 @@ namespace Ryujinx.Graphics.Vulkan
features2.PNext = &featuresDepthClipControl;
}
+ bool supportsAttachmentFeedbackLoop = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_layout");
+
+ if (supportsAttachmentFeedbackLoop)
+ {
+ featuresAttachmentFeedbackLoop.PNext = features2.PNext;
+ features2.PNext = &featuresAttachmentFeedbackLoop;
+ }
+
+ bool supportsDynamicAttachmentFeedbackLoop = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_dynamic_state");
+
+ if (supportsDynamicAttachmentFeedbackLoop)
+ {
+ featuresDynamicAttachmentFeedbackLoop.PNext = features2.PNext;
+ features2.PNext = &featuresDynamicAttachmentFeedbackLoop;
+ }
+
bool usePortability = _physicalDevice.IsDeviceExtensionPresent("VK_KHR_portability_subset");
if (usePortability)
@@ -401,6 +433,8 @@ namespace Ryujinx.Graphics.Vulkan
_physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"),
_physicalDevice.IsDeviceExtensionPresent(ExtExternalMemoryHost.ExtensionName),
supportsDepthClipControl && featuresDepthClipControl.DepthClipControl,
+ supportsAttachmentFeedbackLoop && featuresAttachmentFeedbackLoop.AttachmentFeedbackLoopLayout,
+ supportsDynamicAttachmentFeedbackLoop && featuresDynamicAttachmentFeedbackLoop.AttachmentFeedbackLoopDynamicState,
propertiesSubgroup.SubgroupSize,
supportedSampleCounts,
portabilityFlags,