[Vk] Keep one depth attachment layout for draws that do not sample it

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.
This commit is contained in:
kuba 2026-09-28 10:20:27 +02:00
parent ff3221c770
commit 4e536b18d9
5 changed files with 42 additions and 1 deletions

View File

@ -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);

View File

@ -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() { }

View File

@ -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);

View File

@ -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<TextureResource[]> _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);

View File

@ -66,6 +66,7 @@ internal static unsafe partial class VulkanVideoPresenter
}
owner._preparation = null;
owner._preparedTextures.Clear();
streamRetention.Dispose();
if (StencilStorageImages is { } stencilImages)
{