Use dynamic stencil state

This commit is contained in:
nmzik
2026-09-14 16:19:22 +04:00
parent fe26c1de17
commit bce8924dd6
8 changed files with 78 additions and 105 deletions
@@ -71,19 +71,19 @@ static vk::StencilOp ConvertStencilOp(uint8_t value, uint8_t write_mask, uint8_t
}
}
static PipelineStencilStaticState ConvertStencilState(
static vk::StencilOpState ConvertStencilState(
uint8_t compare, const std::array<uint8_t, 3>& operations, uint8_t op_value,
PipelineStencilDynamicState& dynamic) {
const auto test_value = dynamic.reference;
const vk::StencilOpState& state) {
const auto test_value = state.reference;
auto reference = test_value;
auto required_bits = dynamic.compareMask;
auto required_bits = state.compareMask;
if (compare == static_cast<uint8_t>(vk::CompareOp::eAlways) ||
compare == static_cast<uint8_t>(vk::CompareOp::eNever)) {
required_bits = 0;
}
std::array<vk::StencilOp, 3> converted {};
for (size_t i = 0; i < operations.size(); i++) {
converted[i] = ConvertStencilOp(operations[i], dynamic.writeMask, op_value);
converted[i] = ConvertStencilOp(operations[i], state.writeMask, op_value);
if (converted[i] != vk::StencilOp::eReplace) {
continue;
}
@@ -91,18 +91,18 @@ static PipelineStencilStaticState ConvertStencilState(
if (static_cast<Prospero::StencilOp>(operations[i]) == Prospero::StencilOp::kReplaceOp) {
replacement = op_value;
}
if (((reference ^ replacement) & required_bits & dynamic.writeMask) != 0) {
if (((reference ^ replacement) & required_bits & state.writeMask) != 0) {
DepthFatal("unsupported stencil replacement: compare=%u, compare mask=0x%02" PRIx32
", write mask=0x%02" PRIx32 ", operation value=0x%02" PRIx8
", test value=0x%02" PRIx32,
compare, dynamic.compareMask, dynamic.writeMask, op_value, test_value);
compare, state.compareMask, state.writeMask, op_value, test_value);
}
// Vulkan shares one reference between comparison and every replacement on this face.
reference = (reference & ~dynamic.writeMask) | (replacement & dynamic.writeMask);
required_bits |= dynamic.writeMask;
reference = (reference & ~state.writeMask) | (replacement & state.writeMask);
required_bits |= state.writeMask;
}
dynamic.reference = reference;
return {converted[0], converted[1], converted[2], static_cast<vk::CompareOp>(compare)};
return {converted[0], converted[1], converted[2], static_cast<vk::CompareOp>(compare),
state.compareMask, state.writeMask, reference};
}
[[nodiscard]] static vk::Format ResolveHostDepthAttachmentFormat(const CommandBuffer& buffer,
@@ -333,18 +333,17 @@ void RenderExecutor::ResolveRenderDepthTarget(CommandBuffer& buffer, RenderDepth
dc.stencilfunc_bf > static_cast<uint8_t>(vk::CompareOp::eAlways))) {
DepthFatal("unsupported stencil compare state");
}
r.stencil_dynamic_front = {sm.stencil_mask, front_write_mask, sm.stencil_testval};
r.stencil_static_front = ConvertStencilState(
r.stencil_front = ConvertStencilState(
dc.stencilfunc, {sc.stencil_fail, sc.stencil_zpass, sc.stencil_zfail},
sm.stencil_opval, r.stencil_dynamic_front);
sm.stencil_opval, {.compareMask = sm.stencil_mask, .writeMask = front_write_mask,
.reference = sm.stencil_testval});
if (dc.backface_enable) {
r.stencil_dynamic_back = {sm.stencil_mask_bf, back_write_mask, sm.stencil_testval_bf};
r.stencil_static_back = ConvertStencilState(
r.stencil_back = ConvertStencilState(
dc.stencilfunc_bf, {sc.stencil_fail_bf, sc.stencil_zpass_bf, sc.stencil_zfail_bf},
sm.stencil_opval_bf, r.stencil_dynamic_back);
sm.stencil_opval_bf, {.compareMask = sm.stencil_mask_bf, .writeMask = back_write_mask,
.reference = sm.stencil_testval_bf});
} else {
r.stencil_static_back = r.stencil_static_front;
r.stencil_dynamic_back = r.stencil_dynamic_front;
r.stencil_back = r.stencil_front;
}
}
auto& cache = m_context.GetTextureCache();
@@ -430,14 +429,13 @@ vk::ImageAspectFlags RenderDepthInfo::AttachmentWriteAspects() const {
return writes;
}
const auto face_writes = [&](const PipelineStencilStaticState& state,
const PipelineStencilDynamicState& dynamic) {
if (dynamic.writeMask == 0) {
const auto face_writes = [&](const vk::StencilOpState& state) {
if (state.writeMask == 0) {
return false;
}
bool can_pass = state.compareOp != vk::CompareOp::eNever;
bool can_fail = state.compareOp != vk::CompareOp::eAlways;
if (dynamic.compareMask == 0) {
if (state.compareMask == 0) {
switch (state.compareOp) {
case vk::CompareOp::eEqual:
case vk::CompareOp::eLessOrEqual:
@@ -463,8 +461,7 @@ vk::ImageAspectFlags RenderDepthInfo::AttachmentWriteAspects() const {
(can_pass && depth_fail && state.depthFailOp != vk::StencilOp::eKeep);
};
if (stencil_clear_enable ||
(stencil_test_enable && (face_writes(stencil_static_front, stencil_dynamic_front) ||
face_writes(stencil_static_back, stencil_dynamic_back)))) {
(stencil_test_enable && (face_writes(stencil_front) || face_writes(stencil_back)))) {
writes |= vk::ImageAspectFlagBits::eStencil;
}
return writes;
@@ -34,10 +34,8 @@ struct RenderDepthInfo {
bool stencil_clear_enable = false;
uint8_t stencil_clear_value = 0;
bool stencil_test_enable = false;
PipelineStencilStaticState stencil_static_front;
PipelineStencilStaticState stencil_static_back;
PipelineStencilDynamicState stencil_dynamic_front;
PipelineStencilDynamicState stencil_dynamic_back;
vk::StencilOpState stencil_front;
vk::StencilOpState stencil_back;
ImageId image_id;
[[nodiscard]] vk::ImageAspectFlags AttachmentWriteAspects() const;
@@ -742,9 +742,6 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
static_params.depth_bounds_test_enable = depth.depth_bounds_test_enable;
static_params.depth_min_bounds = depth.depth_min_bounds;
static_params.depth_max_bounds = depth.depth_max_bounds;
static_params.stencil_test_enable = depth.stencil_test_enable;
static_params.stencil_front = depth.stencil_static_front;
static_params.stencil_back = depth.stencil_static_back;
const bool rect_list =
command.GetUserConfig().GetPrimType() == Prospero::PrimitiveType::kRectList;
static_params.cull_back = !rect_list && mc.cull_back;
@@ -42,9 +42,6 @@ struct PipelineStaticParameters {
bool depth_bounds_test_enable = false;
float depth_min_bounds = 0.0f;
float depth_max_bounds = 0.0f;
bool stencil_test_enable = false;
PipelineStencilStaticState stencil_front;
PipelineStencilStaticState stencil_back;
uint32_t color_mask[RENDER_COLOR_ATTACHMENTS_MAX] = {};
bool cull_front = false;
bool cull_back = false;
@@ -68,7 +65,7 @@ struct PipelineStaticParameters {
static_assert(std::is_trivially_copyable_v<PipelineStaticParameters>);
static_assert(std::is_standard_layout_v<PipelineStaticParameters>);
static_assert(alignof(PipelineStaticParameters) == 1);
static_assert(sizeof(PipelineStaticParameters) == 158);
static_assert(sizeof(PipelineStaticParameters) == 125);
struct PipelineRenderingState {
std::array<vk::Format, RENDER_COLOR_ATTACHMENTS_MAX> color_formats {};
@@ -477,15 +477,6 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
#else
(static_params.depth_bounds_test_enable ? VK_TRUE : VK_FALSE);
#endif
depth_stencil_info.stencilTestEnable = (static_params.stencil_test_enable ? VK_TRUE : VK_FALSE);
depth_stencil_info.front.failOp = static_params.stencil_front.failOp;
depth_stencil_info.front.passOp = static_params.stencil_front.passOp;
depth_stencil_info.front.depthFailOp = static_params.stencil_front.depthFailOp;
depth_stencil_info.front.compareOp = static_params.stencil_front.compareOp;
depth_stencil_info.back.failOp = static_params.stencil_back.failOp;
depth_stencil_info.back.passOp = static_params.stencil_back.passOp;
depth_stencil_info.back.depthFailOp = static_params.stencil_back.depthFailOp;
depth_stencil_info.back.compareOp = static_params.stencil_back.compareOp;
depth_stencil_info.minDepthBounds = static_params.depth_min_bounds;
depth_stencil_info.maxDepthBounds = static_params.depth_max_bounds;
@@ -498,6 +489,8 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
vk::DynamicState::eDepthCompareOp,
vk::DynamicState::eDepthBiasEnable,
vk::DynamicState::eDepthBias,
vk::DynamicState::eStencilTestEnable,
vk::DynamicState::eStencilOp,
vk::DynamicState::eStencilCompareMask,
vk::DynamicState::eStencilReference,
vk::DynamicState::eStencilWriteMask,
+9 -12
View File
@@ -403,19 +403,16 @@ static void SetGraphicsDynamicParams(const CommandBuffer& buffer, vk::CommandBuf
vk_buffer.setDepthBias(constant_factor, poly_offset.clamp, slope_factor);
}
vk_buffer.setStencilTestEnable(depth.stencil_test_enable ? VK_TRUE : VK_FALSE);
if (depth.stencil_test_enable) {
vk_buffer.setStencilCompareMask(vk::StencilFaceFlagBits::eFront,
depth.stencil_dynamic_front.compareMask);
vk_buffer.setStencilCompareMask(vk::StencilFaceFlagBits::eBack,
depth.stencil_dynamic_back.compareMask);
vk_buffer.setStencilWriteMask(vk::StencilFaceFlagBits::eFront,
depth.stencil_dynamic_front.writeMask);
vk_buffer.setStencilWriteMask(vk::StencilFaceFlagBits::eBack,
depth.stencil_dynamic_back.writeMask);
vk_buffer.setStencilReference(vk::StencilFaceFlagBits::eFront,
depth.stencil_dynamic_front.reference);
vk_buffer.setStencilReference(vk::StencilFaceFlagBits::eBack,
depth.stencil_dynamic_back.reference);
const auto set_stencil = [&](vk::StencilFaceFlagBits face, const vk::StencilOpState& state) {
vk_buffer.setStencilOp(face, state.failOp, state.passOp, state.depthFailOp, state.compareOp);
vk_buffer.setStencilCompareMask(face, state.compareMask);
vk_buffer.setStencilWriteMask(face, state.writeMask);
vk_buffer.setStencilReference(face, state.reference);
};
set_stencil(vk::StencilFaceFlagBits::eFront, depth.stencil_front);
set_stencil(vk::StencilFaceFlagBits::eBack, depth.stencil_back);
}
#if defined(__APPLE__)
@@ -5,7 +5,6 @@
#include <array>
#include <cstdint>
#include <type_traits>
namespace Libs::Graphics {
@@ -67,33 +66,6 @@ inline constexpr TargetViewInfo ResolveTargetViewInfo(uint32_t base_layer, uint3
base_layer, last_layer - base_layer + 1u, last_layer + 1u};
}
#pragma pack(push, 1)
struct PipelineStencilStaticState {
vk::StencilOp failOp = vk::StencilOp::eKeep;
vk::StencilOp passOp = vk::StencilOp::eKeep;
vk::StencilOp depthFailOp = vk::StencilOp::eKeep;
vk::CompareOp compareOp = vk::CompareOp::eNever;
};
struct PipelineStencilDynamicState {
uint32_t compareMask = 0;
uint32_t writeMask = 0;
uint32_t reference = 0;
};
#pragma pack(pop)
static_assert(std::is_trivially_copyable_v<PipelineStencilStaticState>);
static_assert(std::is_standard_layout_v<PipelineStencilStaticState>);
static_assert(alignof(PipelineStencilStaticState) == 1);
static_assert(sizeof(PipelineStencilStaticState) ==
sizeof(vk::StencilOp) * 3 + sizeof(vk::CompareOp));
static_assert(std::is_trivially_copyable_v<PipelineStencilDynamicState>);
static_assert(std::is_standard_layout_v<PipelineStencilDynamicState>);
static_assert(alignof(PipelineStencilDynamicState) == 1);
static_assert(sizeof(PipelineStencilDynamicState) == sizeof(uint32_t) * 3);
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERTARGET_H_
+42 -20
View File
@@ -10275,9 +10275,9 @@ public:
!read_only_depth.depth_write_enable &&
!read_only_depth.stencil_clear_enable &&
read_only_depth.stencil_test_enable &&
read_only_depth.stencil_dynamic_front.writeMask == 0 &&
read_only_depth.stencil_dynamic_back.writeMask == 0 &&
read_only_depth.stencil_static_front.passOp ==
read_only_depth.stencil_front.writeMask == 0 &&
read_only_depth.stencil_back.writeMask == 0 &&
read_only_depth.stencil_front.passOp ==
vk::StencilOp::eKeep &&
!read_only_depth.AttachmentWriteAspects() &&
depth_attachment_layout(read_only_depth) ==
@@ -12501,15 +12501,18 @@ public:
cmd.setDepthWriteEnable(depth.depth_write_enable);
cmd.setDepthCompareOp(depth.depth_compare_op);
cmd.setDepthBiasEnable(false);
cmd.setStencilTestEnable(depth.stencil_test_enable);
if (depth.stencil_test_enable) {
const auto set_stencil = [&](vk::StencilFaceFlagBits face,
const PipelineStencilDynamicState &state) {
const vk::StencilOpState &state) {
cmd.setStencilOp(face, state.failOp, state.passOp, state.depthFailOp,
state.compareOp);
cmd.setStencilCompareMask(face, state.compareMask);
cmd.setStencilWriteMask(face, state.writeMask);
cmd.setStencilReference(face, state.reference);
};
set_stencil(vk::StencilFaceFlagBits::eFront, depth.stencil_dynamic_front);
set_stencil(vk::StencilFaceFlagBits::eBack, depth.stencil_dynamic_back);
set_stencil(vk::StencilFaceFlagBits::eFront, depth.stencil_front);
set_stencil(vk::StencilFaceFlagBits::eBack, depth.stencil_back);
}
const vk::Bool32 write = true;
cmd.setColorWriteEnableEXT(1, &write);
@@ -12624,6 +12627,9 @@ public:
static_cast<uint16_t>(extent - 1), true};
registers.SetDepthRenderTarget(stencil_target);
HW::DepthControl stencil_control{};
// Keep the same depth/stencil attachment active when stencil testing is disabled.
stencil_control.z_enable = true;
stencil_control.zfunc = static_cast<uint8_t>(vk::CompareOp::eAlways);
stencil_control.stencil_enable = stencil_control.backface_enable = true;
stencil_control.stencilfunc = stencil_control.stencilfunc_bf =
static_cast<uint8_t>(vk::CompareOp::eAlways);
@@ -12651,6 +12657,9 @@ public:
scheduler.Finish();
return ReadBuffer(name, stencil_readback, extent * extent / 4);
};
const std::array stencil_pipelines{
pipeline(true, 2, 2, false, true).pipeline,
pipeline(true, 2, 2, false, false).pipeline};
const auto check_stencil = [&](const char *label, bool back_face,
uint8_t initial, uint8_t written) {
vk::ClearValue clear{};
@@ -12658,7 +12667,11 @@ public:
TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), depth.image_id,
{vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil,
0, 1, 0, 1}, clear);
draw(pipeline(true, 2, 2, false, !back_face));
const auto &selected = pipeline(true, 2, 2, false, !back_face);
Require(name, "dynamic stencil pipeline reuse",
selected.pipeline == stencil_pipelines[back_face],
"stencil enable, operations or references changed the graphics pipeline");
draw(selected);
const auto result = read_stencil();
const auto *stencil_bytes = reinterpret_cast<const uint8_t *>(result.data());
for (uint32_t texel = 0; texel < extent * extent; ++texel) {
@@ -12671,6 +12684,17 @@ public:
check_stencil("front-face ReplaceTest", false, 0x35, 0xb5);
check_stencil("back-face masked ReplaceOp", true, 0xb5, 0x35);
stencil_control.stencil_enable = false;
registers.SetDepthControl(stencil_control);
RenderExecutorTestAccess::ResolveRenderDepthTarget(executor, scheduler.Current(), depth);
check_stencil("disabled front stencil", false, 0x35, 0x35);
check_stencil("disabled back stencil", true, 0xb5, 0xb5);
stencil_control.stencil_enable = true;
registers.SetDepthControl(stencil_control);
RenderExecutorTestAccess::ResolveRenderDepthTarget(executor, scheduler.Current(), depth);
check_stencil("re-enabled front stencil", false, 0x35, 0xb5);
check_stencil("re-enabled back stencil", true, 0xb5, 0x35);
// Frame 6115 uses separate test/op replacement values with Always comparisons.
stencil_control.z_enable = stencil_control.z_write_enable = true;
stencil_control.zfunc = static_cast<uint8_t>(vk::CompareOp::eLessOrEqual);
@@ -12679,8 +12703,8 @@ public:
registers.SetStencilMask({8, 0xff, 0xff, 1, 8, 0xff, 0xff, 1});
RenderExecutorTestAccess::ResolveRenderDepthTarget(executor, scheduler.Current(), depth);
Require(name, "independent stencil references",
depth.stencil_dynamic_front.reference == 8 &&
depth.stencil_dynamic_back.reference == 1,
depth.stencil_front.reference == 8 &&
depth.stencil_back.reference == 1,
"always-pass faces must preserve their distinct replacement values");
check_stencil("front test-value replacement", false, 0x35, 8);
check_stencil("back operation-value replacement", true, 0x35, 1);
@@ -12700,8 +12724,8 @@ public:
registers.SetStencilMask({8, 8, 1, 1, 8, 8, 1, 1});
RenderExecutorTestAccess::ResolveRenderDepthTarget(executor, scheduler.Current(), depth);
Require(name, "masked compare and replace reference",
depth.stencil_dynamic_front.reference == 9 &&
depth.stencil_dynamic_back.reference == 9,
depth.stencil_front.reference == 9 &&
depth.stencil_back.reference == 9,
"disjoint comparison and replacement bits must combine into one reference");
check_stencil("masked compare passes", false, 0xb8, 0xb9);
check_stencil("masked compare fails", false, 0xb0, 0xb0);
@@ -28499,27 +28523,25 @@ void CheckDepthAttachmentWrites() {
target.depth_load_clear_enable = false;
target.stencil_test_enable = true;
target.stencil_dynamic_front = {0xff, 0xff, 0};
target.stencil_dynamic_back = target.stencil_dynamic_front;
target.stencil_static_front = {vk::StencilOp::eKeep, vk::StencilOp::eKeep,
vk::StencilOp::eKeep, vk::CompareOp::eAlways};
target.stencil_static_back = target.stencil_static_front;
target.stencil_front = {vk::StencilOp::eKeep, vk::StencilOp::eKeep,
vk::StencilOp::eKeep, vk::CompareOp::eAlways, 0xff, 0xff, 0};
target.stencil_back = target.stencil_front;
Require("DepthAttachmentWrites", "stencil keep",
!target.AttachmentWriteAspects(),
"KEEP-only stencil state claimed a stencil write");
target.stencil_static_front.failOp = vk::StencilOp::eZero;
target.stencil_front.failOp = vk::StencilOp::eZero;
Require("DepthAttachmentWrites", "unreachable stencil fail",
!target.AttachmentWriteAspects(),
"ALWAYS comparison claimed an unreachable fail operation");
target.stencil_static_front.failOp = vk::StencilOp::eKeep;
target.stencil_static_front.passOp = vk::StencilOp::eReplace;
target.stencil_front.failOp = vk::StencilOp::eKeep;
target.stencil_front.passOp = vk::StencilOp::eReplace;
Require("DepthAttachmentWrites", "stencil pass write",
target.AttachmentWriteAspects() == vk::ImageAspectFlagBits::eStencil,
"write-capable stencil pass did not claim stencil");
target.stencil_dynamic_front.writeMask = 0;
target.stencil_front.writeMask = 0;
Require("DepthAttachmentWrites", "back-face keep",
!target.AttachmentWriteAspects(),
"masked front write or KEEP-only back face claimed stencil");