From 4e536b18d98a37f2e1c03b658753d5a4b9d64e93 Mon Sep 17 00:00:00 2001 From: kuba Date: Mon, 28 Sep 2026 10:20:27 +0200 Subject: [PATCH] [Vk] Keep one depth attachment layout for draws that do not sample it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The depth attachment layout followed each draw's depth/stencil write mask, so a draw sequence that toggles depth writes — opaque geometry, then decals and translucency — changed the layout every few draws, and every layout change ended the rendering scope. The read-only attachment layouts are only required when the draw also samples the attachment. Every other draw now uses the writable layout for the aspects the format has, and the write mask alone selects the access, so the layout stays constant and the scope survives. The presenter answers whether a texture bound for the draw being prepared views the depth attachment's subresources, defaulting to yes for hosts that do not track it. --- .../Gpu/Rendering/DepthTargetResolver.cs | 13 +++++++++++ .../Gpu/Rendering/IRenderHost.cs | 3 +++ .../Gpu/Rendering/RenderExecutor.Targets.cs | 4 +++- .../VulkanVideoPresenter.Descriptors.cs | 22 +++++++++++++++++++ .../VulkanVideoPresenter.Rendering.cs | 1 + 5 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/SharpEmu.Libs/Gpu/Rendering/DepthTargetResolver.cs b/src/SharpEmu.Libs/Gpu/Rendering/DepthTargetResolver.cs index 0c8808e..83733fe 100644 --- a/src/SharpEmu.Libs/Gpu/Rendering/DepthTargetResolver.cs +++ b/src/SharpEmu.Libs/Gpu/Rendering/DepthTargetResolver.cs @@ -63,6 +63,19 @@ public readonly record struct DepthStencilState( public bool IsReadOnly(Format format) => AttachmentWriteAspects(format) == ImageAspectFlags.None; + // The layout for an attachment the draw does not also sample: writable in every aspect the + // format has, so draws that toggle depth or stencil writes keep one layout (and one rendering + // scope). Only a sampled attachment needs the read-only layouts of AttachmentLayout. + public static ImageLayout WritableAttachmentLayout(Format format) + { + var available = ViewFormatRules.DepthAspects(format); + var hasDepth = (available & ImageAspectFlags.DepthBit) != 0; + var hasStencil = (available & ImageAspectFlags.StencilBit) != 0; + return hasDepth && hasStencil ? ImageLayout.DepthStencilAttachmentOptimal + : hasStencil ? ImageLayout.StencilAttachmentOptimal + : ImageLayout.DepthAttachmentOptimal; + } + public ImageLayout AttachmentLayout(Format format) { var available = ViewFormatRules.DepthAspects(format); diff --git a/src/SharpEmu.Libs/Gpu/Rendering/IRenderHost.cs b/src/SharpEmu.Libs/Gpu/Rendering/IRenderHost.cs index b8017b2..c83fdc1 100644 --- a/src/SharpEmu.Libs/Gpu/Rendering/IRenderHost.cs +++ b/src/SharpEmu.Libs/Gpu/Rendering/IRenderHost.cs @@ -137,6 +137,9 @@ public interface IRenderHost void EndRendering(); + // Whether a texture bound for the draw being prepared reads the depth attachment's subresources. + bool SamplesDepthAttachment(in DepthAttachmentState depth) => true; + // The next draw stores to buffers or storage images; called before its BeginRendering. void PrepareMemoryWritingDraw() { } diff --git a/src/SharpEmu.Libs/Gpu/Rendering/RenderExecutor.Targets.cs b/src/SharpEmu.Libs/Gpu/Rendering/RenderExecutor.Targets.cs index c8029b8..09230df 100644 --- a/src/SharpEmu.Libs/Gpu/Rendering/RenderExecutor.Targets.cs +++ b/src/SharpEmu.Libs/Gpu/Rendering/RenderExecutor.Targets.cs @@ -251,7 +251,9 @@ public sealed partial class RenderExecutor } var loadState = depth.LoadState; - var layout = loadState.AttachmentLayout(target.Format); + var layout = _host.SamplesDepthAttachment(in depth) + ? loadState.AttachmentLayout(target.Format) + : DepthStencilState.WritableAttachmentLayout(target.Format); _host.TransitionDepthAttachment(in depth, layout, loadState.AttachmentWriteAspects(target.Format)); var view = target.Request.View; rendering.Width = Math.Min(rendering.Width, target.Width); diff --git a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Descriptors.cs b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Descriptors.cs index 143755c..9bdeb10 100644 --- a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Descriptors.cs +++ b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Descriptors.cs @@ -400,6 +400,28 @@ internal static unsafe partial class VulkanVideoPresenter BindBuffers(stage); ObtainDeviceAddressRanges(stage); BindImages(stage); + _preparedTextures.Add(stage.Textures); + } + + // Textures bound for the draw being prepared; cleared when its preparation closes. + private readonly List _preparedTextures = new(); + + bool IRenderHost.SamplesDepthAttachment(in DepthAttachmentState depth) + { + var image = _imageCache.GetImage(depth.Image); + var attachmentView = depth.Target.Target.Request.View; + foreach (var textures in _preparedTextures) + { + foreach (var texture in textures) + { + if (!texture.IsHostMovie && ReferenceEquals(texture.CachedImage, image) && ViewsOverlap(texture.Request.View, attachmentView)) + { + return true; + } + } + } + + return false; } private BufferView NullStorageBuffer() => new(_bufferCache.GetBuffer(GuestBufferCache.NullBufferId).Handle, 0, NullStorageBufferBytes); diff --git a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Rendering.cs b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Rendering.cs index a6ea683..f2c9ce8 100644 --- a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Rendering.cs +++ b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Rendering.cs @@ -66,6 +66,7 @@ internal static unsafe partial class VulkanVideoPresenter } owner._preparation = null; + owner._preparedTextures.Clear(); streamRetention.Dispose(); if (StencilStorageImages is { } stencilImages) {