mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-22 19:06:15 +08:00
[Vulkan] Honor guest depth clear state (#290)
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user