Mesa (main): ac,radeonsi: cull small lines in the shader using the diamond exit rule

GitLab Mirror gitlab-mirror at kemper.freedesktop.org
Tue Nov 16 02:41:11 UTC 2021


Module: Mesa
Branch: main
Commit: 9151ac3531f05a825b6a07c4977251b45ed34141
URL:    http://cgit.freedesktop.org/mesa/mesa/commit/?id=9151ac3531f05a825b6a07c4977251b45ed34141

Author: Marek Olšák <marek.olsak at amd.com>
Date:   Fri Nov  5 21:56:24 2021 -0400

ac,radeonsi: cull small lines in the shader using the diamond exit rule

It also splits clip_half_line_width into X and Y components for tighter
view culling.

Reviewed-by: Pierre-Eric Pelloux-Prayer <pierre-eric.pelloux-prayer at amd.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/13700>

---

 src/amd/llvm/ac_llvm_cull.c                      | 96 +++++++++++++++++++++++-
 src/gallium/drivers/radeonsi/gfx10_shader_ngg.c  | 31 ++++----
 src/gallium/drivers/radeonsi/si_pipe.h           |  2 +
 src/gallium/drivers/radeonsi/si_shader.h         |  3 +-
 src/gallium/drivers/radeonsi/si_state.c          |  3 +-
 src/gallium/drivers/radeonsi/si_state_viewport.c | 11 ++-
 6 files changed, 125 insertions(+), 21 deletions(-)

diff --git a/src/amd/llvm/ac_llvm_cull.c b/src/amd/llvm/ac_llvm_cull.c
index 87d201f0781..d37a9f847f6 100644
--- a/src/amd/llvm/ac_llvm_cull.c
+++ b/src/amd/llvm/ac_llvm_cull.c
@@ -120,6 +120,25 @@ static LLVMValueRef ac_cull_face(struct ac_llvm_context *ctx, LLVMValueRef pos[3
    return accepted;
 }
 
+static void rotate_45degrees(struct ac_llvm_context *ctx, LLVMValueRef v[2])
+{
+   /* sin(45) == cos(45) */
+   LLVMValueRef sincos45 = LLVMConstReal(ctx->f32, 0.707106781);
+
+   /* x2  =  x*cos45 - y*sin45  =  x*sincos45 - y*sincos45
+    * y2  =  x*sin45 + y*cos45  =  x*sincos45 + y*sincos45
+    */
+   LLVMValueRef first = LLVMBuildFMul(ctx->builder, v[0], sincos45, "");
+
+   /* Doing 2x ffma while duplicating the multiplication is 33% faster than fmul+fadd+fadd. */
+   LLVMValueRef result[2] = {
+      ac_build_fmad(ctx, LLVMBuildFNeg(ctx->builder, v[1], ""), sincos45, first),
+      ac_build_fmad(ctx, v[1], sincos45, first),
+   };
+
+   memcpy(v, result, sizeof(result));
+}
+
 /* Perform view culling and small primitive elimination and return true
  * if the primitive is accepted and initially_accepted == true. */
 static void cull_bbox(struct ac_llvm_context *ctx, LLVMValueRef pos[3][4],
@@ -181,8 +200,8 @@ static void cull_bbox(struct ac_llvm_context *ctx, LLVMValueRef pos[3][4],
          }
       }
 
-      /* Small primitive elimination. */
-      if (options->cull_small_prims) {
+      /* Small primitive culling - triangles. */
+      if (options->cull_small_prims && options->num_vertices == 3) {
          /* Assuming a sample position at (0.5, 0.5), if we round
           * the bounding box min/max extents and the results of
           * the rounding are equal in either the X or Y direction,
@@ -214,6 +233,79 @@ static void cull_bbox(struct ac_llvm_context *ctx, LLVMValueRef pos[3][4],
          accepted = LLVMBuildAnd(builder, accepted, visible, "");
       }
 
+      /* Small primitive culling - lines. */
+      if (options->cull_small_prims && options->num_vertices == 2) {
+         /* This only works with lines without perpendicular end caps (lines with perpendicular
+          * end caps are rasterized as quads and thus can't be culled as small prims in 99% of
+          * cases because line_width >= 1).
+          *
+          * This takes advantage of the diamont exit rule, which says that every pixel
+          * has a diamond inside it touching the pixel boundary and only if a line exits
+          * the diamond, that pixel is filled. If a line enters the diamond or stays
+          * outside the diamond, the pixel isn't filled.
+          *
+          * This algorithm is a little simpler than that. The space outside all diamonds also
+          * has the same diamond shape, which we'll call corner diamonds.
+          *
+          * The idea is to cull all lines that are entirely inside a diamond, including
+          * corner diamonds. If a line is entirely inside a diamond, it can be culled because
+          * it doesn't exit it. If a line is entirely inside a corner diamond, it can be culled
+          * because it doesn't enter any diamond and thus can't exit any diamond.
+          *
+          * The viewport is rotated by 45 degress to turn diamonds into squares, and a bounding
+          * box test is used to determine whether a line is entirely inside any square (diamond).
+          *
+          * The line width doesn't matter. Wide lines only duplicate filled pixels in either X or
+          * Y direction from the filled pixels. MSAA also doesn't matter. MSAA should ideally use
+          * perpendicular end caps that enable quad rasterization for lines. Thus, this should
+          * always use non-MSAA viewport transformation and non-MSAA small prim precision.
+          *
+          * A good test is piglit/lineloop because it draws 10k subpixel lines in a circle.
+          * It should contain no holes if this matches hw behavior.
+          */
+         LLVMValueRef v0[2], v1[2];
+
+         /* Get vertex positions in pixels. */
+         for (unsigned chan = 0; chan < 2; chan++) {
+            v0[chan] = ac_build_fmad(ctx, pos[0][chan], vp_scale[chan], vp_translate[chan]);
+            v1[chan] = ac_build_fmad(ctx, pos[1][chan], vp_scale[chan], vp_translate[chan]);
+         }
+
+         /* Rotate the viewport by 45 degress, so that diamonds become squares. */
+         rotate_45degrees(ctx, v0);
+         rotate_45degrees(ctx, v1);
+
+         LLVMValueRef not_equal[2];
+
+         for (unsigned chan = 0; chan < 2; chan++) {
+            /* The width of each square is sqrt(0.5), so scale it to 1 because we want
+             * round() to give us the position of the closest center of a square (diamond).
+             */
+            v0[chan] = LLVMBuildFMul(builder, v0[chan], LLVMConstReal(ctx->f32, 1.414213562), "");
+            v1[chan] = LLVMBuildFMul(builder, v1[chan], LLVMConstReal(ctx->f32, 1.414213562), "");
+
+            /* Compute the bounding box around both vertices. We do this because we must
+             * enlarge the line area by the precision of the rasterizer.
+             */
+            LLVMValueRef min = ac_build_fmin(ctx, v0[chan], v1[chan]);
+            LLVMValueRef max = ac_build_fmax(ctx, v0[chan], v1[chan]);
+
+            /* Enlarge the bounding box by the precision of the rasterizer. */
+            min = LLVMBuildFSub(builder, min, small_prim_precision, "");
+            max = LLVMBuildFAdd(builder, max, small_prim_precision, "");
+
+            /* Round the bounding box corners. If both rounded corners are equal,
+             * the bounding box is entirely inside a square (diamond).
+             */
+            min = ac_build_round(ctx, min);
+            max = ac_build_round(ctx, max);
+            not_equal[chan] = LLVMBuildFCmp(builder, LLVMRealONE, min, max, "");
+         }
+
+         accepted = LLVMBuildAnd(builder, accepted,
+                                 LLVMBuildOr(builder, not_equal[0], not_equal[1], ""), "");
+      }
+
       /* Disregard the bounding box culling if any W is negative because the code
        * doesn't work with that.
        */
diff --git a/src/gallium/drivers/radeonsi/gfx10_shader_ngg.c b/src/gallium/drivers/radeonsi/gfx10_shader_ngg.c
index 71a14a5f721..b5369faf6ec 100644
--- a/src/gallium/drivers/radeonsi/gfx10_shader_ngg.c
+++ b/src/gallium/drivers/radeonsi/gfx10_shader_ngg.c
@@ -970,44 +970,47 @@ void gfx10_emit_ngg_culling_epilogue(struct ac_shader_abi *abi)
          }
       }
 
+      LLVMValueRef vp_scale[2] = {}, vp_translate[2] = {}, small_prim_precision = NULL;
       LLVMValueRef clip_half_line_width[2] = {};
 
       /* Load the viewport state for small prim culling. */
+      bool prim_is_lines = shader->key.ge.opt.ngg_culling & SI_NGG_CULL_LINES;
       LLVMValueRef ptr = ac_get_arg(&ctx->ac, ctx->small_prim_cull_info);
-      LLVMValueRef vp = ac_build_load_to_sgpr(&ctx->ac, ptr, ctx->ac.i32_0);
+      /* Lines will always use the non-AA viewport transformation. */
+      LLVMValueRef vp = ac_build_load_to_sgpr(&ctx->ac, ptr,
+                                              prim_is_lines ? ctx->ac.i32_1 : ctx->ac.i32_0);
       vp = LLVMBuildBitCast(builder, vp, ctx->ac.v4f32, "");
-      LLVMValueRef vp_scale[2], vp_translate[2];
       vp_scale[0] = ac_llvm_extract_elem(&ctx->ac, vp, 0);
       vp_scale[1] = ac_llvm_extract_elem(&ctx->ac, vp, 1);
       vp_translate[0] = ac_llvm_extract_elem(&ctx->ac, vp, 2);
       vp_translate[1] = ac_llvm_extract_elem(&ctx->ac, vp, 3);
 
-      /* Get the small prim filter precision. */
-      LLVMValueRef small_prim_precision = si_unpack_param(ctx, ctx->vs_state_bits, 7, 4);
-      small_prim_precision =
-         LLVMBuildOr(builder, small_prim_precision, LLVMConstInt(ctx->ac.i32, 0x70, 0), "");
-      small_prim_precision =
-         LLVMBuildShl(builder, small_prim_precision, LLVMConstInt(ctx->ac.i32, 23, 0), "");
-      small_prim_precision = LLVMBuildBitCast(builder, small_prim_precision, ctx->ac.f32, "");
-
       /* Execute culling code. */
       struct ac_cull_options options = {};
       options.cull_view_xy = true;
       options.cull_w = true;
 
-      if (shader->key.ge.opt.ngg_culling & SI_NGG_CULL_LINES) {
-         ptr = LLVMBuildPointerCast(builder, ptr,
-                                    LLVMPointerType(ctx->ac.v2i32, AC_ADDR_SPACE_CONST_32BIT), "");
+      if (prim_is_lines) {
          LLVMValueRef terms = ac_build_load_to_sgpr(&ctx->ac, ptr, LLVMConstInt(ctx->ac.i32, 2, 0));
-         terms = LLVMBuildBitCast(builder, terms, ctx->ac.v2f32, "");
+         terms = LLVMBuildBitCast(builder, terms, ctx->ac.v4f32, "");
          clip_half_line_width[0] = ac_llvm_extract_elem(&ctx->ac, terms, 0);
          clip_half_line_width[1] = ac_llvm_extract_elem(&ctx->ac, terms, 1);
+         small_prim_precision = ac_llvm_extract_elem(&ctx->ac, terms, 2);
 
          options.num_vertices = 2;
+         options.cull_small_prims = shader->key.ge.opt.ngg_culling & SI_NGG_CULL_SMALL_LINES_DIAMOND_EXIT;
 
          assert(!(shader->key.ge.opt.ngg_culling & SI_NGG_CULL_BACK_FACE));
          assert(!(shader->key.ge.opt.ngg_culling & SI_NGG_CULL_FRONT_FACE));
       } else {
+         /* Get the small prim filter precision. */
+         small_prim_precision = si_unpack_param(ctx, ctx->vs_state_bits, 7, 4);
+         small_prim_precision =
+            LLVMBuildOr(builder, small_prim_precision, LLVMConstInt(ctx->ac.i32, 0x70, 0), "");
+         small_prim_precision =
+            LLVMBuildShl(builder, small_prim_precision, LLVMConstInt(ctx->ac.i32, 23, 0), "");
+         small_prim_precision = LLVMBuildBitCast(builder, small_prim_precision, ctx->ac.f32, "");
+
          options.num_vertices = 3;
          options.cull_front = shader->key.ge.opt.ngg_culling & SI_NGG_CULL_FRONT_FACE;
          options.cull_back = shader->key.ge.opt.ngg_culling & SI_NGG_CULL_BACK_FACE;
diff --git a/src/gallium/drivers/radeonsi/si_pipe.h b/src/gallium/drivers/radeonsi/si_pipe.h
index a98b6484318..d5d88a4a9a2 100644
--- a/src/gallium/drivers/radeonsi/si_pipe.h
+++ b/src/gallium/drivers/radeonsi/si_pipe.h
@@ -899,7 +899,9 @@ struct si_saved_cs {
 
 struct si_small_prim_cull_info {
    float scale[2], translate[2];
+   float scale_no_aa[2], translate_no_aa[2];
    float clip_half_line_width[2];      /* line_width * 0.5 in clip space in X and Y directions */
+   float small_prim_precision_no_aa;   /* same as the small prim precision, but ignores MSAA */
    /* The above fields are uploaded to memory. The below fields are passed via user SGPRs. */
    float small_prim_precision;
 };
diff --git a/src/gallium/drivers/radeonsi/si_shader.h b/src/gallium/drivers/radeonsi/si_shader.h
index 118c37ee5a7..bc27e82e696 100644
--- a/src/gallium/drivers/radeonsi/si_shader.h
+++ b/src/gallium/drivers/radeonsi/si_shader.h
@@ -283,6 +283,7 @@ enum
 #define SI_NGG_CULL_BACK_FACE                (1 << 1)   /* back faces */
 #define SI_NGG_CULL_FRONT_FACE               (1 << 2)   /* front faces */
 #define SI_NGG_CULL_LINES                    (1 << 3)   /* the primitive type is lines */
+#define SI_NGG_CULL_SMALL_LINES_DIAMOND_EXIT (1 << 4)   /* cull small lines according to the diamond exit rule */
 
 /**
  * For VS shader keys, describe any fixups required for vertex fetch.
@@ -660,7 +661,7 @@ struct si_shader_key_ge {
       unsigned kill_pointsize : 1;
 
       /* For NGG VS and TES. */
-      unsigned ngg_culling : 4; /* SI_NGG_CULL_* */
+      unsigned ngg_culling : 5; /* SI_NGG_CULL_* */
 
       /* For shaders where monolithic variants have better code.
        *
diff --git a/src/gallium/drivers/radeonsi/si_state.c b/src/gallium/drivers/radeonsi/si_state.c
index d12424cbe29..ffff6234572 100644
--- a/src/gallium/drivers/radeonsi/si_state.c
+++ b/src/gallium/drivers/radeonsi/si_state.c
@@ -969,7 +969,8 @@ static void *si_create_rs_state(struct pipe_context *ctx, const struct pipe_rast
    } else {
       rs->ngg_cull_flags_tris = rs->ngg_cull_flags_tris_y_inverted = SI_NGG_CULL_ENABLED;
       rs->ngg_cull_flags_lines = SI_NGG_CULL_ENABLED |
-                                 SI_NGG_CULL_LINES;
+                                 SI_NGG_CULL_LINES |
+                                 (!rs->perpendicular_end_caps ? SI_NGG_CULL_SMALL_LINES_DIAMOND_EXIT : 0);
 
       bool cull_front, cull_back;
 
diff --git a/src/gallium/drivers/radeonsi/si_state_viewport.c b/src/gallium/drivers/radeonsi/si_state_viewport.c
index be184bf5eb9..7db69b9df0d 100644
--- a/src/gallium/drivers/radeonsi/si_state_viewport.c
+++ b/src/gallium/drivers/radeonsi/si_state_viewport.c
@@ -70,6 +70,9 @@ static void si_get_small_prim_cull_info(struct si_context *sctx, struct si_small
       info.translate[1] += 0.5;
    }
 
+   memcpy(info.scale_no_aa, info.scale, sizeof(info.scale));
+   memcpy(info.translate_no_aa, info.translate, sizeof(info.translate));
+
    /* Scale the framebuffer up, so that samples become pixels and small
     * primitive culling is the same for all sample counts.
     * This only works with the standard DX sample positions, because
@@ -87,11 +90,13 @@ static void si_get_small_prim_cull_info(struct si_context *sctx, struct si_small
    unsigned quant_mode = sctx->viewports.as_scissor[0].quant_mode;
 
    if (quant_mode == SI_QUANT_MODE_12_12_FIXED_POINT_1_4096TH)
-      info.small_prim_precision = num_samples / 4096.0;
+      info.small_prim_precision_no_aa = 1.0 / 4096.0;
    else if (quant_mode == SI_QUANT_MODE_14_10_FIXED_POINT_1_1024TH)
-      info.small_prim_precision = num_samples / 1024.0;
+      info.small_prim_precision_no_aa = 1.0 / 1024.0;
    else
-      info.small_prim_precision = num_samples / 256.0;
+      info.small_prim_precision_no_aa = 1.0 / 256.0;
+
+   info.small_prim_precision = num_samples * info.small_prim_precision_no_aa;
 
    *out = info;
 }



More information about the mesa-commit mailing list