panfrost: Rework fixed-function blending

The fixed-function blend logic uses the following equation: A + B x C.
A, B and C are configurable and can be complemented with negation (for
A and B) or inversion (for C) modifiers. Let's rework the blending
code to take that into account.

Note that we need to update the checksum of a few traces because the
equations we use have changed, leading to small deviations on the
final images. Indeed, there are several valid options for a given GL
blend equation, but the operand selection probably has an impact on the
rounding, leading to those mismatch.

Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
Reviewed-by: Alyssa Rosenzweig <alyssa.rosenzweig@collabora.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/6980>
diff --git a/.gitlab-ci/traces-panfrost.yml b/.gitlab-ci/traces-panfrost.yml
index 6aca928..b432397 100644
--- a/.gitlab-ci/traces-panfrost.yml
+++ b/.gitlab-ci/traces-panfrost.yml
@@ -45,9 +45,9 @@
   - path: gputest/triangle.trace
     expectations:
       - device: gl-panfrost-t860
-        checksum: 6def0c34ade7d4ec930b45d0eef6e46a
+        checksum: 75c5a2e261d576d115a659f6cd52a90d
       - device: gl-panfrost-t760
-        checksum: 6def0c34ade7d4ec930b45d0eef6e46a
+        checksum: 75c5a2e261d576d115a659f6cd52a90d
   - path: humus/Portals.trace
     expectations:
       - device: gl-panfrost-t860
@@ -190,11 +190,11 @@
   - path: gputest/pixmark-julia-fp32.trace
     expectations:
       - device: gl-panfrost-t860
-        checksum: 03c268b0afaf341b4d8226cc6892273a
+        checksum: d85b6967b7c768f28395c5c5dfbcfe3e
   - path: gputest/plot3d.trace
     expectations:
       - device: gl-panfrost-t860
-        checksum: 522afab0fd4bbabbc2f78735646069d8
+        checksum: 991efbfd046f70e1fd965d3983bb2965
   - path: humus/AmbientAperture.trace
     expectations:
       - device: gl-panfrost-t860
diff --git a/src/gallium/drivers/panfrost/pan_blend.h b/src/gallium/drivers/panfrost/pan_blend.h
index 785d307..300bb8a 100644
--- a/src/gallium/drivers/panfrost/pan_blend.h
+++ b/src/gallium/drivers/panfrost/pan_blend.h
@@ -68,7 +68,7 @@
 };
 
 struct panfrost_blend_equation_final {
-        struct mali_blend_equation_packed equation;
+        struct MALI_BLEND_EQUATION equation;
         float constant;
 };
 
@@ -77,7 +77,7 @@
          * fixed-function configuration for this blend state */
 
         bool has_fixed_function;
-        struct mali_blend_equation_packed equation;
+        struct MALI_BLEND_EQUATION equation;
 
         /* Mask of blend color components read */
         unsigned constant_mask;
diff --git a/src/gallium/drivers/panfrost/pan_blend_cso.c b/src/gallium/drivers/panfrost/pan_blend_cso.c
index 43edddb..e5d27037 100644
--- a/src/gallium/drivers/panfrost/pan_blend_cso.c
+++ b/src/gallium/drivers/panfrost/pan_blend_cso.c
@@ -129,8 +129,17 @@
                                         &rt->equation,
                                         &rt->constant_mask);
 
-                if (rt->has_fixed_function)
-                        rt->opaque = (rt->equation.opaque[0] == 0xf0122122);
+                if (rt->has_fixed_function) {
+                        rt->opaque = pipe.rgb_src_factor == PIPE_BLENDFACTOR_ONE &&
+                                     pipe.rgb_dst_factor == PIPE_BLENDFACTOR_ZERO &&
+                                     (pipe.rgb_func == PIPE_BLEND_ADD ||
+                                      pipe.rgb_func == PIPE_BLEND_SUBTRACT) &&
+                                     pipe.alpha_src_factor == PIPE_BLENDFACTOR_ONE &&
+                                     pipe.alpha_dst_factor == PIPE_BLENDFACTOR_ZERO &&
+                                     (pipe.alpha_func == PIPE_BLEND_ADD ||
+                                      pipe.alpha_func == PIPE_BLEND_SUBTRACT) &&
+                                     pipe.colormask == 0xf;
+                }
 
                 rt->load_dest = util_blend_uses_dest(pipe)
                         || pipe.colormask != 0xF;
diff --git a/src/gallium/drivers/panfrost/pan_blending.c b/src/gallium/drivers/panfrost/pan_blending.c
index 5a94975..fda516c 100644
--- a/src/gallium/drivers/panfrost/pan_blending.c
+++ b/src/gallium/drivers/panfrost/pan_blending.c
@@ -28,76 +28,7 @@
 #include "gallium/auxiliary/util/u_blend.h"
 #include "util/format/u_format.h"
 
-/*
- * Implements fixed-function blending on Midgard.
- *
- * Midgard splits blending into a fixed-function fast path and a programmable
- * slow path. The fixed function blending architecture is based on "dominant"
- * blend factors. Blending is encoded separately (but identically) between RGB
- * and alpha functions.
- *
- * Essentially, for a given blending operation, there is a single dominant
- * factor. The following dominant factors are possible:
- *
- * 	- zero
- * 	- source color
- * 	- destination color
- * 	- source alpha
- * 	- destination alpha
- * 	- constant float
- *
- * Further, a dominant factor's arithmetic compliment could be used. For
- * instance, to encode GL_ONE_MINUS_SOURCE_ALPHA, the dominant factor would be
- * MALI_DOMINANT_SRC_ALPHA with the complement_dominant bit set.
- *
- * A single constant float can be passed to the fixed-function hardware,
- * allowing CONSTANT_ALPHA support. Further, if all components of the constant
- * glBlendColor are identical, CONSTANT_COLOR can be implemented with the
- * constant float mode. If the components differ, programmable blending is
- * required.
- *
- * The nondominant factor can be either:
- *
- * 	- the same as the dominant factor (MALI_BLEND_NON_MIRROR)
- * 	- zero (MALI_BLEND_NON_ZERO)
- *
- * Exactly one of the blend operation's source or destination can be used as
- * the dominant factor; this is selected by the
- * MALI_BLEND_DOM_SOURCE/DESTINATION flag.
- *
- * By default, all blending follows the standard OpenGL addition equation:
- *
- * 	out = source_value * source_factor + destination_value * destination_factor
- *
- * By setting the negate_source or negate_dest bits, other blend functions can
- * be created. For instance, for SUBTRACT mode, set the "negate destination"
- * flag, and similarly for REVERSE_SUBTRACT with "negate source".
- *
- * Finally, there is a "clip modifier" controlling the final blending
- * behaviour, allowing for the following modes:
- *
- * 	- normal
- * 	- force source factor to one (MALI_BLEND_MODE_SOURCE_ONE)
- * 	- force destination factor to one (MALI_BLEND_MODE_DEST_ONE)
- *
- * The clipping flags can be used to encode blend modes where the nondominant
- * factor is ONE.
- *
- * As an example putting it all together, to encode the following blend state:
- *
- * 	glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
- * 	glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_ONE);
- *
- * We need the following configuration:
- *
- * 	- negate source (for REVERSE_SUBTRACT)
- * 	- dominant factor "source alpha"
- * 		- complement dominant
- * 		- source dominant
- * 	- force destination to ONE
- *
- * The following routines implement this fixed function blending encoding
- */
+/* Implements fixed-function blending on Midgard. */
 
 /* Not all formats can be blended by fixed-function hardware */
 
@@ -161,54 +92,6 @@
         return (complement == -1) ? factor : complement;
 }
 
-
-/* Attempt to find the dominant factor given a particular factor, complementing
- * as necessary */
-
-static bool
-panfrost_make_dominant_factor(unsigned src_factor, enum mali_dominant_factor *factor)
-{
-        switch (src_factor) {
-        case PIPE_BLENDFACTOR_SRC_COLOR:
-        case PIPE_BLENDFACTOR_INV_SRC_COLOR:
-                *factor = MALI_DOMINANT_SRC_COLOR;
-                break;
-
-        case PIPE_BLENDFACTOR_SRC_ALPHA:
-        case PIPE_BLENDFACTOR_INV_SRC_ALPHA:
-                *factor = MALI_DOMINANT_SRC_ALPHA;
-                break;
-
-        case PIPE_BLENDFACTOR_DST_COLOR:
-        case PIPE_BLENDFACTOR_INV_DST_COLOR:
-                *factor = MALI_DOMINANT_DST_COLOR;
-                break;
-
-        case PIPE_BLENDFACTOR_DST_ALPHA:
-        case PIPE_BLENDFACTOR_INV_DST_ALPHA:
-                *factor = MALI_DOMINANT_DST_ALPHA;
-                break;
-
-        case PIPE_BLENDFACTOR_ONE:
-        case PIPE_BLENDFACTOR_ZERO:
-                *factor = MALI_DOMINANT_ZERO;
-                break;
-
-        case PIPE_BLENDFACTOR_CONST_ALPHA:
-        case PIPE_BLENDFACTOR_INV_CONST_ALPHA:
-        case PIPE_BLENDFACTOR_CONST_COLOR:
-        case PIPE_BLENDFACTOR_INV_CONST_COLOR:
-                *factor = MALI_DOMINANT_CONSTANT;
-                break;
-
-        default:
-                /* Fancy blend modes not supported */
-                return false;
-        }
-
-        return true;
-}
-
 /* Check if this is a special edge case blend factor, which may require the use
  * of clip modifiers */
 
@@ -218,98 +101,154 @@
         return factor == PIPE_BLENDFACTOR_ONE || factor == PIPE_BLENDFACTOR_ZERO;
 }
 
-/* Perform the actual fixed function encoding. Encode the function with negate
- * bits. Check for various cases to work out the dominant/nondominant split and
- * accompanying flags. */
+static bool
+factor_is_supported(unsigned factor)
+{
+        return factor != PIPE_BLENDFACTOR_SRC_ALPHA_SATURATE &&
+               factor != PIPE_BLENDFACTOR_SRC1_COLOR &&
+               factor != PIPE_BLENDFACTOR_SRC1_ALPHA &&
+               factor != PIPE_BLENDFACTOR_INV_SRC1_COLOR &&
+               factor != PIPE_BLENDFACTOR_INV_SRC1_ALPHA;
+}
 
 static bool
-panfrost_make_fixed_blend_part(unsigned func, unsigned src_factor, unsigned dst_factor, unsigned *out)
+can_use_fixed_function_blend(unsigned blend_func,
+                             unsigned src_factor,
+                             unsigned dest_factor)
 {
-        struct mali_blend_mode part = { 0 };
+        if (blend_func != PIPE_BLEND_ADD &&
+            blend_func != PIPE_BLEND_SUBTRACT &&
+            blend_func != PIPE_BLEND_REVERSE_SUBTRACT)
+                return false;
 
-        /* Make sure that the blend function is representible */
+        if (!factor_is_supported(src_factor) ||
+            !factor_is_supported(dest_factor))
+                return false;
 
-        switch (func) {
-        case PIPE_BLEND_ADD:
+        if (src_factor != dest_factor &&
+            src_factor != complement_factor(dest_factor) &&
+            complement_factor(src_factor) != dest_factor &&
+            !is_edge_blendfactor(src_factor) &&
+            !is_edge_blendfactor(dest_factor))
+                return false;
+
+        return true;
+}
+
+static void to_c_factor(unsigned factor, struct MALI_BLEND_FUNCTION *function)
+{
+        if (complement_factor(factor) >= 0)
+                function->invert_c = true;
+
+        switch (uncomplement_factor(factor)) {
+        case PIPE_BLENDFACTOR_ONE:
+        case PIPE_BLENDFACTOR_ZERO:
+                function->invert_c = factor == PIPE_BLENDFACTOR_ONE;
+                function->c = MALI_BLEND_OPERAND_C_ZERO;
                 break;
 
-        /* TODO: Reenable subtraction modes when those fixed */
-        case PIPE_BLEND_SUBTRACT:
-        case PIPE_BLEND_REVERSE_SUBTRACT:
+        case PIPE_BLENDFACTOR_SRC_ALPHA:
+                function->c = MALI_BLEND_OPERAND_C_SRC_ALPHA;
+                break;
+
+        case PIPE_BLENDFACTOR_DST_ALPHA:
+                function->c = MALI_BLEND_OPERAND_C_DEST_ALPHA;
+                break;
+
+        case PIPE_BLENDFACTOR_SRC_COLOR:
+                function->c = MALI_BLEND_OPERAND_C_SRC;
+                break;
+
+        case PIPE_BLENDFACTOR_DST_COLOR:
+                function->c = MALI_BLEND_OPERAND_C_DEST;
+                break;
+
+        case PIPE_BLENDFACTOR_CONST_COLOR:
+        case PIPE_BLENDFACTOR_CONST_ALPHA:
+                function->c = MALI_BLEND_OPERAND_C_CONSTANT;
+                break;
         default:
-                return false;
+                unreachable("Invalid blend factor");
         }
 
-        part.clip_modifier = MALI_BLEND_MOD_NORMAL;
+}
 
-        /* Decide which is dominant, source or destination. If one is an edge
-         * case, use the other as a factor. If they're the same, it doesn't
-         * matter; we just mirror. If they're different non-edge-cases, you
-         * need a blend shader (don't do that). */
+static bool
+to_panfrost_function(unsigned blend_func,
+                     unsigned src_factor,
+                     unsigned dest_factor,
+                     struct MALI_BLEND_FUNCTION *function)
+{
+        if (!can_use_fixed_function_blend(blend_func, src_factor, dest_factor))
+                return false;
 
-        if (is_edge_blendfactor(dst_factor)) {
-                part.dominant = MALI_BLEND_DOM_SOURCE;
-                part.nondominant_mode = MALI_BLEND_NON_ZERO;
+        if (src_factor == PIPE_BLENDFACTOR_ZERO) {
+                function->a = MALI_BLEND_OPERAND_A_ZERO;
+                function->b = MALI_BLEND_OPERAND_B_DEST;
+                if (blend_func == PIPE_BLEND_SUBTRACT)
+                        function->negate_b = true;
+                to_c_factor(dest_factor, function);
+        } else if (src_factor == PIPE_BLENDFACTOR_ONE) {
+                function->a = MALI_BLEND_OPERAND_A_SRC;
+                function->b = MALI_BLEND_OPERAND_B_DEST;
+                if (blend_func == PIPE_BLEND_SUBTRACT)
+                        function->negate_b = true;
+                else if (blend_func == PIPE_BLEND_REVERSE_SUBTRACT)
+                        function->negate_a = true;
+                to_c_factor(dest_factor, function);
+        } else if (dest_factor == PIPE_BLENDFACTOR_ZERO) {
+                function->a = MALI_BLEND_OPERAND_A_ZERO;
+                function->b = MALI_BLEND_OPERAND_B_SRC;
+                if (blend_func == PIPE_BLEND_REVERSE_SUBTRACT)
+                        function->negate_b = true;
+                to_c_factor(src_factor, function);
+        } else if (dest_factor == PIPE_BLENDFACTOR_ONE) {
+                function->a = MALI_BLEND_OPERAND_A_DEST;
+                function->b = MALI_BLEND_OPERAND_B_SRC;
+                if (blend_func == PIPE_BLEND_SUBTRACT)
+                        function->negate_a = true;
+                else if (blend_func == PIPE_BLEND_REVERSE_SUBTRACT)
+                        function->negate_b = true;
+                to_c_factor(src_factor, function);
+        } else if (src_factor == dest_factor) {
+                function->a = MALI_BLEND_OPERAND_A_ZERO;
+                to_c_factor(src_factor, function);
 
-                if (dst_factor == PIPE_BLENDFACTOR_ONE)
-                        part.clip_modifier = MALI_BLEND_MOD_DEST_ONE;
-        } else if (is_edge_blendfactor(src_factor)) {
-                part.dominant = MALI_BLEND_DOM_DESTINATION;
-                part.nondominant_mode = MALI_BLEND_NON_ZERO;
-
-                if (src_factor == PIPE_BLENDFACTOR_ONE)
-                        part.clip_modifier = MALI_BLEND_MOD_SOURCE_ONE;
-        } else if (src_factor == dst_factor) {
-                /* XXX: Why? */
-                part.dominant = func == PIPE_BLEND_ADD ?
-                                MALI_BLEND_DOM_DESTINATION : MALI_BLEND_DOM_SOURCE;
-
-                part.nondominant_mode = MALI_BLEND_NON_MIRROR;
-        } else if (src_factor == complement_factor(dst_factor)) {
-                /* TODO: How does this work exactly? */
-                part.dominant = MALI_BLEND_DOM_SOURCE;
-                part.nondominant_mode = MALI_BLEND_NON_MIRROR;
-                part.clip_modifier = MALI_BLEND_MOD_DEST_ONE;
-
-                /* The complement is handled by the clip modifier, don't set a
-                 * complement flag */
-
-                dst_factor = src_factor;
-        } else if (dst_factor == complement_factor(src_factor)) {
-                part.dominant = MALI_BLEND_DOM_SOURCE;
-                part.nondominant_mode = MALI_BLEND_NON_MIRROR;
-                part.clip_modifier = MALI_BLEND_MOD_SOURCE_ONE;
-
-                src_factor = dst_factor;
+                switch (blend_func) {
+                case PIPE_BLEND_ADD:
+                        function->b = MALI_BLEND_OPERAND_B_SRC_PLUS_DEST;
+                        break;
+                case PIPE_BLEND_REVERSE_SUBTRACT:
+                        function->negate_b = true;
+                        /* fall-through */
+                case PIPE_BLEND_SUBTRACT:
+                        function->b = MALI_BLEND_OPERAND_B_SRC_MINUS_DEST;
+                        break;
+                default:
+                        unreachable("Invalid blend function");
+                }
         } else {
-                return false;
+                assert(src_factor == complement_factor(dest_factor) ||
+                       complement_factor(src_factor) == dest_factor);
+
+                function->a = MALI_BLEND_OPERAND_A_DEST;
+                to_c_factor(src_factor, function);
+
+                switch (blend_func) {
+                case PIPE_BLEND_ADD:
+                        function->b = MALI_BLEND_OPERAND_B_SRC_MINUS_DEST;
+                        break;
+                case PIPE_BLEND_REVERSE_SUBTRACT:
+                        function->b = MALI_BLEND_OPERAND_B_SRC_PLUS_DEST;
+                        function->negate_b = true;
+                        break;
+                case PIPE_BLEND_SUBTRACT:
+                        function->b = MALI_BLEND_OPERAND_B_SRC_PLUS_DEST;
+                        function->negate_a = true;
+                        break;
+                }
         }
 
-        unsigned in_dominant_factor =
-                part.dominant == MALI_BLEND_DOM_SOURCE ? src_factor : dst_factor;
-
-        if (part.clip_modifier == MALI_BLEND_MOD_NORMAL && in_dominant_factor == PIPE_BLENDFACTOR_ONE) {
-                part.clip_modifier = part.dominant == MALI_BLEND_DOM_SOURCE ? MALI_BLEND_MOD_SOURCE_ONE : MALI_BLEND_MOD_DEST_ONE;
-                in_dominant_factor = PIPE_BLENDFACTOR_ZERO;
-        }
-
-        enum mali_dominant_factor dominant_factor;
-
-        if (!panfrost_make_dominant_factor(in_dominant_factor, &dominant_factor))
-                return false;
-
-        part.dominant_factor = dominant_factor;
-        part.complement_dominant = util_blend_factor_is_inverted(in_dominant_factor);
-
-        /* It's not clear what this does, but fixes some ADD blending tests.
-         * More research is needed XXX */
-
-        if ((part.clip_modifier == MALI_BLEND_MOD_SOURCE_ONE) && (part.dominant == MALI_BLEND_DOM_SOURCE))
-                part.negate_dest = true;
-
-        /* Write out mode */
-        memcpy(out, &part, sizeof(part));
-
         return true;
 }
 
@@ -340,19 +279,20 @@
  */
 
 bool
-panfrost_make_fixed_blend_mode(
-        struct pipe_rt_blend_state blend,
-        struct mali_blend_equation_packed *out,
-        unsigned *constant_mask)
+panfrost_make_fixed_blend_mode(const struct pipe_rt_blend_state blend,
+                               struct MALI_BLEND_EQUATION *equation,
+                               unsigned *constant_mask)
 {
         /* If no blending is enabled, default back on `replace` mode */
 
         if (!blend.blend_enable) {
-                pan_pack(out, BLEND_EQUATION, cfg) {
-                        cfg.color_mask = blend.colormask;
-                        cfg.rgb_mode = cfg.alpha_mode = 0x122;
-                }
-
+                equation->color_mask = blend.colormask;
+                equation->rgb.a = MALI_BLEND_OPERAND_A_SRC;
+                equation->rgb.b = MALI_BLEND_OPERAND_B_SRC;
+                equation->rgb.c = MALI_BLEND_OPERAND_C_ZERO;
+                equation->alpha.a = MALI_BLEND_OPERAND_A_SRC;
+                equation->alpha.b = MALI_BLEND_OPERAND_B_SRC;
+                equation->alpha.c = MALI_BLEND_OPERAND_C_ZERO;
                 return true;
         }
 
@@ -368,25 +308,16 @@
         *constant_mask = panfrost_constant_mask(factors, ARRAY_SIZE(factors));
 
         /* Try to compile the actual fixed-function blend */
-
-        unsigned rgb_mode = 0;
-        unsigned alpha_mode = 0;
-
-        if (!panfrost_make_fixed_blend_part(
-                    blend.rgb_func, blend.rgb_src_factor, blend.rgb_dst_factor,
-                    &rgb_mode))
+        if (!to_panfrost_function(blend.rgb_func, blend.rgb_src_factor,
+                                  blend.rgb_dst_factor,
+                                  &equation->rgb))
                 return false;
 
-        if (!panfrost_make_fixed_blend_part(
-                    blend.alpha_func, blend.alpha_src_factor, blend.alpha_dst_factor,
-                    &alpha_mode))
+        if (!to_panfrost_function(blend.alpha_func, blend.alpha_src_factor,
+                                  blend.alpha_dst_factor,
+                                  &equation->alpha))
                 return false;
 
-        pan_pack(out, BLEND_EQUATION, cfg) {
-                cfg.color_mask = blend.colormask;
-                cfg.rgb_mode = rgb_mode;
-                cfg.alpha_mode = alpha_mode;
-        }
-
+        equation->color_mask = blend.colormask;
         return true;
 }
diff --git a/src/gallium/drivers/panfrost/pan_blending.h b/src/gallium/drivers/panfrost/pan_blending.h
index c466e8d..d34e472 100644
--- a/src/gallium/drivers/panfrost/pan_blending.h
+++ b/src/gallium/drivers/panfrost/pan_blending.h
@@ -28,14 +28,14 @@
 #include "pipe/p_state.h"
 #include "pipe/p_defines.h"
 #include <midgard_pack.h>
+#include "pan_blend.h"
 
 struct panfrost_blend_state;
 
 bool
-panfrost_make_fixed_blend_mode(
-        const struct pipe_rt_blend_state blend,
-        struct mali_blend_equation_packed *out,
-        unsigned *constant_mask);
+panfrost_make_fixed_blend_mode(const struct pipe_rt_blend_state blend,
+                               struct MALI_BLEND_EQUATION *equation,
+                               unsigned *constant_mask);
 
 bool
 panfrost_can_fixed_blend(enum pipe_format format);
diff --git a/src/gallium/drivers/panfrost/pan_cmdstream.c b/src/gallium/drivers/panfrost/pan_cmdstream.c
index 1cbfeb8..fd0b6ad 100644
--- a/src/gallium/drivers/panfrost/pan_cmdstream.c
+++ b/src/gallium/drivers/panfrost/pan_cmdstream.c
@@ -307,8 +307,12 @@
                         enum pipe_format format = batch->key.cbufs[i]->format;
                         const struct util_format_description *format_desc;
                         format_desc = util_format_description(format);
+                        struct mali_blend_equation_packed peq;
 
-                        brts[i].equation = blend[i].equation.equation;
+                        pan_pack(&peq, BLEND_EQUATION, cfg) {
+                                cfg = blend[i].equation.equation;
+                        }
+                        brts[i].equation = peq;
 
                         /* TODO: this is a bit more complicated */
                         brts[i].constant = blend[i].equation.constant;
@@ -338,7 +342,13 @@
         if (rt_count == 0) {
                 /* Disable blending for depth-only */
                 pan_pack(rts, MIDGARD_BLEND, cfg) {
-                        cfg.equation = 0xf0122122; /* Replace */
+                        cfg.equation.color_mask = 0xf;
+                        cfg.equation.rgb.a = MALI_BLEND_OPERAND_A_SRC;
+                        cfg.equation.rgb.b = MALI_BLEND_OPERAND_B_SRC;
+                        cfg.equation.rgb.c = MALI_BLEND_OPERAND_C_ZERO;
+                        cfg.equation.alpha.a = MALI_BLEND_OPERAND_A_SRC;
+                        cfg.equation.alpha.b = MALI_BLEND_OPERAND_B_SRC;
+                        cfg.equation.alpha.c = MALI_BLEND_OPERAND_C_ZERO;
                 }
                 return;
         }
@@ -357,7 +367,7 @@
                         if (blend[i].is_shader) {
                                 cfg.shader = blend[i].shader.gpu | blend[i].shader.first_tag;
                         } else {
-                                cfg.equation = blend[i].equation.equation.opaque[0];
+                                cfg.equation = blend[i].equation.equation;
                                 cfg.constant = blend[i].equation.constant;
                         }
                 }
@@ -460,7 +470,7 @@
                         state->sfbd_blend_shader = blend[0].shader.gpu |
                                                    blend[0].shader.first_tag;
                 } else {
-                        state->sfbd_blend_equation = blend[0].equation.equation.opaque[0];
+                        state->sfbd_blend_equation = blend[0].equation.equation;
                         state->sfbd_blend_constant = blend[0].equation.constant;
                 }
         } else {
diff --git a/src/panfrost/include/panfrost-job.h b/src/panfrost/include/panfrost-job.h
index 1d32a35..1482ce7 100644
--- a/src/panfrost/include/panfrost-job.h
+++ b/src/panfrost/include/panfrost-job.h
@@ -38,51 +38,6 @@
 typedef uint64_t u64;
 typedef uint64_t mali_ptr;
 
-enum mali_nondominant_mode {
-        MALI_BLEND_NON_MIRROR = 0,
-        MALI_BLEND_NON_ZERO = 1
-};
-
-enum mali_dominant_blend {
-        MALI_BLEND_DOM_SOURCE = 0,
-        MALI_BLEND_DOM_DESTINATION  = 1
-};
-
-enum mali_dominant_factor {
-        MALI_DOMINANT_UNK0 = 0,
-        MALI_DOMINANT_ZERO = 1,
-        MALI_DOMINANT_SRC_COLOR = 2,
-        MALI_DOMINANT_DST_COLOR = 3,
-        MALI_DOMINANT_UNK4 = 4,
-        MALI_DOMINANT_SRC_ALPHA = 5,
-        MALI_DOMINANT_DST_ALPHA = 6,
-        MALI_DOMINANT_CONSTANT = 7,
-};
-
-enum mali_blend_modifier {
-        MALI_BLEND_MOD_UNK0 = 0,
-        MALI_BLEND_MOD_NORMAL = 1,
-        MALI_BLEND_MOD_SOURCE_ONE = 2,
-        MALI_BLEND_MOD_DEST_ONE = 3,
-};
-
-struct mali_blend_mode {
-        enum mali_blend_modifier clip_modifier : 2;
-        unsigned unused_0 : 1;
-        unsigned negate_source : 1;
-
-        enum mali_dominant_blend dominant : 1;
-
-        enum mali_nondominant_mode nondominant_mode : 1;
-
-        unsigned unused_1 : 1;
-
-        unsigned negate_dest : 1;
-
-        enum mali_dominant_factor dominant_factor : 3;
-        unsigned complement_dominant : 1;
-} __attribute__((packed));
-
 /* Compressed per-pixel formats. Each of these formats expands to one to four
  * floating-point or integer numbers, as defined by the OpenGL specification.
  * There are various places in OpenGL where the user can specify a compressed
diff --git a/src/panfrost/lib/decode.c b/src/panfrost/lib/decode.c
index f1f3db8..e6d35b6 100644
--- a/src/panfrost/lib/decode.c
+++ b/src/panfrost/lib/decode.c
@@ -1185,15 +1185,19 @@
 
                 if (!is_bifrost) {
                         /* TODO: Blend shaders routing/disasm */
-                        union midgard_blend blend;
+                        pandecode_log("SFBD Blend:\n");
+                        pandecode_indent++;
                         if (state.multisample_misc.sfbd_blend_shader) {
-                                blend.shader = state.sfbd_blend_shader;
+                                pandecode_shader_address("Shader", state.sfbd_blend_shader);
                         } else {
-                                blend.equation.opaque[0] = state.sfbd_blend_equation;
-                                blend.constant = state.sfbd_blend_constant;
+                                DUMP_UNPACKED(BLEND_EQUATION, state.sfbd_blend_equation, "Equation:\n");
+                                pandecode_prop("Constant = %f", state.sfbd_blend_constant);
                         }
-                        mali_ptr shader = pandecode_midgard_blend(&blend, state.multisample_misc.sfbd_blend_shader);
-                        if (shader & ~0xF)
+                        pandecode_indent--;
+                        pandecode_log("\n");
+
+                        mali_ptr shader = state.sfbd_blend_shader & ~0xF;
+                        if (state.multisample_misc.sfbd_blend_shader && shader)
                                 pandecode_blend_shader_disassemble(shader, job_no, job_type, false, gpu_id);
                 }
                 pandecode_indent--;
diff --git a/src/panfrost/lib/midgard.xml b/src/panfrost/lib/midgard.xml
index 202327f..a978d0a 100644
--- a/src/panfrost/lib/midgard.xml
+++ b/src/panfrost/lib/midgard.xml
@@ -285,10 +285,43 @@
     <field name="Divisor" size="32" start="3:0" type="uint"/>
   </struct>
 
+  <enum name="Blend Operand A">
+    <value name="Zero" value="1"/>
+    <value name="Src" value="2"/>
+    <value name="Dest" value="3"/>
+  </enum>
+
+  <enum name="Blend Operand B">
+    <value name="Src Minus Dest" value="0"/>
+    <value name="Src Plus Dest" value="1"/>
+    <value name="Src" value="2"/>
+    <value name="Dest" value="3"/>
+  </enum>
+
+  <enum name="Blend Operand C">
+    <value name="Zero" value="1"/>
+    <value name="Src" value="2"/>
+    <value name="Dest" value="3"/>
+    <value name="Src x 2" value="4"/>
+    <value name="Src Alpha" value="5"/>
+    <value name="Dest Alpha" value="6"/>
+    <value name="Constant" value="7"/>
+  </enum>
+
+  <struct name="Blend Function" no-direct-packing="true">
+    <!-- Blend equation: A + (B * C) -->
+    <field name="A" size="2" start="0" type="Blend Operand A"/>
+    <field name="Negate A" size="1" start="3" type="bool"/>
+    <field name="B" size="2" start="4" type="Blend Operand B"/>
+    <field name="Negate B" size="1" start="7" type="bool"/>
+    <field name="C" size="3" start="8" type="Blend Operand C"/>
+    <field name="Invert C" size="1" start="11" type="bool"/>
+  </struct>
+
   <struct name="Blend Equation" size="1">
-    <field name="RGB Mode" size="12" start="0" type="uint"/>
-    <field name="Alpha Mode" size="12" start="12" type="uint"/>
-    <field name="Color mask" size="4" start="28" type="uint" default="15"/>
+    <field name="RGB" size="12" start="0:0" type="Blend Function"/>
+    <field name="Alpha" size="12" start="0:12" type="Blend Function"/>
+    <field name="Color Mask" size="4" start="0:28" type="uint"/>
   </struct>
 
   <struct name="Blend Flags" size="1">
@@ -301,7 +334,7 @@
 
   <struct name="Midgard Blend" size="4">
     <field name="Flags" size="32" start="0:0" type="Blend Flags"/>
-    <field name="Equation" size="32" start="2:0" type="uint"/> <!-- XXX -->
+    <field name="Equation" size="32" start="2:0" type="Blend Equation"/>
     <field name="Constant" size="32" start="3:0" type="float"/>
     <field name="Shader" size="64" start="2:0" type="address"/>
   </struct>
@@ -553,7 +586,7 @@
     <field name="Preload" size="32" start="12:0" type="Preload"/>
     <field name="Thread Balancing" size="16" start="13:0" type="uint"/>
     <field name="SFBD Blend Shader" size="64" start="14:0" type="address"/>
-    <field name="SFBD Blend Equation" size="32" start="14:0" type="uint"/>
+    <field name="SFBD Blend Equation" size="32" start="14:0" type="Blend Equation"/>
     <field name="SFBD Blend Constant" size="32" start="15:0" type="float"/>
   </struct>
 
diff --git a/src/panfrost/lib/pan_blit.c b/src/panfrost/lib/pan_blit.c
index b068e02..8f30b05 100644
--- a/src/panfrost/lib/pan_blit.c
+++ b/src/panfrost/lib/pan_blit.c
@@ -207,16 +207,6 @@
                 cfg.format = (MALI_CHANNEL_R << 0) | (MALI_CHANNEL_G << 3) | (MALI_RGBA32F << 12);
         }
 
-        struct mali_blend_equation_packed eq;
-
-        pan_pack(&eq, BLEND_EQUATION, cfg) {
-                cfg.rgb_mode = 0x122;
-                cfg.alpha_mode = 0x122;
-
-                if (loc < FRAG_RESULT_DATA0)
-                        cfg.color_mask = 0x0;
-        }
-
         /* Determine the sampler type needed. Stencil is always sampled as
          * UINT. Pure (U)INT is always (U)INT. Everything else is FLOAT. */
 
@@ -271,7 +261,15 @@
                         if (cfg.multisample_misc.sfbd_blend_shader) {
                                 cfg.sfbd_blend_shader = blend_shader;
                         } else {
-                                cfg.sfbd_blend_equation = eq.opaque[0];
+                                cfg.sfbd_blend_equation.rgb.a = MALI_BLEND_OPERAND_A_SRC;
+                                cfg.sfbd_blend_equation.rgb.b = MALI_BLEND_OPERAND_B_SRC;
+                                cfg.sfbd_blend_equation.rgb.c = MALI_BLEND_OPERAND_C_ZERO;
+                                cfg.sfbd_blend_equation.alpha.a = MALI_BLEND_OPERAND_A_SRC;
+                                cfg.sfbd_blend_equation.alpha.b = MALI_BLEND_OPERAND_B_SRC;
+                                cfg.sfbd_blend_equation.alpha.c = MALI_BLEND_OPERAND_C_ZERO;
+
+                                if (loc >= FRAG_RESULT_DATA0)
+                                        cfg.sfbd_blend_equation.color_mask = 0xf;
                                 cfg.sfbd_blend_constant = 0;
                         }
                 } else if (!(pool->dev->quirks & IS_BIFROST)) {
@@ -313,6 +311,20 @@
                 void *dest = shader_meta_t.cpu + MALI_RENDERER_STATE_LENGTH + sizeof(struct midgard_blend_rt) * i;
 
                 if (loc == (FRAG_RESULT_DATA0 + i)) {
+                        struct mali_blend_equation_packed eq;
+
+                        pan_pack(&eq, BLEND_EQUATION, cfg) {
+                                cfg.rgb.a = MALI_BLEND_OPERAND_A_SRC;
+                                cfg.rgb.b = MALI_BLEND_OPERAND_B_SRC;
+                                cfg.rgb.c = MALI_BLEND_OPERAND_C_ZERO;
+                                cfg.alpha.a = MALI_BLEND_OPERAND_A_SRC;
+                                cfg.alpha.b = MALI_BLEND_OPERAND_B_SRC;
+                                cfg.alpha.c = MALI_BLEND_OPERAND_C_ZERO;
+
+                                if (loc >= FRAG_RESULT_DATA0)
+                                        cfg.color_mask = 0xf;
+                        }
+
                         union midgard_blend replace = {
                                 .equation = eq
                         };