From bce8924dd690d56608df21a813bb67b073d4b22b Mon Sep 17 00:00:00 2001 From: nmzik Date: Mon, 14 Sep 2026 16:19:22 +0400 Subject: [PATCH] Use dynamic stencil state --- .../host_gpu/renderer/depthRenderTarget.cpp | 47 +++++++------- .../host_gpu/renderer/depthRenderTarget.h | 6 +- .../renderer/pipeline/pipelineCache.cpp | 3 - .../renderer/pipeline/pipelineCache.h | 5 +- .../host_gpu/renderer/pipeline/shaders.cpp | 11 +--- src/graphics/host_gpu/renderer/renderDraw.cpp | 21 +++---- src/graphics/host_gpu/renderer/renderTarget.h | 28 --------- tests/ShaderRecompilerComputeTests.cpp | 62 +++++++++++++------ 8 files changed, 78 insertions(+), 105 deletions(-) diff --git a/src/graphics/host_gpu/renderer/depthRenderTarget.cpp b/src/graphics/host_gpu/renderer/depthRenderTarget.cpp index 25f455eb4..d689b84a0 100644 --- a/src/graphics/host_gpu/renderer/depthRenderTarget.cpp +++ b/src/graphics/host_gpu/renderer/depthRenderTarget.cpp @@ -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& 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(vk::CompareOp::eAlways) || compare == static_cast(vk::CompareOp::eNever)) { required_bits = 0; } std::array 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(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(compare)}; + return {converted[0], converted[1], converted[2], static_cast(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(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; diff --git a/src/graphics/host_gpu/renderer/depthRenderTarget.h b/src/graphics/host_gpu/renderer/depthRenderTarget.h index f1a26cdb3..3f7791bc3 100644 --- a/src/graphics/host_gpu/renderer/depthRenderTarget.h +++ b/src/graphics/host_gpu/renderer/depthRenderTarget.h @@ -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; diff --git a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp index 0e9b01594..20f9e5ce4 100644 --- a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp @@ -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; diff --git a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.h b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.h index af687dbfe..8ecb80dcd 100644 --- a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.h +++ b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.h @@ -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); static_assert(std::is_standard_layout_v); static_assert(alignof(PipelineStaticParameters) == 1); -static_assert(sizeof(PipelineStaticParameters) == 158); +static_assert(sizeof(PipelineStaticParameters) == 125); struct PipelineRenderingState { std::array color_formats {}; diff --git a/src/graphics/host_gpu/renderer/pipeline/shaders.cpp b/src/graphics/host_gpu/renderer/pipeline/shaders.cpp index f07ed0c25..1d1cd32f8 100644 --- a/src/graphics/host_gpu/renderer/pipeline/shaders.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/shaders.cpp @@ -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, diff --git a/src/graphics/host_gpu/renderer/renderDraw.cpp b/src/graphics/host_gpu/renderer/renderDraw.cpp index 3d7fe7ffc..ee79ea5de 100644 --- a/src/graphics/host_gpu/renderer/renderDraw.cpp +++ b/src/graphics/host_gpu/renderer/renderDraw.cpp @@ -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__) diff --git a/src/graphics/host_gpu/renderer/renderTarget.h b/src/graphics/host_gpu/renderer/renderTarget.h index ff18dd240..4fcd98cb6 100644 --- a/src/graphics/host_gpu/renderer/renderTarget.h +++ b/src/graphics/host_gpu/renderer/renderTarget.h @@ -5,7 +5,6 @@ #include #include -#include 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); -static_assert(std::is_standard_layout_v); -static_assert(alignof(PipelineStencilStaticState) == 1); -static_assert(sizeof(PipelineStencilStaticState) == - sizeof(vk::StencilOp) * 3 + sizeof(vk::CompareOp)); -static_assert(std::is_trivially_copyable_v); -static_assert(std::is_standard_layout_v); -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_ diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 4cf935497..16b8be5d7 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -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(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(vk::CompareOp::eAlways); stencil_control.stencil_enable = stencil_control.backface_enable = true; stencil_control.stencilfunc = stencil_control.stencilfunc_bf = static_cast(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(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(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");