VK_EXT_blend_operation_advanced minimal implementation

This CL adds support for VK_EXT_blend_operation_advanced,
with limited features:
- VkPipelineColorBlendAdvancedStateCreateInfoEXT::blendOverlap
  must be VK_BLEND_OVERLAP_UNCORRELATED_EXT
- VkPipelineColorBlendAdvancedStateCreateInfoEXT::dstPremultiplied
  must be VK_TRUE
- VkPipelineColorBlendAdvancedStateCreateInfoEXT::srcPremultiplied
  must be VK_TRUE

Tests: dEQP-VK.api.info.vulkan1p2_limits_validation.ext_blend_operation_advanced
Tests: dEQP-VK.pipeline.blend_operation_advanced.*
Bug: b/203652972
Change-Id: I046452c4399e4895dd98689ce237b50353dcf24a
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/58348
Commit-Queue: Alexis Hétu <sugoi@google.com>
Tested-by: Alexis Hétu <sugoi@google.com>
Presubmit-Ready: Alexis Hétu <sugoi@google.com>
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
diff --git a/src/Device/Context.cpp b/src/Device/Context.cpp
index 550a815..64148a5 100644
--- a/src/Device/Context.cpp
+++ b/src/Device/Context.cpp
@@ -531,6 +531,30 @@
 				blendConstants.w = colorBlendState->blendConstants[3];
 			}
 
+			const VkBaseInStructure *extensionColorBlendInfo = reinterpret_cast<const VkBaseInStructure *>(colorBlendState->pNext);
+			while(extensionColorBlendInfo)
+			{
+				switch(extensionColorBlendInfo->sType)
+				{
+				case VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_ADVANCED_STATE_CREATE_INFO_EXT:
+					{
+						const VkPipelineColorBlendAdvancedStateCreateInfoEXT *colorBlendAdvancedCreateInfo = reinterpret_cast<const VkPipelineColorBlendAdvancedStateCreateInfoEXT *>(extensionColorBlendInfo);
+						ASSERT(colorBlendAdvancedCreateInfo->blendOverlap == VK_BLEND_OVERLAP_UNCORRELATED_EXT);
+						ASSERT(colorBlendAdvancedCreateInfo->dstPremultiplied == VK_TRUE);
+						ASSERT(colorBlendAdvancedCreateInfo->srcPremultiplied == VK_TRUE);
+					}
+					break;
+				case VK_STRUCTURE_TYPE_MAX_ENUM:
+					// dEQP tests that this value is ignored.
+					break;
+				default:
+					UNSUPPORTED("pCreateInfo->colorBlendState->pNext sType = %s", vk::Stringify(extensionColorBlendInfo->sType).c_str());
+					break;
+				}
+
+				extensionColorBlendInfo = extensionColorBlendInfo->pNext;
+			}
+
 			ASSERT(colorBlendState->attachmentCount <= sw::MAX_COLOR_BUFFERS);
 			for(auto i = 0u; i < colorBlendState->attachmentCount; i++)
 			{
@@ -725,8 +749,22 @@
 	case VK_BLEND_OP_REVERSE_SUBTRACT:
 		return blendState[index].sourceBlendFactor;
 	case VK_BLEND_OP_MIN:
-		return VK_BLEND_FACTOR_ONE;
 	case VK_BLEND_OP_MAX:
+	case VK_BLEND_OP_MULTIPLY_EXT:
+	case VK_BLEND_OP_SCREEN_EXT:
+	case VK_BLEND_OP_OVERLAY_EXT:
+	case VK_BLEND_OP_DARKEN_EXT:
+	case VK_BLEND_OP_LIGHTEN_EXT:
+	case VK_BLEND_OP_COLORDODGE_EXT:
+	case VK_BLEND_OP_COLORBURN_EXT:
+	case VK_BLEND_OP_HARDLIGHT_EXT:
+	case VK_BLEND_OP_SOFTLIGHT_EXT:
+	case VK_BLEND_OP_DIFFERENCE_EXT:
+	case VK_BLEND_OP_EXCLUSION_EXT:
+	case VK_BLEND_OP_HSL_HUE_EXT:
+	case VK_BLEND_OP_HSL_SATURATION_EXT:
+	case VK_BLEND_OP_HSL_COLOR_EXT:
+	case VK_BLEND_OP_HSL_LUMINOSITY_EXT:
 		return VK_BLEND_FACTOR_ONE;
 	default:
 		ASSERT(false);
@@ -748,8 +786,22 @@
 	case VK_BLEND_OP_REVERSE_SUBTRACT:
 		return blendState[index].destBlendFactor;
 	case VK_BLEND_OP_MIN:
-		return VK_BLEND_FACTOR_ONE;
 	case VK_BLEND_OP_MAX:
+	case VK_BLEND_OP_MULTIPLY_EXT:
+	case VK_BLEND_OP_SCREEN_EXT:
+	case VK_BLEND_OP_OVERLAY_EXT:
+	case VK_BLEND_OP_DARKEN_EXT:
+	case VK_BLEND_OP_LIGHTEN_EXT:
+	case VK_BLEND_OP_COLORDODGE_EXT:
+	case VK_BLEND_OP_COLORBURN_EXT:
+	case VK_BLEND_OP_HARDLIGHT_EXT:
+	case VK_BLEND_OP_SOFTLIGHT_EXT:
+	case VK_BLEND_OP_DIFFERENCE_EXT:
+	case VK_BLEND_OP_EXCLUSION_EXT:
+	case VK_BLEND_OP_HSL_HUE_EXT:
+	case VK_BLEND_OP_HSL_SATURATION_EXT:
+	case VK_BLEND_OP_HSL_COLOR_EXT:
+	case VK_BLEND_OP_HSL_LUMINOSITY_EXT:
 		return VK_BLEND_FACTOR_ONE;
 	default:
 		ASSERT(false);
@@ -862,9 +914,23 @@
 			}
 		}
 	case VK_BLEND_OP_MIN:
-		return VK_BLEND_OP_MIN;
 	case VK_BLEND_OP_MAX:
-		return VK_BLEND_OP_MAX;
+	case VK_BLEND_OP_MULTIPLY_EXT:
+	case VK_BLEND_OP_SCREEN_EXT:
+	case VK_BLEND_OP_OVERLAY_EXT:
+	case VK_BLEND_OP_DARKEN_EXT:
+	case VK_BLEND_OP_LIGHTEN_EXT:
+	case VK_BLEND_OP_COLORDODGE_EXT:
+	case VK_BLEND_OP_COLORBURN_EXT:
+	case VK_BLEND_OP_HARDLIGHT_EXT:
+	case VK_BLEND_OP_SOFTLIGHT_EXT:
+	case VK_BLEND_OP_DIFFERENCE_EXT:
+	case VK_BLEND_OP_EXCLUSION_EXT:
+	case VK_BLEND_OP_HSL_HUE_EXT:
+	case VK_BLEND_OP_HSL_SATURATION_EXT:
+	case VK_BLEND_OP_HSL_COLOR_EXT:
+	case VK_BLEND_OP_HSL_LUMINOSITY_EXT:
+		return blendState[index].blendOperation;
 	default:
 		ASSERT(false);
 	}
@@ -883,8 +949,22 @@
 	case VK_BLEND_OP_REVERSE_SUBTRACT:
 		return blendState[index].sourceBlendFactorAlpha;
 	case VK_BLEND_OP_MIN:
-		return VK_BLEND_FACTOR_ONE;
 	case VK_BLEND_OP_MAX:
+	case VK_BLEND_OP_MULTIPLY_EXT:
+	case VK_BLEND_OP_SCREEN_EXT:
+	case VK_BLEND_OP_OVERLAY_EXT:
+	case VK_BLEND_OP_DARKEN_EXT:
+	case VK_BLEND_OP_LIGHTEN_EXT:
+	case VK_BLEND_OP_COLORDODGE_EXT:
+	case VK_BLEND_OP_COLORBURN_EXT:
+	case VK_BLEND_OP_HARDLIGHT_EXT:
+	case VK_BLEND_OP_SOFTLIGHT_EXT:
+	case VK_BLEND_OP_DIFFERENCE_EXT:
+	case VK_BLEND_OP_EXCLUSION_EXT:
+	case VK_BLEND_OP_HSL_HUE_EXT:
+	case VK_BLEND_OP_HSL_SATURATION_EXT:
+	case VK_BLEND_OP_HSL_COLOR_EXT:
+	case VK_BLEND_OP_HSL_LUMINOSITY_EXT:
 		return VK_BLEND_FACTOR_ONE;
 	default:
 		ASSERT(false);
@@ -904,8 +984,22 @@
 	case VK_BLEND_OP_REVERSE_SUBTRACT:
 		return blendState[index].destBlendFactorAlpha;
 	case VK_BLEND_OP_MIN:
-		return VK_BLEND_FACTOR_ONE;
 	case VK_BLEND_OP_MAX:
+	case VK_BLEND_OP_MULTIPLY_EXT:
+	case VK_BLEND_OP_SCREEN_EXT:
+	case VK_BLEND_OP_OVERLAY_EXT:
+	case VK_BLEND_OP_DARKEN_EXT:
+	case VK_BLEND_OP_LIGHTEN_EXT:
+	case VK_BLEND_OP_COLORDODGE_EXT:
+	case VK_BLEND_OP_COLORBURN_EXT:
+	case VK_BLEND_OP_HARDLIGHT_EXT:
+	case VK_BLEND_OP_SOFTLIGHT_EXT:
+	case VK_BLEND_OP_DIFFERENCE_EXT:
+	case VK_BLEND_OP_EXCLUSION_EXT:
+	case VK_BLEND_OP_HSL_HUE_EXT:
+	case VK_BLEND_OP_HSL_SATURATION_EXT:
+	case VK_BLEND_OP_HSL_COLOR_EXT:
+	case VK_BLEND_OP_HSL_LUMINOSITY_EXT:
 		return VK_BLEND_FACTOR_ONE;
 	default:
 		ASSERT(false);
@@ -1019,6 +1113,24 @@
 		return VK_BLEND_OP_MIN;
 	case VK_BLEND_OP_MAX:
 		return VK_BLEND_OP_MAX;
+	case VK_BLEND_OP_MULTIPLY_EXT:
+	case VK_BLEND_OP_SCREEN_EXT:
+	case VK_BLEND_OP_OVERLAY_EXT:
+	case VK_BLEND_OP_DARKEN_EXT:
+	case VK_BLEND_OP_LIGHTEN_EXT:
+	case VK_BLEND_OP_COLORDODGE_EXT:
+	case VK_BLEND_OP_COLORBURN_EXT:
+	case VK_BLEND_OP_HARDLIGHT_EXT:
+	case VK_BLEND_OP_SOFTLIGHT_EXT:
+	case VK_BLEND_OP_DIFFERENCE_EXT:
+	case VK_BLEND_OP_EXCLUSION_EXT:
+	case VK_BLEND_OP_HSL_HUE_EXT:
+	case VK_BLEND_OP_HSL_SATURATION_EXT:
+	case VK_BLEND_OP_HSL_COLOR_EXT:
+	case VK_BLEND_OP_HSL_LUMINOSITY_EXT:
+		// All of the currently supported advanced blend modes compute the alpha the same way
+		// Use VK_BLEND_OP_MULTIPLY_EXT as a placeholder
+		return VK_BLEND_OP_MULTIPLY_EXT;
 	default:
 		ASSERT(false);
 	}
diff --git a/src/Pipeline/PixelRoutine.cpp b/src/Pipeline/PixelRoutine.cpp
index 8053391..ab1187c 100644
--- a/src/Pipeline/PixelRoutine.cpp
+++ b/src/Pipeline/PixelRoutine.cpp
@@ -1948,6 +1948,243 @@
 	}
 }
 
+Float4 PixelRoutine::blendOpOverlay(Float4 &src, Float4 &dst)
+{
+	Int4 largeDst = CmpGT(dst, Float4(0.5f));
+	return As<Float4>(
+	    (~largeDst &
+	     As<Int4>(Float4(2.0f) * src * dst)) |
+	    (largeDst &
+	     As<Int4>(Float4(1.0f) - (Float4(2.0f) * (Float4(1.0f) - src) * (Float4(1.0f) - dst)))));
+}
+
+Float4 PixelRoutine::blendOpColorDodge(Float4 &src, Float4 &dst)
+{
+	Int4 srcBelowOne = CmpLT(src, Float4(1.0f));
+	Int4 positiveDst = CmpGT(dst, Float4(0.0f));
+	return As<Float4>(positiveDst & ((~srcBelowOne &
+	                                  As<Int4>(Float4(1.0f))) |
+	                                 (srcBelowOne &
+	                                  As<Int4>(Min(Float4(1.0f), (dst / (Float4(1.0f) - src)))))));
+}
+
+Float4 PixelRoutine::blendOpColorBurn(Float4 &src, Float4 &dst)
+{
+	Int4 dstBelowOne = CmpLT(dst, Float4(1.0f));
+	Int4 positiveSrc = CmpGT(src, Float4(0.0f));
+	return As<Float4>(
+	    (~dstBelowOne &
+	     As<Int4>(Float4(1.0f))) |
+	    (dstBelowOne & positiveSrc &
+	     As<Int4>(Float4(1.0f) - Min(Float4(1.0f), (Float4(1.0f) - dst) / src))));
+}
+
+Float4 PixelRoutine::blendOpHardlight(Float4 &src, Float4 &dst)
+{
+	Int4 largeSrc = CmpGT(src, Float4(0.5f));
+	return As<Float4>(
+	    (~largeSrc &
+	     As<Int4>(Float4(2.0f) * src * dst)) |
+	    (largeSrc &
+	     As<Int4>(Float4(1.0f) - (Float4(2.0f) * (Float4(1.0f) - src) * (Float4(1.0f) - dst)))));
+}
+
+Float4 PixelRoutine::blendOpSoftlight(Float4 &src, Float4 &dst)
+{
+	Int4 largeSrc = CmpGT(src, Float4(0.5f));
+	Int4 largeDst = CmpGT(dst, Float4(0.25f));
+
+	return As<Float4>(
+	    (~largeSrc &
+	     As<Int4>(dst - ((Float4(1.0f) - (Float4(2.0f) * src)) * dst * (Float4(1.0f) - dst)))) |
+	    (largeSrc & ((~largeDst &
+	                  As<Int4>(dst + (((Float4(2.0f) * src) - Float4(1.0f)) * dst * ((((Float4(16.0f) * dst) - Float4(12.0f)) * dst) + Float4(3.0f))))) |
+	                 (largeDst &
+	                  As<Int4>(dst + (((Float4(2.0f) * src) - Float4(1.0f)) * (Sqrt(dst) - dst)))))));
+}
+
+Float4 PixelRoutine::maxRGB(Vector4f &c)
+{
+	return Max(Max(c.x, c.y), c.z);
+}
+
+Float4 PixelRoutine::minRGB(Vector4f &c)
+{
+	return Min(Min(c.x, c.y), c.z);
+}
+
+void PixelRoutine::setLumSat(Vector4f &cbase, Vector4f &csat, Vector4f &clum, Float4 &x, Float4 &y, Float4 &z)
+{
+	Float4 minbase = minRGB(cbase);
+	Float4 sbase = maxRGB(cbase) - minbase;
+	Float4 ssat = maxRGB(csat) - minRGB(csat);
+	Int4 isNonZero = CmpGT(sbase, Float4(0.0f));
+	Vector4f color;
+	color.x = As<Float4>(isNonZero & As<Int4>((cbase.x - minbase) * ssat / sbase));
+	color.y = As<Float4>(isNonZero & As<Int4>((cbase.y - minbase) * ssat / sbase));
+	color.z = As<Float4>(isNonZero & As<Int4>((cbase.z - minbase) * ssat / sbase));
+	setLum(color, clum, x, y, z);
+}
+
+Float4 PixelRoutine::lumRGB(Vector4f &c)
+{
+	return c.x * Float4(0.3f) + c.y * Float4(0.59f) + c.z * Float4(0.11f);
+}
+
+Float4 PixelRoutine::computeLum(Float4 &color, Float4 &lum, Float4 &mincol, Float4 &maxcol, Int4 &negative, Int4 &aboveOne)
+{
+	return As<Float4>(
+	    (negative &
+	     As<Int4>(lum + ((color - lum) * lum) / (lum - mincol))) |
+	    (~negative &
+	     ((aboveOne &
+	       As<Int4>(lum + ((color - lum) * (Float4(1.0f) - lum)) / (Float4(maxcol) - lum))) |
+	      (~aboveOne &
+	       As<Int4>(color)))));
+}
+
+void PixelRoutine::setLum(Vector4f &cbase, Vector4f &clum, Float4 &x, Float4 &y, Float4 &z)
+{
+	Float4 lbase = lumRGB(cbase);
+	Float4 llum = lumRGB(clum);
+	Float4 ldiff = llum - lbase;
+
+	Vector4f color;
+	color.x = cbase.x + ldiff;
+	color.y = cbase.y + ldiff;
+	color.z = cbase.z + ldiff;
+
+	Float4 lum = lumRGB(color);
+	Float4 mincol = minRGB(color);
+	Float4 maxcol = maxRGB(color);
+
+	Int4 negative = CmpLT(mincol, Float4(0.0f));
+	Int4 aboveOne = CmpGT(maxcol, Float4(1.0f));
+
+	x = computeLum(color.x, lum, mincol, maxcol, negative, aboveOne);
+	y = computeLum(color.y, lum, mincol, maxcol, negative, aboveOne);
+	z = computeLum(color.z, lum, mincol, maxcol, negative, aboveOne);
+}
+
+void PixelRoutine::premultiply(Vector4f &c)
+{
+	Int4 nonZeroAlpha = CmpNEQ(c.w, Float4(0.0f));
+	c.x = As<Float4>(nonZeroAlpha & As<Int4>(c.x / c.w));
+	c.y = As<Float4>(nonZeroAlpha & As<Int4>(c.y / c.w));
+	c.z = As<Float4>(nonZeroAlpha & As<Int4>(c.z / c.w));
+}
+
+Vector4f PixelRoutine::computeAdvancedBlendMode(int index, const Vector4f &src, const Vector4f &dst, const Vector4f &srcFactor, const Vector4f &dstFactor)
+{
+	Vector4f srcColor = src;
+	srcColor.x *= srcFactor.x;
+	srcColor.y *= srcFactor.y;
+	srcColor.z *= srcFactor.z;
+	srcColor.w *= srcFactor.w;
+
+	Vector4f dstColor = dst;
+	dstColor.x *= dstFactor.x;
+	dstColor.y *= dstFactor.y;
+	dstColor.z *= dstFactor.z;
+	dstColor.w *= dstFactor.w;
+
+	premultiply(srcColor);
+	premultiply(dstColor);
+
+	Vector4f blendedColor;
+
+	switch(state.blendState[index].blendOperation)
+	{
+	case VK_BLEND_OP_MULTIPLY_EXT:
+		blendedColor.x = (srcColor.x * dstColor.x);
+		blendedColor.y = (srcColor.y * dstColor.y);
+		blendedColor.z = (srcColor.z * dstColor.z);
+		break;
+	case VK_BLEND_OP_SCREEN_EXT:
+		blendedColor.x = srcColor.x + dstColor.x - (srcColor.x * dstColor.x);
+		blendedColor.y = srcColor.y + dstColor.y - (srcColor.y * dstColor.y);
+		blendedColor.z = srcColor.z + dstColor.z - (srcColor.z * dstColor.z);
+		break;
+	case VK_BLEND_OP_OVERLAY_EXT:
+		blendedColor.x = blendOpOverlay(srcColor.x, dstColor.x);
+		blendedColor.y = blendOpOverlay(srcColor.y, dstColor.y);
+		blendedColor.z = blendOpOverlay(srcColor.z, dstColor.z);
+		break;
+	case VK_BLEND_OP_DARKEN_EXT:
+		blendedColor.x = Min(srcColor.x, dstColor.x);
+		blendedColor.y = Min(srcColor.y, dstColor.y);
+		blendedColor.z = Min(srcColor.z, dstColor.z);
+		break;
+	case VK_BLEND_OP_LIGHTEN_EXT:
+		blendedColor.x = Max(srcColor.x, dstColor.x);
+		blendedColor.y = Max(srcColor.y, dstColor.y);
+		blendedColor.z = Max(srcColor.z, dstColor.z);
+		break;
+	case VK_BLEND_OP_COLORDODGE_EXT:
+		blendedColor.x = blendOpColorDodge(srcColor.x, dstColor.x);
+		blendedColor.y = blendOpColorDodge(srcColor.y, dstColor.y);
+		blendedColor.z = blendOpColorDodge(srcColor.z, dstColor.z);
+		break;
+	case VK_BLEND_OP_COLORBURN_EXT:
+		blendedColor.x = blendOpColorBurn(srcColor.x, dstColor.x);
+		blendedColor.y = blendOpColorBurn(srcColor.y, dstColor.y);
+		blendedColor.z = blendOpColorBurn(srcColor.z, dstColor.z);
+		break;
+	case VK_BLEND_OP_HARDLIGHT_EXT:
+		blendedColor.x = blendOpHardlight(srcColor.x, dstColor.x);
+		blendedColor.y = blendOpHardlight(srcColor.y, dstColor.y);
+		blendedColor.z = blendOpHardlight(srcColor.z, dstColor.z);
+		break;
+	case VK_BLEND_OP_SOFTLIGHT_EXT:
+		blendedColor.x = blendOpSoftlight(srcColor.x, dstColor.x);
+		blendedColor.y = blendOpSoftlight(srcColor.y, dstColor.y);
+		blendedColor.z = blendOpSoftlight(srcColor.z, dstColor.z);
+		break;
+	case VK_BLEND_OP_DIFFERENCE_EXT:
+		blendedColor.x = Abs(srcColor.x - dstColor.x);
+		blendedColor.y = Abs(srcColor.y - dstColor.y);
+		blendedColor.z = Abs(srcColor.z - dstColor.z);
+		break;
+	case VK_BLEND_OP_EXCLUSION_EXT:
+		blendedColor.x = srcColor.x + dstColor.x - (srcColor.x * dstColor.x * Float4(2.0f));
+		blendedColor.y = srcColor.y + dstColor.y - (srcColor.y * dstColor.y * Float4(2.0f));
+		blendedColor.z = srcColor.z + dstColor.z - (srcColor.z * dstColor.z * Float4(2.0f));
+		break;
+	case VK_BLEND_OP_HSL_HUE_EXT:
+		setLumSat(srcColor, dstColor, dstColor, blendedColor.x, blendedColor.y, blendedColor.z);
+		break;
+	case VK_BLEND_OP_HSL_SATURATION_EXT:
+		setLumSat(dstColor, srcColor, dstColor, blendedColor.x, blendedColor.y, blendedColor.z);
+		break;
+	case VK_BLEND_OP_HSL_COLOR_EXT:
+		setLum(srcColor, dstColor, blendedColor.x, blendedColor.y, blendedColor.z);
+		break;
+	case VK_BLEND_OP_HSL_LUMINOSITY_EXT:
+		setLum(dstColor, srcColor, blendedColor.x, blendedColor.y, blendedColor.z);
+		break;
+	default:
+		UNSUPPORTED("Unsupported advanced VkBlendOp: %d", int(state.blendState[index].blendOperation));
+		break;
+	}
+
+	Float4 p = srcColor.w * dstColor.w;
+	blendedColor.x *= p;
+	blendedColor.y *= p;
+	blendedColor.z *= p;
+
+	p = srcColor.w * (Float4(1.0f) - dstColor.w);
+	blendedColor.x += srcColor.x * p;
+	blendedColor.y += srcColor.y * p;
+	blendedColor.z += srcColor.z * p;
+
+	p = dstColor.w * (Float4(1.0f) - srcColor.w);
+	blendedColor.x += dstColor.x * p;
+	blendedColor.y += dstColor.y * p;
+	blendedColor.z += dstColor.z * p;
+
+	return blendedColor;
+}
+
 bool PixelRoutine::blendFactorCanExceedFormatRange(VkBlendFactor blendFactor, vk::Format format)
 {
 	switch(blendFactor)
@@ -2162,6 +2399,8 @@
 
 	blendFactorRGB(sourceFactor, sourceColor, destColor, state.blendState[index].sourceBlendFactor, format);
 	blendFactorRGB(destFactor, sourceColor, destColor, state.blendState[index].destBlendFactor, format);
+	blendFactorAlpha(sourceFactor.w, sourceColor.w, destColor.w, state.blendState[index].sourceBlendFactorAlpha, format);
+	blendFactorAlpha(destFactor.w, sourceColor.w, destColor.w, state.blendState[index].destBlendFactorAlpha, format);
 
 	Vector4f blendedColor;
 
@@ -2207,13 +2446,27 @@
 		blendedColor.y = Float4(0.0f);
 		blendedColor.z = Float4(0.0f);
 		break;
+	case VK_BLEND_OP_MULTIPLY_EXT:
+	case VK_BLEND_OP_SCREEN_EXT:
+	case VK_BLEND_OP_OVERLAY_EXT:
+	case VK_BLEND_OP_DARKEN_EXT:
+	case VK_BLEND_OP_LIGHTEN_EXT:
+	case VK_BLEND_OP_COLORDODGE_EXT:
+	case VK_BLEND_OP_COLORBURN_EXT:
+	case VK_BLEND_OP_HARDLIGHT_EXT:
+	case VK_BLEND_OP_SOFTLIGHT_EXT:
+	case VK_BLEND_OP_DIFFERENCE_EXT:
+	case VK_BLEND_OP_EXCLUSION_EXT:
+	case VK_BLEND_OP_HSL_HUE_EXT:
+	case VK_BLEND_OP_HSL_SATURATION_EXT:
+	case VK_BLEND_OP_HSL_COLOR_EXT:
+	case VK_BLEND_OP_HSL_LUMINOSITY_EXT:
+		blendedColor = computeAdvancedBlendMode(index, sourceColor, destColor, sourceFactor, destFactor);
+		break;
 	default:
 		UNSUPPORTED("VkBlendOp: %d", int(state.blendState[index].blendOperation));
 	}
 
-	blendFactorAlpha(sourceFactor.w, sourceColor.w, destColor.w, state.blendState[index].sourceBlendFactorAlpha, format);
-	blendFactorAlpha(destFactor.w, sourceColor.w, destColor.w, state.blendState[index].destBlendFactorAlpha, format);
-
 	switch(state.blendState[index].blendOperationAlpha)
 	{
 	case VK_BLEND_OP_ADD:
@@ -2240,6 +2493,11 @@
 	case VK_BLEND_OP_ZERO_EXT:
 		blendedColor.w = Float4(0.0f);
 		break;
+	case VK_BLEND_OP_MULTIPLY_EXT:
+		// All of the currently supported advanced blend modes compute the alpha the same way
+		// Use VK_BLEND_OP_MULTIPLY_EXT as a placeholder
+		blendedColor.w = sourceColor.w + destColor.w - (sourceColor.w * destColor.w);
+		break;
 	default:
 		UNSUPPORTED("VkBlendOp: %d", int(state.blendState[index].blendOperationAlpha));
 	}
diff --git a/src/Pipeline/PixelRoutine.hpp b/src/Pipeline/PixelRoutine.hpp
index 3f410e7..ba5f28d 100644
--- a/src/Pipeline/PixelRoutine.hpp
+++ b/src/Pipeline/PixelRoutine.hpp
@@ -77,6 +77,19 @@
 	void blendFactorRGB(Vector4f &blendFactorRGB, const Vector4f &sourceColor, const Vector4f &destColor, VkBlendFactor colorBlendFactor, vk::Format format);
 	void blendFactorAlpha(Float4 &blendFactorAlpha, const Float4 &sourceAlpha, const Float4 &destAlpha, VkBlendFactor alphaBlendFactor, vk::Format format);
 	bool blendFactorCanExceedFormatRange(VkBlendFactor blendFactor, vk::Format format);
+	Vector4f computeAdvancedBlendMode(int index, const Vector4f &src, const Vector4f &dst, const Vector4f &srcFactor, const Vector4f &dstFactor);
+	Float4 blendOpOverlay(Float4 &src, Float4 &dst);
+	Float4 blendOpColorDodge(Float4 &src, Float4 &dst);
+	Float4 blendOpColorBurn(Float4 &src, Float4 &dst);
+	Float4 blendOpHardlight(Float4 &src, Float4 &dst);
+	Float4 blendOpSoftlight(Float4 &src, Float4 &dst);
+	void setLumSat(Vector4f &cbase, Vector4f &csat, Vector4f &clum, Float4 &x, Float4 &y, Float4 &z);
+	void setLum(Vector4f &cbase, Vector4f &clum, Float4 &x, Float4 &y, Float4 &z);
+	Float4 computeLum(Float4 &color, Float4 &lum, Float4 &mincol, Float4 &maxcol, Int4 &negative, Int4 &aboveOne);
+	Float4 maxRGB(Vector4f &c);
+	Float4 minRGB(Vector4f &c);
+	Float4 lumRGB(Vector4f &c);
+	void premultiply(Vector4f &c);
 	void writeStencil(Pointer<Byte> &sBuffer, const Int &x, const Int sMask[4], const Int zMask[4], const Int cMask[4], const SampleSet &samples);
 	void writeDepth(Pointer<Byte> &zBuffer, const Int &x, const Int zMask[4], const SampleSet &samples);
 	void occlusionSampleCount(const Int zMask[4], const Int sMask[4], const SampleSet &samples);
diff --git a/src/Vulkan/VkPhysicalDevice.cpp b/src/Vulkan/VkPhysicalDevice.cpp
index be9e649..fe8f840 100644
--- a/src/Vulkan/VkPhysicalDevice.cpp
+++ b/src/Vulkan/VkPhysicalDevice.cpp
@@ -332,12 +332,17 @@
 	features->depthClipEnable = VK_TRUE;
 }
 
-static void getPhysicalDevicCustomBorderColorFeaturesExt(VkPhysicalDeviceCustomBorderColorFeaturesEXT *features)
+static void getPhysicalDeviceCustomBorderColorFeaturesExt(VkPhysicalDeviceCustomBorderColorFeaturesEXT *features)
 {
 	features->customBorderColors = VK_TRUE;
 	features->customBorderColorWithoutFormat = VK_TRUE;
 }
 
+static void getPhysicalDeviceBlendOperationAdvancedFeaturesExt(VkPhysicalDeviceBlendOperationAdvancedFeaturesEXT *features)
+{
+	features->advancedBlendCoherentOperations = VK_FALSE;
+}
+
 static void getPhysicalDevice4444FormatsFeaturesExt(VkPhysicalDevice4444FormatsFeaturesEXT *features)
 {
 	features->formatA4R4G4B4 = VK_TRUE;
@@ -437,7 +442,10 @@
 			getPhysicalDeviceDepthClipEnableFeaturesExt(reinterpret_cast<VkPhysicalDeviceDepthClipEnableFeaturesEXT *>(curExtension));
 			break;
 		case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT:
-			getPhysicalDevicCustomBorderColorFeaturesExt(reinterpret_cast<VkPhysicalDeviceCustomBorderColorFeaturesEXT *>(curExtension));
+			getPhysicalDeviceCustomBorderColorFeaturesExt(reinterpret_cast<VkPhysicalDeviceCustomBorderColorFeaturesEXT *>(curExtension));
+			break;
+		case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BLEND_OPERATION_ADVANCED_FEATURES_EXT:
+			getPhysicalDeviceBlendOperationAdvancedFeaturesExt(reinterpret_cast<VkPhysicalDeviceBlendOperationAdvancedFeaturesEXT *>(curExtension));
 			break;
 		case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_4444_FORMATS_FEATURES_EXT:
 			getPhysicalDevice4444FormatsFeaturesExt(reinterpret_cast<struct VkPhysicalDevice4444FormatsFeaturesEXT *>(curExtension));
@@ -1034,6 +1042,18 @@
 	properties->maxCustomBorderColorSamplers = MAX_SAMPLER_ALLOCATION_COUNT;
 }
 
+void PhysicalDevice::getProperties(VkPhysicalDeviceBlendOperationAdvancedPropertiesEXT *properties) const
+{
+	// Note: advancedBlendMaxColorAttachments could already support sw::MAX_COLOR_BUFFERS as is,
+	//       but using a value of 1 is enough for ANGLE to implement GL_KHR_blend_equation_advanced
+	properties->advancedBlendMaxColorAttachments = 1;
+	properties->advancedBlendIndependentBlend = VK_FALSE;
+	properties->advancedBlendNonPremultipliedSrcColor = VK_FALSE;
+	properties->advancedBlendNonPremultipliedDstColor = VK_FALSE;
+	properties->advancedBlendCorrelatedOverlap = VK_FALSE;
+	properties->advancedBlendAllOperations = VK_FALSE;
+}
+
 template<typename T>
 static void getSamplerFilterMinmaxProperties(T *properties)
 {
@@ -1121,6 +1141,7 @@
 InstantiateHasExtendedFeatures(VkPhysicalDeviceVulkan11Features);
 InstantiateHasExtendedFeatures(VkPhysicalDeviceVulkan12Features);
 InstantiateHasExtendedFeatures(VkPhysicalDeviceDepthClipEnableFeaturesEXT);
+InstantiateHasExtendedFeatures(VkPhysicalDeviceBlendOperationAdvancedFeaturesEXT);
 #undef InstantiateHasExtendedFeatures
 
 void PhysicalDevice::GetFormatProperties(Format format, VkFormatProperties *pFormatProperties)
diff --git a/src/Vulkan/VkPhysicalDevice.hpp b/src/Vulkan/VkPhysicalDevice.hpp
index 068011a..080d1af 100644
--- a/src/Vulkan/VkPhysicalDevice.hpp
+++ b/src/Vulkan/VkPhysicalDevice.hpp
@@ -68,6 +68,7 @@
 	void getProperties(VkPhysicalDeviceDescriptorIndexingProperties *properties) const;
 	void getProperties(VkPhysicalDeviceDepthStencilResolveProperties *properties) const;
 	void getProperties(VkPhysicalDeviceCustomBorderColorPropertiesEXT *properties) const;
+	void getProperties(VkPhysicalDeviceBlendOperationAdvancedPropertiesEXT *properties) const;
 	void getProperties(VkPhysicalDeviceVulkan11Properties *properties) const;
 
 	static void GetFormatProperties(Format format, VkFormatProperties *pFormatProperties);
diff --git a/src/Vulkan/libVulkan.cpp b/src/Vulkan/libVulkan.cpp
index 2017350..5faaf75 100644
--- a/src/Vulkan/libVulkan.cpp
+++ b/src/Vulkan/libVulkan.cpp
@@ -371,6 +371,8 @@
 	{ { VK_EXT_IMAGE_ROBUSTNESS_EXTENSION_NAME, VK_EXT_IMAGE_ROBUSTNESS_SPEC_VERSION } },
 	// Useful for D3D emulation
 	{ { VK_EXT_4444_FORMATS_EXTENSION_NAME, VK_EXT_4444_FORMATS_SPEC_VERSION } },
+	// Used by ANGLE to support GL_KHR_blend_equation_advanced
+	{ { VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME, VK_EXT_BLEND_OPERATION_ADVANCED_SPEC_VERSION } },
 #ifndef __ANDROID__
 	// We fully support the KHR_swapchain v70 additions, so just track the spec version.
 	{ { VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_KHR_SWAPCHAIN_SPEC_VERSION } },
@@ -917,6 +919,16 @@
 				}
 			}
 			break;
+		case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BLEND_OPERATION_ADVANCED_FEATURES_EXT:
+			{
+				const auto *blendOpFeatures = reinterpret_cast<const VkPhysicalDeviceBlendOperationAdvancedFeaturesEXT *>(extensionCreateInfo);
+				bool hasFeatures = vk::Cast(physicalDevice)->hasExtendedFeatures(blendOpFeatures);
+				if(!hasFeatures)
+				{
+					return VK_ERROR_FEATURE_NOT_PRESENT;
+				}
+			}
+			break;
 		// These structs are supported, but no behavior changes based on their feature bools
 		case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_UNIFORM_BUFFER_STANDARD_LAYOUT_FEATURES:
 		case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SUBGROUP_EXTENDED_TYPES_FEATURES:
@@ -3253,6 +3265,12 @@
 				vk::Cast(physicalDevice)->getProperties(properties);
 			}
 			break;
+		case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BLEND_OPERATION_ADVANCED_PROPERTIES_EXT:
+			{
+				auto properties = reinterpret_cast<VkPhysicalDeviceBlendOperationAdvancedPropertiesEXT *>(extensionProperties);
+				vk::Cast(physicalDevice)->getProperties(properties);
+			}
+			break;
 		case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_INTEGER_DOT_PRODUCT_PROPERTIES_KHR:
 			break;
 		default: