From 1ec69f8e7d88c7666b789f9d03025895d8020e0b Mon Sep 17 00:00:00 2001 From: Senaxx Date: Tue, 27 Oct 2026 05:56:52 +0100 Subject: [PATCH] gpu: make cull mode and front face dynamic state --- .../host_gpu/renderer/dynamicState.cpp | 8 ++++++ src/graphics/host_gpu/renderer/dynamicState.h | 20 ++++++++++++++ .../renderer/pipeline/pipelineCache.cpp | 8 ++---- .../renderer/pipeline/pipelineCache.h | 5 +--- .../host_gpu/renderer/pipeline/shaders.cpp | 15 ++--------- src/graphics/host_gpu/renderer/renderDraw.cpp | 14 ++++++++++ tests/ShaderRecompilerComputeTests.cpp | 26 ++++++++++++++++++- 7 files changed, 72 insertions(+), 24 deletions(-) diff --git a/src/graphics/host_gpu/renderer/dynamicState.cpp b/src/graphics/host_gpu/renderer/dynamicState.cpp index da9f69667..478324e0f 100644 --- a/src/graphics/host_gpu/renderer/dynamicState.cpp +++ b/src/graphics/host_gpu/renderer/dynamicState.cpp @@ -125,6 +125,14 @@ void DynamicState::Commit(const GraphicContext& graphics, vk::CommandBuffer cmdb } } } + if (dirty_state.cull_mode) { + dirty_state.cull_mode = false; + cmdbuf.setCullMode(cull_mode); + } + if (dirty_state.front_face) { + dirty_state.front_face = false; + cmdbuf.setFrontFace(front_face); + } if (dirty_state.blend_constants) { dirty_state.blend_constants = false; cmdbuf.setBlendConstants(blend_constants.data()); diff --git a/src/graphics/host_gpu/renderer/dynamicState.h b/src/graphics/host_gpu/renderer/dynamicState.h index c243b20ef..0f9971616 100644 --- a/src/graphics/host_gpu/renderer/dynamicState.h +++ b/src/graphics/host_gpu/renderer/dynamicState.h @@ -52,6 +52,9 @@ struct DynamicState { bool stencil_back_write_mask : 1; bool stencil_back_compare_mask : 1; + bool cull_mode : 1; + bool front_face : 1; + bool blend_constants : 1; bool color_write_enables : 1; bool line_width : 1; @@ -86,6 +89,9 @@ struct DynamicState { uint32_t stencil_back_write_mask {}; uint32_t stencil_back_compare_mask {}; + vk::CullModeFlags cull_mode {}; + vk::FrontFace front_face {}; + std::array blend_constants {}; std::array color_write_enables {}; uint32_t num_color_write_enables {}; @@ -221,6 +227,20 @@ struct DynamicState { } } + void SetCullMode(const vk::CullModeFlags cull_mode_) { + if (cull_mode != cull_mode_) { + cull_mode = cull_mode_; + dirty_state.cull_mode = true; + } + } + + void SetFrontFace(const vk::FrontFace front_face_) { + if (front_face != front_face_) { + front_face = front_face_; + dirty_state.front_face = true; + } + } + void SetBlendConstants(const std::array blend_constants_) { if (blend_constants != blend_constants_) { blend_constants = blend_constants_; diff --git a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp index e67f254c3..419d9580c 100644 --- a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp @@ -1003,15 +1003,11 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(const CommandBuffer& } static_params.alpha_to_coverage_enable = ps_active && ctx.GetShaderRegisters().AlphaToMask(); const bool rect_list = Prospero::IsRectList(command.GetUserConfig().GetPrimType()); - static_params.cull_back = !rect_list && mc.cull_back; - static_params.cull_front = !rect_list && mc.cull_front; - static_params.face = mc.face; static_params.provoking_vtx_last = mc.provoking_vtx_last; static_params.polygon_mode = - ResolvePolygonMode(mc, static_params.cull_front, static_params.cull_back); + ResolvePolygonMode(mc, !rect_list && mc.cull_front, !rect_list && mc.cull_back); if (tessellation) { - static_params.patch_control_points = - static_cast(vs_input_info.tess.input_control_points); + static_params.patch_control_points = vs_input_info.tess.input_control_points; } else if (!mesh && static_params.topology == vk::PrimitiveTopology::ePatchList) { // Rectangle lists expand through generated tessellation stages. static_params.patch_control_points = 3; diff --git a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.h b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.h index b9ab8981a..46fce0f11 100644 --- a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.h +++ b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.h @@ -54,15 +54,12 @@ struct PipelineStaticParameters { bool depth_clip_enable = true; vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList; // 0 without tessellation, 3 for a rectangle list, else the input control points. - uint8_t patch_control_points = 0; + uint32_t patch_control_points = 0; bool primitive_restart_enable = false; uint32_t samples = 1; bool sample_shading_enable = false; bool alpha_to_coverage_enable = false; uint32_t color_mask[RENDER_COLOR_ATTACHMENTS_MAX] = {}; - bool cull_front = false; - bool cull_back = false; - bool face = false; bool provoking_vtx_last = false; vk::PolygonMode polygon_mode = vk::PolygonMode::eFill; uint8_t color_srcblend[RENDER_COLOR_ATTACHMENTS_MAX] = {}; diff --git a/src/graphics/host_gpu/renderer/pipeline/shaders.cpp b/src/graphics/host_gpu/renderer/pipeline/shaders.cpp index 61fe8d959..715718707 100644 --- a/src/graphics/host_gpu/renderer/pipeline/shaders.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/shaders.cpp @@ -219,17 +219,6 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p vk::PipelineViewportStateCreateInfo viewport_state {}; viewport_state.pNext = &depth_clip_control; - vk::CullModeFlags cull_mode = vk::CullModeFlagBits::eNone; - if (static_params.cull_back) { - cull_mode |= vk::CullModeFlagBits::eBack; - } - if (static_params.cull_front) { - cull_mode |= vk::CullModeFlagBits::eFront; - } - - vk::FrontFace front_face = - (static_params.face ? vk::FrontFace::eClockwise : vk::FrontFace::eCounterClockwise); - vk::PipelineRasterizationDepthClipStateCreateInfoEXT clip_ext {}; clip_ext.depthClipEnable = static_params.depth_clip_enable ? VK_TRUE : VK_FALSE; @@ -250,8 +239,6 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p provoking_vertex.pNext = rasterizer.pNext; rasterizer.pNext = &provoking_vertex; } - rasterizer.cullMode = cull_mode; - rasterizer.frontFace = front_face; rasterizer.polygonMode = static_params.polygon_mode; rasterizer.lineWidth = 1.0f; @@ -347,6 +334,8 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p vk::DynamicState::eViewportWithCount, vk::DynamicState::eScissorWithCount, vk::DynamicState::eLineWidth, + vk::DynamicState::eCullMode, + vk::DynamicState::eFrontFace, vk::DynamicState::eDepthTestEnable, vk::DynamicState::eDepthWriteEnable, vk::DynamicState::eDepthCompareOp, diff --git a/src/graphics/host_gpu/renderer/renderDraw.cpp b/src/graphics/host_gpu/renderer/renderDraw.cpp index 454a91ff7..dab458944 100644 --- a/src/graphics/host_gpu/renderer/renderDraw.cpp +++ b/src/graphics/host_gpu/renderer/renderDraw.cpp @@ -403,6 +403,20 @@ static void UpdateGraphicsDynamicState(const CommandBuffer& buffer, DynamicState dynamic_state.SetDepthBias(constant_factor, poly_offset.clamp, slope_factor); } + // Rect lists are drawn without culling. + vk::CullModeFlags cull_mode {}; + if (!Prospero::IsRectList(buffer.GetUserConfig().GetPrimType())) { + if (mode.cull_front) { + cull_mode |= vk::CullModeFlagBits::eFront; + } + if (mode.cull_back) { + cull_mode |= vk::CullModeFlagBits::eBack; + } + } + dynamic_state.SetCullMode(cull_mode); + dynamic_state.SetFrontFace(mode.face ? vk::FrontFace::eClockwise + : vk::FrontFace::eCounterClockwise); + dynamic_state.SetStencilTestEnabled(depth.stencil_test_enable); if (depth.stencil_test_enable) { const auto& front = depth.stencil_front; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 65d38f2ab..a1ae255c8 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -18826,6 +18826,10 @@ public: cmd.setViewportWithCount(1, &viewport); cmd.setScissorWithCount(1, &scissor); cmd.setLineWidth(1); + cmd.setCullMode(vk::CullModeFlagBits::eNone); + cmd.setFrontFace(registers.GetModeControl().face + ? vk::FrontFace::eClockwise + : vk::FrontFace::eCounterClockwise); cmd.setDepthTestEnable(depth.depth_test_enable); cmd.setDepthWriteEnable(depth.depth_write_enable); cmd.setDepthCompareOp(depth.depth_compare_op); @@ -19023,6 +19027,9 @@ public: const std::array stencil_pipelines{ pipeline(true, 2, 2, false, true).pipeline, pipeline(true, 2, 2, false, false).pipeline}; + Require(name, "dynamic winding pipeline reuse", + stencil_pipelines[0] == stencil_pipelines[1], + "the winding changed the graphics pipeline"); const auto check_stencil = [&](const char *label, bool back_face, uint8_t initial, uint8_t written) { vk::ClearValue clear{}; @@ -19655,6 +19662,22 @@ public: "native vertex or instance offset was applied twice"); } } + // With back faces culled, the triangle draws for exactly one winding. + const auto draws_with_back_cull = [&](bool clockwise) { + registers.SetModeControl({.cull_back = true, .face = clockwise}); + TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), sparse_color.image_id, + {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}, {}); + RenderExecutorTestAccess::DrawAuto(executor, scheduler.Current(), + {.vertex_count = 3, .instance_count = 1, .first_vertex = 7, .first_instance = 5, + .offset_source = DrawOffsetSource::IndirectArgs}); + const auto pixels = ReadCachedTexel(name, context, sparse_color.image_id, + {}, {extent, extent, 1}); + return std::ranges::any_of(pixels, [](u32 word) { return word != 0; }); + }; + Require("DynamicCullMode", "back faces culled by winding", + draws_with_back_cull(false) != draws_with_back_cull(true), + "the draw's cull mode or front face did not reach the host GPU"); + registers.SetModeControl({}); // Indirect records come from the GPU copy; the CPU backing holds zeros. In each pair the // first record draws nothing and the second covers the target, through vertex offset 7 @@ -19800,9 +19823,10 @@ public: registers.SetPsInputEna(2); registers.SetPsInputAddr(2); registers.SetPsInputSettings(0, 0); + // Rect lists are drawn without culling. const auto check_rect = [&](Prospero::PrimitiveType primitive, const char *rect_name) { user_config.SetPrimitiveType(primitive); - registers.SetModeControl({}); + registers.SetModeControl({.cull_front = true, .cull_back = true}); TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), sparse_color.image_id, {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}, {}); RenderExecutorTestAccess::DrawAuto(