diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index e989bf6b31..77c211e5d7 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, @@ -849,6 +849,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { VK_DYNAMIC_STATE_STENCIL_OP_EXT, }; dynamic_states.insert(dynamic_states.end(), extended.begin(), extended.end()); + dynamic_states.push_back(VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT); // VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT is part of EDS1 // Only use it if VIDS is not active (VIDS replaces it with full vertex input control) @@ -863,7 +864,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { dynamic_states.push_back(VK_DYNAMIC_STATE_VERTEX_INPUT_EXT); } - // EDS2 - Core (3 states) + // EDS2 - Core states if (key.state.extended_dynamic_state_2) { static constexpr std::array extended2{ VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE_EXT, @@ -871,6 +872,9 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE_EXT, }; dynamic_states.insert(dynamic_states.end(), extended2.begin(), extended2.end()); + if (device.IsExtExtendedDynamicState2PatchControlPointsSupported()) { + dynamic_states.push_back(VK_DYNAMIC_STATE_PATCH_CONTROL_POINTS_EXT); + } } // EDS2 - LogicOp (granular) @@ -913,6 +917,10 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { } } + if (device.IsExtLineRasterizationSupported() && device.SupportsStippledRectangularLines()) { + dynamic_states.push_back(VK_DYNAMIC_STATE_LINE_STIPPLE_EXT); + } + const VkPipelineDynamicStateCreateInfo dynamic_state_ci{ .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, .pNext = nullptr, 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_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 77a4e8616a..b9ba7ed328 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -479,7 +479,8 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, device.IsExtExtendedDynamicState2Supported(); dynamic_features.has_extended_dynamic_state_2_logic_op = device.IsExtExtendedDynamicState2ExtrasSupported(); - dynamic_features.has_extended_dynamic_state_2_patch_control_points = false; + dynamic_features.has_extended_dynamic_state_2_patch_control_points = + device.IsExtExtendedDynamicState2PatchControlPointsSupported(); dynamic_features.has_extended_dynamic_state_3_blend = device.IsExtExtendedDynamicState3BlendingSupported(); diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 60b899a811..fb2a01c26d 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) { @@ -1017,9 +1044,12 @@ void RasterizerVulkan::UpdateDynamicStates() { UpdateDepthBounds(regs); UpdateStencilFaces(regs); UpdateLineWidth(regs); + UpdateLineStipple(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); @@ -1032,11 +1062,12 @@ void RasterizerVulkan::UpdateDynamicStates() { } } - // EDS2: PrimitiveRestart, RasterizerDiscard, DepthBias enable/disable + // EDS2: PrimitiveRestart, RasterizerDiscard, DepthBias enable/disable, PatchControlPoints if (device.IsExtExtendedDynamicState2Supported()) { UpdatePrimitiveRestartEnable(regs); UpdateRasterizerDiscardEnable(regs); UpdateDepthBiasEnable(regs); + UpdatePatchControlPoints(regs); } // EDS2 Extras: LogicOp operation selection @@ -1384,6 +1415,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,11 +1473,49 @@ 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) { + 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); }); } +void RasterizerVulkan::UpdatePatchControlPoints(Tegra::Engines::Maxwell3D::Regs& regs) { + if (!device.IsExtExtendedDynamicState2PatchControlPointsSupported()) { + return; + } + GraphicsPipeline* const pipeline = pipeline_cache.CurrentGraphicsPipeline(); + if (pipeline == nullptr || !pipeline->HasTessellationStages()) { + return; + } + const u32 patch_control_points = (std::max)(regs.patch_vertices, 1u); + if (!state_tracker.ChangePatchControlPoints(patch_control_points)) { + return; + } + scheduler.Record([patch_control_points](vk::CommandBuffer cmdbuf) { + cmdbuf.SetPatchControlPointsEXT(patch_control_points); + }); +} + void RasterizerVulkan::UpdateRasterizerDiscardEnable(Tegra::Engines::Maxwell3D::Regs& regs) { if (!state_tracker.TouchRasterizerDiscardEnable()) { return; @@ -1464,6 +1555,20 @@ void RasterizerVulkan::UpdateLineStippleEnable(Tegra::Engines::Maxwell3D::Regs& }); } +void RasterizerVulkan::UpdateLineStipple(Tegra::Engines::Maxwell3D::Regs& regs) { + if (!state_tracker.TouchLineStipple()) { + return; + } + if (!device.IsExtLineRasterizationSupported() || !device.SupportsStippledRectangularLines()) { + return; + } + scheduler.Record([factor = regs.line_stipple_params.factor, + pattern = static_cast(regs.line_stipple_params.pattern)]( + vk::CommandBuffer cmdbuf) { + cmdbuf.SetLineStippleEXT(factor, pattern); + }); +} + void RasterizerVulkan::UpdateLineRasterizationMode(Tegra::Engines::Maxwell3D::Regs& regs) { if (!device.IsExtLineRasterizationSupported()) { return; @@ -1771,25 +1876,29 @@ void RasterizerVulkan::UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs) // generating dirty state. Track the highest dirty attribute and update all attributes until // that one. size_t highest_dirty_attr{}; + bool has_dirty_attr = false; for (size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) { if (dirty[Dirty::VertexAttribute0 + index]) { + has_dirty_attr = true; highest_dirty_attr = index; } } - for (size_t index = 0; index < highest_dirty_attr; ++index) { - const Maxwell::VertexAttribute attribute{regs.vertex_attrib_format[index]}; - const u32 binding{attribute.buffer}; - dirty[Dirty::VertexAttribute0 + index] = false; - dirty[Dirty::VertexBinding0 + static_cast(binding)] = true; - if (!attribute.constant) { - attributes.push_back({ - .sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT, - .pNext = nullptr, - .location = static_cast(index), - .binding = binding, - .format = MaxwellToVK::VertexFormat(device, attribute.type, attribute.size), - .offset = attribute.offset, - }); + if (has_dirty_attr) { + for (size_t index = 0; index <= highest_dirty_attr; ++index) { + const Maxwell::VertexAttribute attribute{regs.vertex_attrib_format[index]}; + const u32 binding{attribute.buffer}; + dirty[Dirty::VertexAttribute0 + index] = false; + dirty[Dirty::VertexBinding0 + static_cast(binding)] = true; + if (!attribute.constant) { + attributes.push_back({ + .sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT, + .pNext = nullptr, + .location = static_cast(index), + .binding = binding, + .format = MaxwellToVK::VertexFormat(device, attribute.type, attribute.size), + .offset = attribute.offset, + }); + } } } for (size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) { diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index b689c6b660..2e14ff06be 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -170,11 +170,13 @@ 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); void UpdateDepthCompareOp(Tegra::Engines::Maxwell3D::Regs& regs); void UpdatePrimitiveRestartEnable(Tegra::Engines::Maxwell3D::Regs& regs); + void UpdatePatchControlPoints(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateRasterizerDiscardEnable(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateConservativeRasterizationMode(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateLineStippleEnable(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..3bceaa603f 100644 --- a/src/video_core/renderer_vulkan/vk_state_tracker.cpp +++ b/src/video_core/renderer_vulkan/vk_state_tracker.cpp @@ -216,12 +216,15 @@ void SetupDirtyVertexAttributes(Tables& tables) { } void SetupDirtyVertexBindings(Tables& tables) { - // Do NOT include stride here, it's implicit in VertexBuffer + // Dynamic vertex input needs binding state updates when stride/divisor/instancing changes + static constexpr size_t stride_offset = 0; static constexpr size_t divisor_offset = 3; for (size_t i = 0; i < Regs::NumVertexArrays; ++i) { const u8 flag = static_cast(VertexBinding0 + i); tables[0][OFF(vertex_stream_instances) + i] = VertexInput; tables[1][OFF(vertex_stream_instances) + i] = flag; + tables[0][OFF(vertex_streams) + i * NUM(vertex_streams[0]) + stride_offset] = VertexInput; + tables[1][OFF(vertex_streams) + i * NUM(vertex_streams[0]) + stride_offset] = flag; tables[0][OFF(vertex_streams) + i * NUM(vertex_streams[0]) + divisor_offset] = VertexInput; tables[1][OFF(vertex_streams) + i * NUM(vertex_streams[0]) + divisor_offset] = flag; } @@ -265,7 +268,8 @@ void StateTracker::ChangeChannel(Tegra::Control::ChannelState& channel_state) { void StateTracker::InvalidateState() { flags->set(); - current_topology = INVALID_TOPOLOGY; + current_primitive_topology = INVALID_PRIMITIVE_TOPOLOGY; + current_patch_control_points = INVALID_PATCH_CONTROL_POINTS; 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..aba865caf1 100644 --- a/src/video_core/renderer_vulkan/vk_state_tracker.h +++ b/src/video_core/renderer_vulkan/vk_state_tracker.h @@ -85,7 +85,8 @@ public: void InvalidateCommandBufferState() { (*flags) |= invalidation_flags; - current_topology = INVALID_TOPOLOGY; + current_primitive_topology = INVALID_PRIMITIVE_TOPOLOGY; + current_patch_control_points = INVALID_PATCH_CONTROL_POINTS; stencil_reset = true; } @@ -280,9 +281,15 @@ 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; + } + + bool ChangePatchControlPoints(u32 new_patch_control_points) { + const bool has_changed = current_patch_control_points != new_patch_control_points; + current_patch_control_points = new_patch_control_points; return has_changed; } @@ -293,7 +300,8 @@ public: void InvalidateState(); private: - static constexpr auto INVALID_TOPOLOGY = static_cast(~0u); + static constexpr u32 INVALID_PRIMITIVE_TOPOLOGY = ~0u; + static constexpr u32 INVALID_PATCH_CONTROL_POINTS = ~0u; bool Exchange(std::size_t id, bool new_value) const noexcept { const bool is_dirty = (*flags)[id]; @@ -310,7 +318,8 @@ 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; + u32 current_patch_control_points = INVALID_PATCH_CONTROL_POINTS; bool two_sided_stencil = false; StencilProperties front{}; StencilProperties back{}; diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index d29a8cd3f3..6990e21e9c 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -625,6 +625,10 @@ public: return features.extended_dynamic_state2.extendedDynamicState2LogicOp; } + bool IsExtExtendedDynamicState2PatchControlPointsSupported() const { + return features.extended_dynamic_state2.extendedDynamicState2PatchControlPoints; + } + /// Returns true if the device supports VK_EXT_extended_dynamic_state3. bool IsExtExtendedDynamicState3Supported() const { return extensions.extended_dynamic_state3;