From bfb85c1385ba1327b14b916c07e9999cd84e9f77 Mon Sep 17 00:00:00 2001 From: wildcard Date: Wed, 11 Mar 2026 15:56:43 +0100 Subject: [PATCH] [vulkan] [eds] implement VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT --- .../renderer_vulkan/vk_graphics_pipeline.cpp | 3 +- .../renderer_vulkan/vk_graphics_pipeline.h | 4 + .../renderer_vulkan/vk_rasterizer.cpp | 77 ++++++++++++++++++- .../renderer_vulkan/vk_rasterizer.h | 1 + .../renderer_vulkan/vk_state_tracker.cpp | 2 +- .../renderer_vulkan/vk_state_tracker.h | 14 ++-- 6 files changed, 89 insertions(+), 12 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index e989bf6b31..1413f340ad 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -830,7 +830,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { .pAttachments = cb_attachments.data(), .blendConstants = {} }; - static_vector dynamic_states{ + static_vector dynamic_states{ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_DEPTH_BIAS, VK_DYNAMIC_STATE_BLEND_CONSTANTS, VK_DYNAMIC_STATE_DEPTH_BOUNDS, VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK, @@ -847,6 +847,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { VK_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE_EXT, VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE_EXT, VK_DYNAMIC_STATE_STENCIL_OP_EXT, + VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT, }; dynamic_states.insert(dynamic_states.end(), extended.begin(), extended.end()); diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h index 34941d6e8d..6c49072f75 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h @@ -90,6 +90,10 @@ public: return fragment_has_color0_output; } + bool HasTessellationStages() const noexcept { + return spv_modules[1] || spv_modules[2]; + } + bool UsesExtendedDynamicState() const noexcept { return key.state.extended_dynamic_state != 0; } diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 60b899a811..48d3235eb1 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -61,6 +61,33 @@ struct DrawParams { bool is_indexed; }; +bool SupportsPrimitiveRestart(VkPrimitiveTopology topology) { + static constexpr std::array unsupported_topologies{ + VK_PRIMITIVE_TOPOLOGY_POINT_LIST, + VK_PRIMITIVE_TOPOLOGY_LINE_LIST, + VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY, + VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY, + VK_PRIMITIVE_TOPOLOGY_PATCH_LIST, + // VK_PRIMITIVE_TOPOLOGY_QUAD_LIST_EXT, + }; + return std::ranges::find(unsupported_topologies, topology) == unsupported_topologies.end(); +} + +VkPrimitiveTopology DynamicInputAssemblyTopology(const Device& device, + const MaxwellDrawState& draw_state, + const GraphicsPipeline& pipeline) { + auto topology = MaxwellToVK::PrimitiveTopology(device, draw_state.topology); + if (topology == VK_PRIMITIVE_TOPOLOGY_PATCH_LIST) { + if (!pipeline.HasTessellationStages()) { + topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; + } + } else if (pipeline.HasTessellationStages()) { + topology = VK_PRIMITIVE_TOPOLOGY_PATCH_LIST; + } + return topology; +} + VkViewport GetViewportState(const Device& device, const Maxwell& regs, size_t index, float scale) { const auto& src = regs.viewport_transform[index]; const auto conv = [scale](float value) { @@ -1018,8 +1045,10 @@ void RasterizerVulkan::UpdateDynamicStates() { UpdateStencilFaces(regs); UpdateLineWidth(regs); - // EDS1: CullMode, DepthCompare, FrontFace, StencilOp, DepthBoundsTest, DepthTest, DepthWrite, StencilTest + // EDS1: PrimitiveTopology, CullMode, DepthCompare, FrontFace, StencilOp, DepthBoundsTest, + // DepthTest, DepthWrite, StencilTest if (device.IsExtExtendedDynamicStateSupported()) { + UpdatePrimitiveTopology(); UpdateCullMode(regs); UpdateDepthCompareOp(regs); UpdateFrontFace(regs); @@ -1384,6 +1413,28 @@ void RasterizerVulkan::UpdateCullMode(Tegra::Engines::Maxwell3D::Regs& regs) { }); } +void RasterizerVulkan::UpdatePrimitiveTopology() { + GraphicsPipeline* const pipeline = pipeline_cache.CurrentGraphicsPipeline(); + if (pipeline == nullptr) { + return; + } + + const MaxwellDrawState& draw_state = maxwell3d->draw_manager->GetDrawState(); + const VkPrimitiveTopology topology = DynamicInputAssemblyTopology(device, draw_state, *pipeline); + + if (!state_tracker.ChangePrimitiveTopology(static_cast(topology))) { + return; + } + // Primitive restart support depends on topology, so force re-evaluation on topology changes + if (device.IsExtExtendedDynamicState2Supported()) { + maxwell3d->dirty.flags[Dirty::PrimitiveRestartEnable] = true; + } + + scheduler.Record([topology](vk::CommandBuffer cmdbuf) { + cmdbuf.SetPrimitiveTopologyEXT(topology); + }); +} + void RasterizerVulkan::UpdateDepthBoundsTestEnable(Tegra::Engines::Maxwell3D::Regs& regs) { if (!state_tracker.TouchDepthBoundsTestEnable()) { return; @@ -1420,7 +1471,29 @@ void RasterizerVulkan::UpdatePrimitiveRestartEnable(Tegra::Engines::Maxwell3D::R if (!state_tracker.TouchPrimitiveRestartEnable()) { return; } - scheduler.Record([enable = regs.primitive_restart.enabled](vk::CommandBuffer cmdbuf) { + GraphicsPipeline* const pipeline = pipeline_cache.CurrentGraphicsPipeline(); + if (pipeline == nullptr) { + // No graphics pipeline is currently available so repeat when available + maxwell3d->dirty.flags[Dirty::PrimitiveRestartEnable] = true; + return; + } + + const MaxwellDrawState& draw_state = maxwell3d->draw_manager->GetDrawState(); + const VkPrimitiveTopology topology = DynamicInputAssemblyTopology(device, draw_state, *pipeline); + + bool enable = regs.primitive_restart.enabled != 0; + if (device.IsMoltenVK()) { + // MoltenVK/Metal + enable = true; + } else if (enable) { + enable = ((topology != VK_PRIMITIVE_TOPOLOGY_PATCH_LIST && + device.IsTopologyListPrimitiveRestartSupported()) || + SupportsPrimitiveRestart(topology) || + (topology == VK_PRIMITIVE_TOPOLOGY_PATCH_LIST && + device.IsPatchListPrimitiveRestartSupported())); + } + + scheduler.Record([enable](vk::CommandBuffer cmdbuf) { cmdbuf.SetPrimitiveRestartEnableEXT(enable); }); } diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index b689c6b660..49c25633a2 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -170,6 +170,7 @@ private: void UpdateLineWidth(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateCullMode(Tegra::Engines::Maxwell3D::Regs& regs); + void UpdatePrimitiveTopology(); void UpdateDepthBoundsTestEnable(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateDepthTestEnable(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateDepthWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs); diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.cpp b/src/video_core/renderer_vulkan/vk_state_tracker.cpp index 79967d540a..519417477a 100644 --- a/src/video_core/renderer_vulkan/vk_state_tracker.cpp +++ b/src/video_core/renderer_vulkan/vk_state_tracker.cpp @@ -265,7 +265,7 @@ void StateTracker::ChangeChannel(Tegra::Control::ChannelState& channel_state) { void StateTracker::InvalidateState() { flags->set(); - current_topology = INVALID_TOPOLOGY; + current_primitive_topology = INVALID_PRIMITIVE_TOPOLOGY; stencil_reset = true; } diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.h b/src/video_core/renderer_vulkan/vk_state_tracker.h index 74bae9e181..0f995e9711 100644 --- a/src/video_core/renderer_vulkan/vk_state_tracker.h +++ b/src/video_core/renderer_vulkan/vk_state_tracker.h @@ -78,14 +78,12 @@ static_assert(Last <= (std::numeric_limits::max)()); } // namespace Dirty class StateTracker { - using Maxwell = Tegra::Engines::Maxwell3D::Regs; - public: explicit StateTracker(); void InvalidateCommandBufferState() { (*flags) |= invalidation_flags; - current_topology = INVALID_TOPOLOGY; + current_primitive_topology = INVALID_PRIMITIVE_TOPOLOGY; stencil_reset = true; } @@ -280,9 +278,9 @@ public: return Exchange(Dirty::LineRasterizationMode, false); } - bool ChangePrimitiveTopology(Maxwell::PrimitiveTopology new_topology) { - const bool has_changed = current_topology != new_topology; - current_topology = new_topology; + bool ChangePrimitiveTopology(u32 new_topology) { + const bool has_changed = current_primitive_topology != new_topology; + current_primitive_topology = new_topology; return has_changed; } @@ -293,7 +291,7 @@ public: void InvalidateState(); private: - static constexpr auto INVALID_TOPOLOGY = static_cast(~0u); + static constexpr u32 INVALID_PRIMITIVE_TOPOLOGY = ~0u; bool Exchange(std::size_t id, bool new_value) const noexcept { const bool is_dirty = (*flags)[id]; @@ -310,7 +308,7 @@ private: Tegra::Engines::Maxwell3D::DirtyState::Flags* flags; Tegra::Engines::Maxwell3D::DirtyState::Flags default_flags; Tegra::Engines::Maxwell3D::DirtyState::Flags invalidation_flags; - Maxwell::PrimitiveTopology current_topology = INVALID_TOPOLOGY; + u32 current_primitive_topology = INVALID_PRIMITIVE_TOPOLOGY; bool two_sided_stencil = false; StencilProperties front{}; StencilProperties back{};