[v2,1/6] nir: Add nir_lower_blend pass

Submitted by Alyssa Rosenzweig on May 13, 2019, 3:45 a.m.

Details

Message ID 20190513034600.3602-2-alyssa@rosenzweig.io
State New
Headers show
Series "Programmatic blending" ( rev: 1 ) in Mesa

Not browsing as part of any series.

Commit Message

Alyssa Rosenzweig May 13, 2019, 3:45 a.m.
This new lowering pass implements the OpenGL ES blend pipeline in
shaders, applicable to hardware lacking full-featured blending hardware
(including Midgard/Bifrost and vc4). This pass is run on a fragment
shader, rewriting the store to a blended version, loading in the
framebuffer destination color and constant color via intrinsics as
necessary. This pass is sufficient for OpenGL ES 2.0 and is verified to
pass dEQP's blend tests. MIN/MAX modes are included and tested as well.
That said, at present it has the following limitations:

 - MRT is not supported (ES3).
 - sRGB support is missing (ES3).
 - Extended blending is not yet ported from GLSL IR lowering (ES3.2)
 - Dual-source blending is not supported. (N/A)
 - Logic ops are not supported. (N/A)

v2: Fix code conventions (per Ian Romanick's feedback). Implement color
masks.

Signed-off-by: Alyssa Rosenzweig <alyssa@rosenzweig.io>
Cc: Eric Anholt <eric@anholt.net>
Cc: Ian Romanick <idr@freedesktop.org>
---
 src/compiler/Makefile.sources      |   1 +
 src/compiler/nir/meson.build       |   1 +
 src/compiler/nir/nir.h             |  26 +++
 src/compiler/nir/nir_lower_blend.c | 246 +++++++++++++++++++++++++++++
 4 files changed, 274 insertions(+)
 create mode 100644 src/compiler/nir/nir_lower_blend.c

Patch hide | download patch | download mbox

diff --git a/src/compiler/Makefile.sources b/src/compiler/Makefile.sources
index 1b6dc25f1ed..6b449514831 100644
--- a/src/compiler/Makefile.sources
+++ b/src/compiler/Makefile.sources
@@ -238,6 +238,7 @@  NIR_FILES = \
 	nir/nir_lower_bit_size.c \
 	nir/nir_lower_bool_to_float.c \
 	nir/nir_lower_bool_to_int32.c \
+	nir/nir_lower_blend.c \
 	nir/nir_lower_clamp_color_outputs.c \
 	nir/nir_lower_clip.c \
 	nir/nir_lower_clip_cull_distance_arrays.c \
diff --git a/src/compiler/nir/meson.build b/src/compiler/nir/meson.build
index 69de4121a73..04d70643cfc 100644
--- a/src/compiler/nir/meson.build
+++ b/src/compiler/nir/meson.build
@@ -116,6 +116,7 @@  files_libnir = files(
   'nir_lower_array_deref_of_vec.c',
   'nir_lower_atomics_to_ssbo.c',
   'nir_lower_bitmap.c',
+  'nir_lower_blend.c',
   'nir_lower_bool_to_float.c',
   'nir_lower_bool_to_int32.c',
   'nir_lower_clamp_color_outputs.c',
diff --git a/src/compiler/nir/nir.h b/src/compiler/nir/nir.h
index 8441c9f26c5..448cd8c3bcf 100644
--- a/src/compiler/nir/nir.h
+++ b/src/compiler/nir/nir.h
@@ -3446,6 +3446,32 @@  typedef enum  {
 
 bool nir_lower_to_source_mods(nir_shader *shader, nir_lower_to_source_mods_flags options);
 
+/* These structs encapsulates the blend state such that it can be lowered
+ * cleanly
+ */
+
+typedef struct {
+      enum blend_func func;
+
+      enum blend_factor src_factor;
+      bool invert_src_factor;
+
+      enum blend_factor dst_factor;
+      bool invert_dst_factor;
+} nir_lower_blend_channel;
+
+typedef struct {
+   struct {
+      nir_lower_blend_channel rgb;
+      nir_lower_blend_channel alpha;
+
+      /* 4-bit colormask. 0x0 for none, 0xF for RGBA, 0x1 for R */
+      unsigned colormask;
+   } rt[8];
+} nir_lower_blend_options;
+
+void nir_lower_blend(nir_shader *shader, nir_lower_blend_options options);
+
 bool nir_lower_gs_intrinsics(nir_shader *shader);
 
 typedef unsigned (*nir_lower_bit_size_callback)(const nir_alu_instr *, void *);
diff --git a/src/compiler/nir/nir_lower_blend.c b/src/compiler/nir/nir_lower_blend.c
new file mode 100644
index 00000000000..e1d8df48973
--- /dev/null
+++ b/src/compiler/nir/nir_lower_blend.c
@@ -0,0 +1,246 @@ 
+/*
+ * Copyright (C) 2019 Alyssa Rosenzweig
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+/**
+ * @file
+ *
+ * Implements the fragment pipeline (blending and writeout) in software, to be
+ * run as a dedicated "blend shader" stage on Midgard/Bifrost, or as a fragment
+ * shader variant on typical GPUs. This pass is useful if hardware lacks
+ * fixed-function blending in part or in full.
+ */
+
+#include "nir/nir.h"
+#include "nir/nir_builder.h"
+
+/* Given processed factors, combine them per a blend function */
+
+static nir_ssa_def *
+nir_blend_func(
+      nir_builder *b,
+      enum blend_func func,
+      nir_ssa_def *src, nir_ssa_def *dst)
+{
+   switch (func) {
+   case BLEND_FUNC_ADD:
+      return nir_fadd(b, src, dst);
+   case BLEND_FUNC_SUBTRACT:
+      return nir_fsub(b, src, dst);
+   case BLEND_FUNC_REVERSE_SUBTRACT:
+      return nir_fsub(b, dst, src);
+   case BLEND_FUNC_MIN:
+      return nir_fmin(b, src, dst);
+   case BLEND_FUNC_MAX:
+      return nir_fmax(b, src, dst);
+   }
+
+   unreachable("Invalid blend function");
+}
+
+/* Does this blend function multiply by a blend factor? */
+
+static bool
+nir_blend_factored(enum blend_func func)
+{
+   switch (func) {
+   case BLEND_FUNC_ADD:
+   case BLEND_FUNC_SUBTRACT:
+   case BLEND_FUNC_REVERSE_SUBTRACT:
+      return true;
+   default:
+      return false;
+   }
+}
+
+/* Compute a src_alpha_saturate factor */
+static nir_ssa_def *
+nir_alpha_saturate(
+      nir_builder *b,
+      nir_ssa_def *src, nir_ssa_def *dst,
+      unsigned chan)
+{
+   nir_ssa_def *Asrc = nir_channel(b, src, 3);
+   nir_ssa_def *Adst = nir_channel(b, dst, 3);
+   nir_ssa_def *one = nir_imm_float(b, 1.0);
+   nir_ssa_def *Adsti = nir_fsub(b, one, Adst);
+
+   return (chan < 3) ? nir_fmin(b, Asrc, Adsti) : one;
+}
+
+/* Returns a scalar single factor, unmultiplied */
+
+static nir_ssa_def *
+nir_blend_factor_value(
+      nir_builder *b,
+      nir_ssa_def *src, nir_ssa_def *dst, nir_ssa_def *bconst,
+      unsigned chan,
+      enum blend_factor factor)
+{
+   switch (factor) {
+   case BLEND_FACTOR_ZERO:
+      return nir_imm_float(b, 0.0);
+   case BLEND_FACTOR_SRC_COLOR:
+      return nir_channel(b, src, chan);
+   case BLEND_FACTOR_DST_COLOR:
+      return nir_channel(b, dst, chan);
+   case BLEND_FACTOR_SRC_ALPHA:
+      return nir_channel(b, src, 3);
+   case BLEND_FACTOR_DST_ALPHA:
+      return nir_channel(b, dst, 3);
+   case BLEND_FACTOR_CONSTANT_COLOR:
+      return nir_channel(b, bconst, chan);
+   case BLEND_FACTOR_CONSTANT_ALPHA:
+      return nir_channel(b, bconst, 3);
+   case BLEND_FACTOR_SRC_ALPHA_SATURATE:
+      return nir_alpha_saturate(b, src, dst, chan);
+   }
+
+   unreachable("Invalid blend factor");
+}
+
+static nir_ssa_def *
+nir_blend_factor(
+      nir_builder *b,
+      nir_ssa_def *raw_scalar,
+      nir_ssa_def *src, nir_ssa_def *dst, nir_ssa_def *bconst,
+      unsigned chan,
+      enum blend_factor factor,
+      bool inverted)
+{
+   nir_ssa_def *f =
+      nir_blend_factor_value(b, src, dst, bconst, chan, factor);
+
+   if (inverted)
+      f = nir_fsub(b, nir_imm_float(b, 1.0), f);
+
+   return nir_fmul(b, raw_scalar, f);
+}
+
+/* Given a colormask, "blend" with the destination */
+
+static nir_ssa_def *
+nir_color_mask(
+      nir_builder *b,
+      unsigned mask,
+      nir_ssa_def *src,
+      nir_ssa_def *dst)
+{
+   nir_ssa_def *masked[4];
+
+   for (unsigned c = 0; c < 4; ++c) {
+      bool enab = (mask & (1 << c)); 
+      masked[c] = enab ? nir_channel(b, src, c) : nir_channel(b, dst, c);
+   }
+
+   return nir_vec(b, masked, 4);
+}
+
+/* Given a blend state, the source color, and the destination color,
+ * return the blended color
+ */
+
+static nir_ssa_def *
+nir_blend(
+      nir_builder *b,
+      nir_lower_blend_options options,
+      nir_ssa_def *src, nir_ssa_def *dst)
+{
+   /* Grab the blend constant ahead of time */
+   nir_ssa_def *bconst = nir_load_blend_const_color_rgba(b);
+
+   /* We blend per channel and recombine later */
+   nir_ssa_def *channels[4];
+
+   for (unsigned c = 0; c < 4; ++c) {
+      /* Decide properties based on channel */
+      nir_lower_blend_channel chan =
+         (c < 3) ? options.rt[0].rgb : options.rt[0].alpha;
+
+      nir_ssa_def *psrc = nir_channel(b, src, c);
+      nir_ssa_def *pdst = nir_channel(b, dst, c);
+
+      if (nir_blend_factored(chan.func)) {
+         psrc = nir_blend_factor(
+                  b, psrc,
+                  src, dst, bconst, c,
+                  chan.src_factor, chan.invert_src_factor);
+
+         pdst = nir_blend_factor(
+                  b, pdst,
+                  src, dst, bconst, c,
+                  chan.dst_factor, chan.invert_dst_factor);
+      }
+
+      channels[c] = nir_blend_func(b, chan.func, psrc, pdst);
+   }
+
+   /* Then just recombine with an applied colormask */
+   nir_ssa_def *blended = nir_vec(b, channels, 4);
+   return nir_color_mask(b, options.rt[0].colormask, blended, dst);
+}
+
+void
+nir_lower_blend(nir_shader *shader, nir_lower_blend_options options)
+{
+   /* Blend shaders are represented as special fragment shaders */
+   assert(shader->info.stage == MESA_SHADER_FRAGMENT);
+
+   nir_foreach_function(func, shader) {
+      nir_foreach_block(block, func->impl) {
+         nir_foreach_instr_safe(instr, block) {
+            if (instr->type != nir_instr_type_intrinsic)
+               continue;
+
+            nir_intrinsic_instr *intr = nir_instr_as_intrinsic(instr);
+            if (intr->intrinsic != nir_intrinsic_store_deref)
+               continue;
+
+            /* TODO: Extending to MRT */
+            nir_variable *var = nir_intrinsic_get_var(intr, 0);
+            if (var->data.location != FRAG_RESULT_COLOR)
+               continue;
+
+            nir_builder b;
+            nir_builder_init(&b, func->impl);
+            b.cursor = nir_before_instr(instr);
+
+            /* Grab the input color */
+            nir_ssa_def *src = nir_ssa_for_src(&b, intr->src[1], 4);
+
+            /* Grab the tilebuffer color - io lowered to load_output */
+            nir_ssa_def *dst = nir_load_var(&b, var);
+
+            /* Blend the two colors per the passed options */
+            nir_ssa_def *blended = nir_blend(&b, options, src, dst);
+
+            /* Write out the final color instead of the input */
+            nir_instr_rewrite_src(instr, &intr->src[1],
+                                  nir_src_for_ssa(blended));
+ 
+         }
+      }
+
+      nir_metadata_preserve(func->impl, nir_metadata_block_index |
+                            nir_metadata_dominance);
+   }
+}