[Vulkan] Honor guest depth clear state (#290)

This commit is contained in:
kuba
2026-07-17 01:29:53 +02:00
committed by GitHub
parent 2129a12684
commit dabf723b3e
4 changed files with 150 additions and 19 deletions
+14 -10
View File
@@ -121,6 +121,7 @@ public static partial class AgcExports
private const uint CbBlend0Control = 0x1E0; private const uint CbBlend0Control = 0x1E0;
private const uint PaScModeCntl0 = 0x292; private const uint PaScModeCntl0 = 0x292;
// GFX10 DB context registers (register byte address minus 0x28000, / 4). // GFX10 DB context registers (register byte address minus 0x28000, / 4).
private const uint DbRenderControl = 0x000;
private const uint DbDepthView = 0x002; private const uint DbDepthView = 0x002;
private const uint DbDepthSizeXy = 0x007; private const uint DbDepthSizeXy = 0x007;
private const uint DbDepthClear = 0x00B; private const uint DbDepthClear = 0x00B;
@@ -5291,7 +5292,7 @@ public static partial class AgcExports
var hasDepthOnlyCandidate = hasExportShader && var hasDepthOnlyCandidate = hasExportShader &&
!hasPixelShader && !hasPixelShader &&
depthTarget is not null && depthTarget is not null &&
(depthState.TestEnable || depthState.WriteEnable); (depthState.TestEnable || depthState.WriteEnable || depthState.ClearEnable);
if (hasDepthOnlyCandidate && if (hasDepthOnlyCandidate &&
TryCreateTranslatedDepthOnlyGuestDraw( TryCreateTranslatedDepthOnlyGuestDraw(
ctx, ctx,
@@ -6652,27 +6653,30 @@ public static partial class AgcExports
// DB_DEPTH_CONTROL (context register 0x200): Z_ENABLE bit1, Z_WRITE_ENABLE // DB_DEPTH_CONTROL (context register 0x200): Z_ENABLE bit1, Z_WRITE_ENABLE
// bit2, ZFUNC bits[6:4] (GCN compare, matches Vulkan CompareOp ordering). // bit2, ZFUNC bits[6:4] (GCN compare, matches Vulkan CompareOp ordering).
// DB_RENDER_CONTROL (context register 0x000): DEPTH_CLEAR_ENABLE bit0.
private const uint DbDepthControl = 0x200; private const uint DbDepthControl = 0x200;
private static GuestDepthState DecodeDepthState( internal static GuestDepthState DecodeDepthState(
IReadOnlyDictionary<uint, uint> registers) IReadOnlyDictionary<uint, uint> registers)
{ {
if (!registers.TryGetValue(DbDepthControl, out var control)) var hasDepthControl = registers.TryGetValue(DbDepthControl, out var control);
{ registers.TryGetValue(DbRenderControl, out var renderControl);
return GuestDepthState.Default;
}
var testEnable = (control & 0x2u) != 0; var testEnable = (control & 0x2u) != 0;
var writeEnable = (control & 0x4u) != 0; var writeEnable = (control & 0x4u) != 0;
var compareOp = (control >> 4) & 0x7u; var compareOp = hasDepthControl
return new GuestDepthState(testEnable, writeEnable, compareOp); ? (control >> 4) & 0x7u
: GuestDepthState.Default.CompareOp;
var clearEnable = (renderControl & 0x1u) != 0;
return new GuestDepthState(testEnable, writeEnable, compareOp, clearEnable);
} }
private static GuestDepthTarget? DecodeDepthTarget( private static GuestDepthTarget? DecodeDepthTarget(
IReadOnlyDictionary<uint, uint> registers) IReadOnlyDictionary<uint, uint> registers)
{ {
var depthState = DecodeDepthState(registers); var depthState = DecodeDepthState(registers);
if (!depthState.TestEnable && !depthState.WriteEnable) if (!depthState.TestEnable &&
!depthState.WriteEnable &&
!depthState.ClearEnable)
{ {
return null; return null;
} }
+3 -2
View File
@@ -91,9 +91,10 @@ internal readonly record struct GuestRasterState(
internal readonly record struct GuestDepthState( internal readonly record struct GuestDepthState(
bool TestEnable, bool TestEnable,
bool WriteEnable, bool WriteEnable,
uint CompareOp) uint CompareOp,
bool ClearEnable = false)
{ {
public static GuestDepthState Default { get; } = new(false, false, 7); public static GuestDepthState Default { get; } = new(false, false, 7, false);
} }
/// <summary>Factors/funcs are raw guest CB_BLEND*_CONTROL register bitfields; the /// <summary>Factors/funcs are raw guest CB_BLEND*_CONTROL register bitfields; the
@@ -2213,6 +2213,12 @@ internal static unsafe class VulkanVideoPresenter
return keys; return keys;
} }
internal static bool ShouldAttachGuestDepth(
GuestDepthTarget? target,
GuestDepthState state) =>
target is not null &&
(state.TestEnable || state.WriteEnable || state.ClearEnable);
private readonly record struct Presentation( private readonly record struct Presentation(
byte[]? Pixels, byte[]? Pixels,
uint Width, uint Width,
@@ -9440,6 +9446,7 @@ internal static unsafe class VulkanVideoPresenter
{ {
var extent = new Extent2D(firstTarget.Width, firstTarget.Height); var extent = new Extent2D(firstTarget.Width, firstTarget.Height);
var draw = work.Draw; var draw = work.Draw;
var clearDepthForDraw = draw.RenderState.Depth.ClearEnable;
if (work.DepthTarget?.ReadOnly == true && draw.RenderState.Depth.WriteEnable) if (work.DepthTarget?.ReadOnly == true && draw.RenderState.Depth.WriteEnable)
{ {
draw = draw with draw = draw with
@@ -9452,9 +9459,10 @@ internal static unsafe class VulkanVideoPresenter
} }
GuestDepthResource? depth = null; GuestDepthResource? depth = null;
DepthFramebufferResource? depthFramebuffer = null; DepthFramebufferResource? depthFramebuffer = null;
if (work.DepthTarget is { } depthTarget && if (ShouldAttachGuestDepth(
(draw.RenderState.Depth.TestEnable || work.DepthTarget,
draw.RenderState.Depth.WriteEnable)) draw.RenderState.Depth) &&
work.DepthTarget is { } depthTarget)
{ {
var effectiveDepthTarget = depthTarget; var effectiveDepthTarget = depthTarget;
if (depthTarget.Width < firstTarget.Width || depthTarget.Height < firstTarget.Height) if (depthTarget.Width < firstTarget.Width || depthTarget.Height < firstTarget.Height)
@@ -9501,21 +9509,45 @@ internal static unsafe class VulkanVideoPresenter
depth = GetOrCreateGuestDepth(effectiveDepthTarget); depth = GetOrCreateGuestDepth(effectiveDepthTarget);
PrepareFirstUseDepth(depth, draw.RenderState.Depth); PrepareFirstUseDepth(depth, draw.RenderState.Depth);
if (clearDepthForDraw)
{
depth.GuestClearDepth = effectiveDepthTarget.ClearDepth;
depth.ClearDepth = effectiveDepthTarget.ClearDepth;
}
if (targets.Length == 1) if (targets.Length == 1)
{ {
depthFramebuffer = GetOrCreateDepthFramebuffer(firstTarget, depth); depthFramebuffer = GetOrCreateDepthFramebuffer(firstTarget, depth);
} }
} }
if (clearDepthForDraw)
{
// DB_RENDER_CONTROL.DEPTH_CLEAR_ENABLE makes this a DB
// clear operation. The draw still produces color, but its
// interpolated vertex Z is not the guest clear value.
draw = draw with
{
RenderState = draw.RenderState with
{
Depth = draw.RenderState.Depth with
{
TestEnable = false,
WriteEnable = false,
ClearEnable = false,
},
},
};
}
var renderPass = depthFramebuffer is null var renderPass = depthFramebuffer is null
? firstTarget.Initialized ? firstTarget.Initialized
? firstTarget.RenderPass ? firstTarget.RenderPass
: firstTarget.InitialRenderPass : firstTarget.InitialRenderPass
: firstTarget.Initialized : firstTarget.Initialized
? depth!.Initialized ? depth!.Initialized && !clearDepthForDraw
? depthFramebuffer.LoadRenderPass ? depthFramebuffer.LoadRenderPass
: depthFramebuffer.DepthClearRenderPass : depthFramebuffer.DepthClearRenderPass
: depth!.Initialized : depth!.Initialized && !clearDepthForDraw
? depthFramebuffer.ColorClearRenderPass ? depthFramebuffer.ColorClearRenderPass
: depthFramebuffer.BothClearRenderPass; : depthFramebuffer.BothClearRenderPass;
var framebuffer = depthFramebuffer?.Framebuffer ?? firstTarget.Framebuffer; var framebuffer = depthFramebuffer?.Framebuffer ?? firstTarget.Framebuffer;
@@ -9531,7 +9563,7 @@ internal static unsafe class VulkanVideoPresenter
targets.Select(target => targets.Select(target =>
target.Initialized || target.InitialUploadPending).ToArray(), target.Initialized || target.InitialUploadPending).ToArray(),
depth, depth,
depth?.Initialized == true); depth?.Initialized == true && !clearDepthForDraw);
transientRenderPass = renderPass; transientRenderPass = renderPass;
transientFramebuffer = framebuffer; transientFramebuffer = framebuffer;
} }
@@ -9719,7 +9751,12 @@ internal static unsafe class VulkanVideoPresenter
if (depth is not null) if (depth is not null)
{ {
depth.Initialized = true; depth.Initialized = true;
if (draw.RenderState.Depth.WriteEnable) depth.Layout = ImageLayout.DepthStencilAttachmentOptimal;
if (clearDepthForDraw)
{
depth.InitializationSource = "guest-depth-clear";
}
else if (draw.RenderState.Depth.WriteEnable)
{ {
depth.InitializationSource = "translated-depth-write"; depth.InitializationSource = "translated-depth-write";
} }
@@ -0,0 +1,89 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.Libs.Agc;
using SharpEmu.Libs.Gpu;
using SharpEmu.Libs.VideoOut;
using Xunit;
namespace SharpEmu.Libs.Tests.VideoOut;
public sealed class VulkanDepthAttachmentTests
{
private static readonly GuestDepthTarget Target = new(
ReadAddress: 0x1000,
WriteAddress: 0x1000,
Width: 1920,
Height: 1080,
GuestFormat: 1,
SwizzleMode: 0,
ClearDepth: 1f,
ReadOnly: false);
[Fact]
public void GuestDepthTarget_AttachesForDepthWork()
{
var state = new GuestDepthState(
TestEnable: true,
WriteEnable: true,
CompareOp: 3);
Assert.True(VulkanVideoPresenter.ShouldAttachGuestDepth(Target, state));
}
[Theory]
[InlineData(true, false)]
[InlineData(false, true)]
public void GuestDepthTarget_AttachesForEitherDepthOperation(
bool testEnable,
bool writeEnable)
{
var state = new GuestDepthState(testEnable, writeEnable, CompareOp: 3);
Assert.True(VulkanVideoPresenter.ShouldAttachGuestDepth(Target, state));
}
[Fact]
public void GuestDepthTarget_RequiresTargetAndDepthWork()
{
var state = GuestDepthState.Default;
Assert.False(VulkanVideoPresenter.ShouldAttachGuestDepth(Target, state));
Assert.False(VulkanVideoPresenter.ShouldAttachGuestDepth(
target: null,
new GuestDepthState(true, false, CompareOp: 3)));
}
[Fact]
public void GuestDepthTarget_AttachesForDepthClear()
{
var state = new GuestDepthState(
TestEnable: false,
WriteEnable: false,
CompareOp: 7,
ClearEnable: true);
Assert.True(VulkanVideoPresenter.ShouldAttachGuestDepth(Target, state));
}
[Theory]
[InlineData(0x41u, true)]
[InlineData(0x40u, false)]
public void DepthState_DecodesRenderControlClearBit(
uint renderControl,
bool clearEnable)
{
var registers = new Dictionary<uint, uint>
{
[0x000] = renderControl,
[0x200] = 0x776,
};
var state = AgcExports.DecodeDepthState(registers);
Assert.True(state.TestEnable);
Assert.True(state.WriteEnable);
Assert.Equal(7u, state.CompareOp);
Assert.Equal(clearEnable, state.ClearEnable);
}
}