video_core: Emulate zero/one MIN/MAX blend factors (#5197)

* Use equivalent add operations for UNORM & sRGB targets after blend factor substitutions
This commit is contained in:
kiwakiwaa authored and GitHub committed 2026-10-05 23:23:07 +03:00
1 parent c679ce6092
commit c5dd8b8b50
3 files changed
+50 -36

No files matched your search

@@ -262,6 +262,26 @@ vk::BlendOp BlendOp(AmdGpu::BlendControl::BlendFunc func) {
}
}
void NormalizeMinMaxBlend(vk::BlendOp& op, vk::BlendFactor& src, vk::BlendFactor& dst,
AmdGpu::NumberFormat format) {
if (format != AmdGpu::NumberFormat::Unorm && format != AmdGpu::NumberFormat::Srgb) {
return;
}
if (op != vk::BlendOp::eMin && op != vk::BlendOp::eMax) {
return;
}
if ((src != vk::BlendFactor::eZero && src != vk::BlendFactor::eOne) ||
(dst != vk::BlendFactor::eZero && dst != vk::BlendFactor::eOne) ||
(src != vk::BlendFactor::eZero && dst != vk::BlendFactor::eZero)) {
return;
}
if (op == vk::BlendOp::eMin) {
src = vk::BlendFactor::eZero;
dst = vk::BlendFactor::eZero;
}
op = vk::BlendOp::eAdd;
}
vk::LogicOp LogicOp(AmdGpu::ColorControl::LogicOp logic_op) {
using LogicOp = AmdGpu::ColorControl::LogicOp;
switch (logic_op) {
@@ -35,6 +35,9 @@ bool IsDualSourceBlendFactor(AmdGpu::BlendControl::BlendFactor factor);
vk::BlendOp BlendOp(AmdGpu::BlendControl::BlendFunc func);
void NormalizeMinMaxBlend(vk::BlendOp& op, vk::BlendFactor& src, vk::BlendFactor& dst,
AmdGpu::NumberFormat format);
vk::LogicOp LogicOp(AmdGpu::ColorControl::LogicOp logic_op);
vk::SamplerAddressMode ClampMode(AmdGpu::ClampMode mode);
@@ -311,47 +311,14 @@ GraphicsPipeline::GraphicsPipeline(
const auto alpha_blend =
control.separate_alpha_blend ? LiverpoolToVK::BlendOp(control.alpha_func) : color_blend;
// Vulkan ignores blend factors for min/max, but a factor that zeroes one operand
// makes the operation collapse to a plain selection: min(s, 0) is 0 and max(s, 0)
// is s for normalized alpha. Rewrite those to the equivalent add so the result is
// exact instead of leaving the other operand to survive.
auto eff_src_alpha = src_alpha;
auto eff_dst_alpha = dst_alpha;
auto eff_alpha_blend = alpha_blend;
if (alpha_blend == vk::BlendOp::eMin || alpha_blend == vk::BlendOp::eMax) {
const bool takes_max = alpha_blend == vk::BlendOp::eMax;
if (src_alpha == vk::BlendFactor::eOne && dst_alpha == vk::BlendFactor::eZero) {
eff_alpha_blend = vk::BlendOp::eAdd;
eff_src_alpha = takes_max ? vk::BlendFactor::eOne : vk::BlendFactor::eZero;
eff_dst_alpha = vk::BlendFactor::eZero;
} else if (src_alpha == vk::BlendFactor::eZero && dst_alpha == vk::BlendFactor::eOne) {
eff_alpha_blend = vk::BlendOp::eAdd;
eff_src_alpha = vk::BlendFactor::eZero;
eff_dst_alpha = takes_max ? vk::BlendFactor::eOne : vk::BlendFactor::eZero;
}
}
const auto color_scaled_min_max =
(color_blend == vk::BlendOp::eMin || color_blend == vk::BlendOp::eMax) &&
(src_color != vk::BlendFactor::eOne || dst_color != vk::BlendFactor::eOne) &&
!key.color_buffers[i].blend_self_scale;
const auto alpha_scaled_min_max =
(eff_alpha_blend == vk::BlendOp::eMin || eff_alpha_blend == vk::BlendOp::eMax) &&
(eff_src_alpha != vk::BlendFactor::eOne || eff_dst_alpha != vk::BlendFactor::eOne);
if (color_scaled_min_max || alpha_scaled_min_max) {
LOG_WARNING(
Render_Vulkan,
"Unimplemented use of min/max blend op with blend factor not equal to one.");
}
attachments[i] = vk::PipelineColorBlendAttachmentState{
.blendEnable = control.enable,
.srcColorBlendFactor = src_color,
.dstColorBlendFactor = dst_color,
.colorBlendOp = color_blend,
.srcAlphaBlendFactor = eff_src_alpha,
.dstAlphaBlendFactor = eff_dst_alpha,
.alphaBlendOp = eff_alpha_blend,
.srcAlphaBlendFactor = src_alpha,
.dstAlphaBlendFactor = dst_alpha,
.alphaBlendOp = alpha_blend,
.colorWriteMask =
instance.IsDynamicColorWriteMaskSupported()
? vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
@@ -391,6 +358,30 @@ GraphicsPipeline::GraphicsPipeline(
? vk::BlendFactor::eOne
: vk::BlendFactor::eZero; // 1-A
}
auto& attachment = attachments[i];
LiverpoolToVK::NormalizeMinMaxBlend(attachment.colorBlendOp, attachment.srcColorBlendFactor,
attachment.dstColorBlendFactor,
key.color_buffers[i].num_format);
LiverpoolToVK::NormalizeMinMaxBlend(attachment.alphaBlendOp, attachment.srcAlphaBlendFactor,
attachment.dstAlphaBlendFactor,
key.color_buffers[i].num_format);
const auto color_scaled_min_max =
(attachment.colorBlendOp == vk::BlendOp::eMin ||
attachment.colorBlendOp == vk::BlendOp::eMax) &&
(attachment.srcColorBlendFactor != vk::BlendFactor::eOne ||
attachment.dstColorBlendFactor != vk::BlendFactor::eOne);
const auto alpha_scaled_min_max =
(attachment.alphaBlendOp == vk::BlendOp::eMin ||
attachment.alphaBlendOp == vk::BlendOp::eMax) &&
(attachment.srcAlphaBlendFactor != vk::BlendFactor::eOne ||
attachment.dstAlphaBlendFactor != vk::BlendFactor::eOne);
if (control.enable && (color_scaled_min_max || alpha_scaled_min_max)) {
LOG_WARNING(
Render_Vulkan,
"Unimplemented use of min/max blend op with blend factor not equal to one.");
}
}
const vk::PipelineColorBlendStateCreateInfo color_blending = {