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.
This commit is contained in:
Bruno 2026-10-07 03:50:37 +02:00 committed by GitHub
parent 59cee41375
commit 7d9129941b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 94 additions and 18 deletions

View File

@ -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:

View File

@ -4,6 +4,7 @@
#pragma once
#include <cstddef>
#include <optional>
#include <boost/container/small_vector.hpp>
@ -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<Shader::OutputTopology> 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)

View File

@ -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<vk::ShaderModule, NUM_STAGES> stages,
const std::array<const Shader::Info*, NUM_STAGES>& infos)
const std::array<const Shader::Info*, NUM_STAGES>& 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,

View File

@ -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<vk::ShaderModule, NUM_STAGES> stages,
const std::array<const Shader::Info*, NUM_STAGES>& infos);
const std::array<const Shader::Info*, NUM_STAGES>& 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

View File

@ -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<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
}
previous_stage = &program;
}
const bool has_tessellation{static_cast<bool>(modules[2])};
const bool tessellation_isolines{
static_cast<Maxwell::Tessellation::DomainType>(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<GraphicsPipeline>(
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();

View File

@ -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;