From 1574e7e8042046309a0f471d82dc6c9fd040c856 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Mon, 1 Dec 2025 02:37:41 -0400 Subject: [PATCH] [vk] DualBlendFactor --- .../renderer_vulkan/fixed_pipeline_state.h | 1 + .../renderer_vulkan/vk_graphics_pipeline.cpp | 73 ++++++++++++++++++- .../renderer_vulkan/vk_pipeline_cache.cpp | 5 ++ src/video_core/vulkan_common/vulkan_device.h | 10 +++ 4 files changed, 85 insertions(+), 4 deletions(-) diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.h b/src/video_core/renderer_vulkan/fixed_pipeline_state.h index c5bc14f448..dd11000e65 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.h +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.h @@ -27,6 +27,7 @@ struct DynamicFeatures { bool has_extended_dynamic_state_2_patch_control_points; bool has_extended_dynamic_state_3_blend; bool has_extended_dynamic_state_3_enables; + bool has_dual_source_blend; bool has_dynamic_vertex_input; }; diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 58ba162e36..dca263b322 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -85,6 +85,48 @@ PixelFormat ResolveTexelBufferFormat(PixelFormat format, return format; } +bool UsesDualSourceFactor(Maxwell::Blend::Factor factor) { + switch (factor) { + case Maxwell::Blend::Factor::Source1Color_D3D: + case Maxwell::Blend::Factor::Source1Color_GL: + case Maxwell::Blend::Factor::OneMinusSource1Color_D3D: + case Maxwell::Blend::Factor::OneMinusSource1Color_GL: + case Maxwell::Blend::Factor::Source1Alpha_D3D: + case Maxwell::Blend::Factor::Source1Alpha_GL: + case Maxwell::Blend::Factor::OneMinusSource1Alpha_D3D: + case Maxwell::Blend::Factor::OneMinusSource1Alpha_GL: + return true; + default: + return false; + } +} + +Maxwell::Blend::Factor FallbackDualSourceFactor(Maxwell::Blend::Factor factor) { + switch (factor) { + case Maxwell::Blend::Factor::Source1Color_D3D: + case Maxwell::Blend::Factor::Source1Color_GL: + return Maxwell::Blend::Factor::SourceColor_D3D; + case Maxwell::Blend::Factor::OneMinusSource1Color_D3D: + case Maxwell::Blend::Factor::OneMinusSource1Color_GL: + return Maxwell::Blend::Factor::OneMinusSourceColor_D3D; + case Maxwell::Blend::Factor::Source1Alpha_D3D: + case Maxwell::Blend::Factor::Source1Alpha_GL: + return Maxwell::Blend::Factor::SourceAlpha_D3D; + case Maxwell::Blend::Factor::OneMinusSource1Alpha_D3D: + case Maxwell::Blend::Factor::OneMinusSource1Alpha_GL: + return Maxwell::Blend::Factor::OneMinusSourceAlpha_D3D; + default: + return factor; + } +} + +bool AttachmentUsesDualSource(const FixedPipelineState::BlendingAttachment& blend) { + return UsesDualSourceFactor(blend.SourceRGBFactor()) || + UsesDualSourceFactor(blend.DestRGBFactor()) || + UsesDualSourceFactor(blend.SourceAlphaFactor()) || + UsesDualSourceFactor(blend.DestAlphaFactor()); +} + DescriptorLayoutBuilder MakeBuilder(const Device& device, std::span infos) { DescriptorLayoutBuilder builder{device}; for (size_t index = 0; index < infos.size(); ++index) { @@ -855,6 +897,12 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { } static_vector cb_attachments; const size_t num_attachments{NumAttachments(key.state)}; + const bool supports_dual_source_blend = device.SupportsDualSourceBlend(); + const u32 max_dual_source_attachments = supports_dual_source_blend + ? device.MaxFragmentDualSrcAttachments() + : 0; + u32 granted_dual_source_attachments = 0; + bool logged_dual_source_warning = false; for (size_t index = 0; index < num_attachments; ++index) { static constexpr std::array mask_table{ VK_COLOR_COMPONENT_R_BIT, @@ -868,13 +916,30 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { for (size_t i = 0; i < mask_table.size(); ++i) { write_mask |= mask[i] ? mask_table[i] : 0; } + const bool attachment_uses_dual_source = AttachmentUsesDualSource(blend); + const bool allow_dual_source = attachment_uses_dual_source && supports_dual_source_blend && + granted_dual_source_attachments < max_dual_source_attachments; + if (allow_dual_source) { + ++granted_dual_source_attachments; + } else if (attachment_uses_dual_source && !logged_dual_source_warning) { + LOG_WARNING(Render_Vulkan, + "Dual-source blend factors exceed device limit (maxFragmentDualSrcAttachments={}), falling back to single-source factors", + max_dual_source_attachments); + logged_dual_source_warning = true; + } + const auto sanitize_factor = [&](Maxwell::Blend::Factor factor) { + if (allow_dual_source || !UsesDualSourceFactor(factor)) { + return factor; + } + return FallbackDualSourceFactor(factor); + }; cb_attachments.push_back({ .blendEnable = blend.enable != 0, - .srcColorBlendFactor = MaxwellToVK::BlendFactor(blend.SourceRGBFactor()), - .dstColorBlendFactor = MaxwellToVK::BlendFactor(blend.DestRGBFactor()), + .srcColorBlendFactor = MaxwellToVK::BlendFactor(sanitize_factor(blend.SourceRGBFactor())), + .dstColorBlendFactor = MaxwellToVK::BlendFactor(sanitize_factor(blend.DestRGBFactor())), .colorBlendOp = MaxwellToVK::BlendEquation(blend.EquationRGB()), - .srcAlphaBlendFactor = MaxwellToVK::BlendFactor(blend.SourceAlphaFactor()), - .dstAlphaBlendFactor = MaxwellToVK::BlendFactor(blend.DestAlphaFactor()), + .srcAlphaBlendFactor = MaxwellToVK::BlendFactor(sanitize_factor(blend.SourceAlphaFactor())), + .dstAlphaBlendFactor = MaxwellToVK::BlendFactor(sanitize_factor(blend.DestAlphaFactor())), .alphaBlendOp = MaxwellToVK::BlendEquation(blend.EquationAlpha()), .colorWriteMask = write_mask, }); diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index e96c88d3aa..c1b8c7f925 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -492,6 +492,11 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, dynamic_features.has_extended_dynamic_state_3_blend = device.IsExtExtendedDynamicState3BlendingSupported(); + dynamic_features.has_dual_source_blend = device.SupportsDualSourceBlend(); + if (!dynamic_features.has_dual_source_blend) { + LOG_WARNING(Render_Vulkan, "Dual-source blending unsupported, disabling dynamic blend"); + dynamic_features.has_extended_dynamic_state_3_blend = false; + } dynamic_features.has_extended_dynamic_state_3_enables = device.IsExtExtendedDynamicState3EnablesSupported(); diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 00284057ef..21470d20ee 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -710,6 +710,16 @@ public: return features.features.alphaToOne != VK_FALSE; } + bool SupportsDualSourceBlend(u32 required_dual_source_attachments = 1) const { + const u32 max_dual = properties.properties.limits.maxFragmentDualSrcAttachments; + return features.features.dualSrcBlend != VK_FALSE && + max_dual >= required_dual_source_attachments; + } + + u32 MaxFragmentDualSrcAttachments() const { + return properties.properties.limits.maxFragmentDualSrcAttachments; + } + bool SupportsDynamicState3DepthClampEnable() const { return dynamic_state3_depth_clamp_enable; }