From 7d9129941b6f763737fc4d9c8c27b25abafd197b Mon Sep 17 00:00:00 2001 From: Bruno <56844494+fab918@users.noreply.github.com> Date: Wed, 7 Oct 2026 03:50:37 +0200 Subject: [PATCH] Fix line rasterization and alpha multisample state (#374) The line check was inverted, so the line state was set on every pipeline except lines. Detect lines from the output of the last stage before rasterization, and only use line modes the device supports. Line smoothing is ignored on multisampled targets, as in OpenGL. Only enable alpha to one when the device supports it. Only enable alpha to coverage and alpha to one when the fragment shader writes color 0, and never together with smooth lines, which Vulkan does not allow. Also use a line strip output in the generated layer passthrough geometry shader for line lists. It used a triangle strip and read a vertex that does not exist. --- .../renderer_opengl/gl_shader_cache.cpp | 1 + .../renderer_vulkan/pipeline_helper.h | 36 ++++++++++++++ .../renderer_vulkan/vk_graphics_pipeline.cpp | 48 ++++++++++++------- .../renderer_vulkan/vk_graphics_pipeline.h | 3 +- .../renderer_vulkan/vk_pipeline_cache.cpp | 12 ++++- .../vulkan_common/vulkan_device.h | 12 +++++ 6 files changed, 94 insertions(+), 18 deletions(-) diff --git a/src/yuzu_video_core/renderer_opengl/gl_shader_cache.cpp b/src/yuzu_video_core/renderer_opengl/gl_shader_cache.cpp index feff51bd..724838e9 100644 --- a/src/yuzu_video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/yuzu_video_core/renderer_opengl/gl_shader_cache.cpp @@ -62,6 +62,7 @@ Shader::OutputTopology MaxwellToOutputTopology(Maxwell::PrimitiveTopology topolo switch (topology) { case Maxwell::PrimitiveTopology::Points: return Shader::OutputTopology::PointList; + case Maxwell::PrimitiveTopology::Lines: case Maxwell::PrimitiveTopology::LineStrip: return Shader::OutputTopology::LineStrip; default: diff --git a/src/yuzu_video_core/renderer_vulkan/pipeline_helper.h b/src/yuzu_video_core/renderer_vulkan/pipeline_helper.h index dc2bb303..e172d90f 100644 --- a/src/yuzu_video_core/renderer_vulkan/pipeline_helper.h +++ b/src/yuzu_video_core/renderer_vulkan/pipeline_helper.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include @@ -19,6 +20,41 @@ namespace Vulkan { using Shader::Backend::SPIRV::NUM_TEXTURE_AND_IMAGE_SCALING_WORDS; +// Returns the primitives that reach rasterization: the output of the last stage before it. +inline Shader::OutputTopology GetRasterizationOutputTopology( + Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology input, bool tessellation, + bool tessellation_isolines, std::optional geometry) { + if (geometry) { + return *geometry; + } + if (tessellation) { + return tessellation_isolines ? Shader::OutputTopology::LineStrip + : Shader::OutputTopology::TriangleStrip; + } + using Topology = Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology; + switch (input) { + case Topology::Points: + case Topology::Patches: + // Patches without tessellation shaders are drawn as points. + return Shader::OutputTopology::PointList; + case Topology::Lines: + case Topology::LineStrip: + case Topology::LinesAdjacency: + case Topology::LineStripAdjacency: + return Shader::OutputTopology::LineStrip; + default: + return Shader::OutputTopology::TriangleStrip; + } +} + +// Triangles drawn with the line polygon mode are rasterized as lines too. +inline bool RasterizesLines(Shader::OutputTopology output, + Tegra::Engines::Maxwell3D::Regs::PolygonMode polygon_mode) { + return output == Shader::OutputTopology::LineStrip || + (output == Shader::OutputTopology::TriangleStrip && + polygon_mode == Tegra::Engines::Maxwell3D::Regs::PolygonMode::Line); +} + class DescriptorLayoutBuilder { public: DescriptorLayoutBuilder(const Device& device_, VkShaderStageFlags storage_buffer_stages_ = 0) diff --git a/src/yuzu_video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/yuzu_video_core/renderer_vulkan/vk_graphics_pipeline.cpp index a7acc942..dd46380b 100644 --- a/src/yuzu_video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/yuzu_video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -130,14 +130,6 @@ bool SupportsPrimitiveRestart(VkPrimitiveTopology topology) { return std::ranges::find(unsupported_topologies, topology) == unsupported_topologies.end(); } -bool IsLine(VkPrimitiveTopology topology) { - static constexpr std::array line_topologies{ - VK_PRIMITIVE_TOPOLOGY_LINE_LIST, VK_PRIMITIVE_TOPOLOGY_LINE_STRIP, - // VK_PRIMITIVE_TOPOLOGY_LINE_LOOP_EXT, - }; - return std::ranges::find(line_topologies, topology) == line_topologies.end(); -} - VkViewportSwizzleNV UnpackViewportSwizzle(u16 swizzle) { union Swizzle { u32 raw; @@ -286,10 +278,11 @@ GraphicsPipeline::GraphicsPipeline( GuestDescriptorQueue& guest_descriptor_queue_, Common::ThreadWorker* worker_thread, PipelineStatistics* pipeline_statistics, RenderPassCache& render_pass_cache, const GraphicsPipelineCacheKey& key_, std::array stages, - const std::array& infos) + const std::array& infos, bool rasterizes_lines_) : key{key_}, device{device_}, texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, pipeline_cache(pipeline_cache_), scheduler{scheduler_}, - guest_descriptor_queue{guest_descriptor_queue_}, spv_modules{std::move(stages)} { + guest_descriptor_queue{guest_descriptor_queue_}, spv_modules{std::move(stages)}, + rasterizes_lines{rasterizes_lines_} { if (shader_notify) { shader_notify->MarkShaderBuilding(); } @@ -794,12 +787,27 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { .depthBiasSlopeFactor = 0.0f, .lineWidth = 1.0f, }; + const bool smooth_lines_supported = + device.IsExtLineRasterizationSupported() && device.SupportsSmoothLines(); + const bool rectangular_lines_supported = + device.IsExtLineRasterizationSupported() && device.SupportsRectangularLines(); + // As in OpenGL, line smoothing is ignored when rendering to a multisampled target. + const bool is_multisampled = + MaxwellToVK::MsaaMode(key.state.msaa_mode) != VK_SAMPLE_COUNT_1_BIT; + const VkLineRasterizationModeEXT line_rasterization_mode = + key.state.smooth_lines != 0 && smooth_lines_supported && !is_multisampled + ? VK_LINE_RASTERIZATION_MODE_RECTANGULAR_SMOOTH_EXT + : rectangular_lines_supported ? VK_LINE_RASTERIZATION_MODE_RECTANGULAR_EXT + : VK_LINE_RASTERIZATION_MODE_DEFAULT_EXT; + const bool use_line_state = + rasterizes_lines && device.IsExtLineRasterizationSupported(); + const bool smooth_line_rasterization = + use_line_state && + line_rasterization_mode == VK_LINE_RASTERIZATION_MODE_RECTANGULAR_SMOOTH_EXT; VkPipelineRasterizationLineStateCreateInfoEXT line_state{ .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_LINE_STATE_CREATE_INFO_EXT, .pNext = nullptr, - .lineRasterizationMode = key.state.smooth_lines != 0 - ? VK_LINE_RASTERIZATION_MODE_RECTANGULAR_SMOOTH_EXT - : VK_LINE_RASTERIZATION_MODE_RECTANGULAR_EXT, + .lineRasterizationMode = line_rasterization_mode, .stippledLineEnable = VK_FALSE, // TODO .lineStippleFactor = 0, .lineStipplePattern = 0, @@ -820,7 +828,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { ? VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT : VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT, }; - if (IsLine(input_assembly_topology) && device.IsExtLineRasterizationSupported()) { + if (use_line_state) { line_state.pNext = std::exchange(rasterization_ci.pNext, &line_state); } if (device.IsExtConservativeRasterizationSupported()) { @@ -830,6 +838,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { provoking_vertex.pNext = std::exchange(rasterization_ci.pNext, &provoking_vertex); } + const bool supports_alpha_output = stage_infos[NUM_STAGES - 1].stores_frag_color[0]; const VkPipelineMultisampleStateCreateInfo multisample_ci{ .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, .pNext = nullptr, @@ -838,8 +847,15 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { .sampleShadingEnable = VK_FALSE, .minSampleShading = 0.0f, .pSampleMask = nullptr, - .alphaToCoverageEnable = key.state.alpha_to_coverage_enabled != 0 ? VK_TRUE : VK_FALSE, - .alphaToOneEnable = key.state.alpha_to_one_enabled != 0 ? VK_TRUE : VK_FALSE, + .alphaToCoverageEnable = !smooth_line_rasterization && supports_alpha_output && + key.state.alpha_to_coverage_enabled != 0 + ? VK_TRUE + : VK_FALSE, + .alphaToOneEnable = !smooth_line_rasterization && supports_alpha_output && + device.SupportsAlphaToOne() && + key.state.alpha_to_one_enabled != 0 + ? VK_TRUE + : VK_FALSE, }; const VkPipelineDepthStencilStateCreateInfo depth_stencil_ci{ .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, diff --git a/src/yuzu_video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/yuzu_video_core/renderer_vulkan/vk_graphics_pipeline.h index 0284fcb2..f1858666 100644 --- a/src/yuzu_video_core/renderer_vulkan/vk_graphics_pipeline.h +++ b/src/yuzu_video_core/renderer_vulkan/vk_graphics_pipeline.h @@ -76,7 +76,7 @@ public: GuestDescriptorQueue& guest_descriptor_queue, Common::ThreadWorker* worker_thread, PipelineStatistics* pipeline_statistics, RenderPassCache& render_pass_cache, const GraphicsPipelineCacheKey& key, std::array stages, - const std::array& infos); + const std::array& infos, bool rasterizes_lines); GraphicsPipeline& operator=(GraphicsPipeline&&) noexcept = delete; GraphicsPipeline(GraphicsPipeline&&) noexcept = delete; @@ -157,6 +157,7 @@ private: std::mutex build_mutex; std::atomic_bool is_built{false}; bool uses_push_descriptor{false}; + const bool rasterizes_lines; }; } // namespace Vulkan diff --git a/src/yuzu_video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/yuzu_video_core/renderer_vulkan/vk_pipeline_cache.cpp index b6dfcd1b..ff4057d2 100644 --- a/src/yuzu_video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/yuzu_video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -64,6 +64,7 @@ Shader::OutputTopology MaxwellToOutputTopology(Maxwell::PrimitiveTopology topolo switch (topology) { case Maxwell::PrimitiveTopology::Points: return Shader::OutputTopology::PointList; + case Maxwell::PrimitiveTopology::Lines: case Maxwell::PrimitiveTopology::LineStrip: return Shader::OutputTopology::LineStrip; default: @@ -699,11 +700,20 @@ std::unique_ptr PipelineCache::CreateGraphicsPipeline( } previous_stage = &program; } + const bool has_tessellation{static_cast(modules[2])}; + const bool tessellation_isolines{ + static_cast(key.state.tessellation_primitive.Value()) == + Maxwell::Tessellation::DomainType::Isolines}; + const auto output_topology{GetRasterizationOutputTopology( + key.state.topology, has_tessellation, tessellation_isolines, + modules[3] ? std::optional{programs[4].output_topology} : std::nullopt)}; + const bool rasterizes_lines{RasterizesLines( + output_topology, FixedPipelineState::UnpackPolygonMode(key.state.polygon_mode))}; Common::ThreadWorker* const thread_worker{build_in_parallel ? &workers : nullptr}; return std::make_unique( scheduler, buffer_cache, texture_cache, vulkan_pipeline_cache, &shader_notify, device, descriptor_pool, guest_descriptor_queue, thread_worker, statistics, render_pass_cache, key, - std::move(modules), infos); + std::move(modules), infos, rasterizes_lines); } catch (const Shader::Exception& exception) { auto hash = key.Hash(); diff --git a/src/yuzu_video_core/vulkan_common/vulkan_device.h b/src/yuzu_video_core/vulkan_common/vulkan_device.h index f9c51f33..fe826e1a 100644 --- a/src/yuzu_video_core/vulkan_common/vulkan_device.h +++ b/src/yuzu_video_core/vulkan_common/vulkan_device.h @@ -584,6 +584,18 @@ public: return extensions.line_rasterization; } + bool SupportsRectangularLines() const { + return features.line_rasterization.rectangularLines != VK_FALSE; + } + + bool SupportsSmoothLines() const { + return features.line_rasterization.smoothLines != VK_FALSE; + } + + bool SupportsAlphaToOne() const { + return features.features.alphaToOne != VK_FALSE; + } + /// Returns true if the device supports VK_EXT_vertex_input_dynamic_state. bool IsExtVertexInputDynamicStateSupported() const { return extensions.vertex_input_dynamic_state;