[Bf-blender-cvs] [0cb5eae9d06] master: Gizmo: optimize intersection tests, fix selection bias

Campbell Barton noreply at git.blender.org
Wed Jan 26 07:24:37 CET 2022


Commit: 0cb5eae9d0617abedf745753c23061ddfcfd1416
Author: Campbell Barton
Date:   Wed Jan 26 17:10:37 2022 +1100
Branches: master
https://developer.blender.org/rB0cb5eae9d0617abedf745753c23061ddfcfd1416

Gizmo: optimize intersection tests, fix selection bias

Use more efficient logic for detecting when gizmos are under the cursor.
Even though this isn't a bottleneck, it runs on cursor motion in the
3D viewport, so avoiding any lag here is beneficial.

The common case for cursor motion without any gizmos was always
drawing two passes (one small, then again if nothing was found).

Now a single draw call at the larger size is used.

In isolation this gives around 1.2x-1.4x speedup.

When there are multiple gizmos a depth-buffer picking is used
(similar to object / bone selection) which is more involved but
still only performs 2x draw calls since the result is cached for reuse.

See note in gizmo_find_intersected_3d for a more detailed explanation.

Also restore the depth values in the selection result as they're
needed for gizmos to use selection bias.
Broken since support for GL_SELECT was removed.

===================================================================

M	source/blender/gpu/intern/gpu_select_pick.c
M	source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c

===================================================================

diff --git a/source/blender/gpu/intern/gpu_select_pick.c b/source/blender/gpu/intern/gpu_select_pick.c
index 737e4c6e874..ddd3dfc6879 100644
--- a/source/blender/gpu/intern/gpu_select_pick.c
+++ b/source/blender/gpu/intern/gpu_select_pick.c
@@ -673,7 +673,7 @@ uint gpu_select_pick_end(void)
 #endif
       /* first 3 are dummy values */
       g_pick_state.buffer[hits][0] = 1;
-      g_pick_state.buffer[hits][1] = 0x0; /* depth_data[i].depth; */ /* unused */
+      g_pick_state.buffer[hits][1] = depth_data[i].depth;
       g_pick_state.buffer[hits][2] = 0x0; /* z-far is currently never used. */
       g_pick_state.buffer[hits][3] = depth_data[i].id;
       hits++;
diff --git a/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c b/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c
index 5152d9dd1ab..e61de28d0a4 100644
--- a/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c
+++ b/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c
@@ -507,8 +507,7 @@ void WM_gizmomap_draw(wmGizmoMap *gzmap,
 
 static void gizmo_draw_select_3d_loop(const bContext *C,
                                       wmGizmo **visible_gizmos,
-                                      const int visible_gizmos_len,
-                                      bool *r_use_select_bias)
+                                      const int visible_gizmos_len)
 {
 
   /* TODO(campbell): this depends on depth buffer being written to,
@@ -544,10 +543,6 @@ static void gizmo_draw_select_3d_loop(const bContext *C,
       is_depth_skip_prev = is_depth_skip;
     }
 
-    if (gz->select_bias != 0.0) {
-      *r_use_select_bias = true;
-    }
-
     /* pass the selection id shifted by 8 bits. Last 8 bits are used for selected gizmo part id */
 
     gz->type->draw_select(C, gz, select_id << 8);
@@ -565,7 +560,10 @@ static int gizmo_find_intersected_3d_intern(wmGizmo **visible_gizmos,
                                             const int visible_gizmos_len,
                                             const bContext *C,
                                             const int co[2],
-                                            const int hotspot)
+                                            const int hotspot,
+                                            const bool use_depth_test,
+                                            const bool has_3d_select_bias,
+                                            int *r_hits)
 {
   const wmWindowManager *wm = CTX_wm_manager(C);
   ScrArea *area = CTX_wm_area(C);
@@ -579,30 +577,69 @@ static int gizmo_find_intersected_3d_intern(wmGizmo **visible_gizmos,
 
   BLI_rcti_init_pt_radius(&rect, co, hotspot);
 
-  ED_view3d_draw_setup_view(
-      wm, CTX_wm_window(C), depsgraph, CTX_data_scene(C), region, v3d, NULL, NULL, &rect);
+  /* The selection mode is assigned for the following reasons:
+   *
+   * - #GPU_SELECT_ALL: Use it to check if there is anything at the cursor location
+   *   (only ever runs once).
+   * - #GPU_SELECT_PICK_NEAREST: Use if there are more than 1 item at the cursor location,
+   *   select the best one.
+   * - #GPU_SELECT_PICK_ALL: Use for the same purpose as #GPU_SELECT_PICK_NEAREST
+   *   when the selection depths need to re-ordered based on a bias.
+   * */
+  const int gpu_select_mode = (use_depth_test ?
+                                   (has_3d_select_bias ?
+                                         /* Using select bias means the depths need to be
+                                          * re-calculated based on the bias to pick the best. */
+                                         GPU_SELECT_PICK_ALL :
+                                         /* No bias, just pick the closest. */
+                                         GPU_SELECT_PICK_NEAREST) :
+                                   /* Fast-path (occlusion queries). */
+                                   GPU_SELECT_ALL);
+
+  if (GPU_select_is_cached()) {
+    GPU_select_begin(buffer, ARRAY_SIZE(buffer), &rect, gpu_select_mode, 0);
+    GPU_select_cache_load_id();
+    hits = GPU_select_end();
+  }
+  else {
+    /* TODO: waiting for the GPU in the middle of the event loop for every
+     * mouse move is bad for performance, we need to find a solution to not
+     * use the GPU or draw something once. (see T61474) */
 
-  bool use_select_bias = false;
+    ED_view3d_draw_setup_view(
+        wm, CTX_wm_window(C), depsgraph, CTX_data_scene(C), region, v3d, NULL, NULL, &rect);
+
+    /* There is no need to bind to the depth buffer outside this function
+     * because all future passes the will use the cached depths. */
+    GPUFrameBuffer *depth_read_fb = NULL;
+    if (use_depth_test) {
+      GPUViewport *viewport = WM_draw_region_get_viewport(CTX_wm_region(C));
+      GPUTexture *depth_tx = GPU_viewport_depth_texture(viewport);
+      GPU_framebuffer_ensure_config(&depth_read_fb,
+                                    {
+                                        GPU_ATTACHMENT_TEXTURE(depth_tx),
+                                        GPU_ATTACHMENT_NONE,
+                                    });
+      GPU_framebuffer_bind(depth_read_fb);
+    }
 
-  /* TODO: waiting for the GPU in the middle of the event loop for every
-   * mouse move is bad for performance, we need to find a solution to not
-   * use the GPU or draw something once. (see T61474) */
-  GPU_select_begin(buffer, ARRAY_SIZE(buffer), &rect, GPU_SELECT_NEAREST_FIRST_PASS, 0);
-  /* do the drawing */
-  gizmo_draw_select_3d_loop(C, visible_gizmos, visible_gizmos_len, &use_select_bias);
+    GPU_select_begin(buffer, ARRAY_SIZE(buffer), &rect, gpu_select_mode, 0);
+    gizmo_draw_select_3d_loop(C, visible_gizmos, visible_gizmos_len);
+    hits = GPU_select_end();
 
-  hits = GPU_select_end();
+    if (use_depth_test) {
+      GPU_framebuffer_restore();
+      GPU_framebuffer_free(depth_read_fb);
+    }
 
-  if (hits > 0) {
-    GPU_select_begin(buffer, ARRAY_SIZE(buffer), &rect, GPU_SELECT_NEAREST_SECOND_PASS, hits);
-    gizmo_draw_select_3d_loop(C, visible_gizmos, visible_gizmos_len, &use_select_bias);
-    GPU_select_end();
+    ED_view3d_draw_setup_view(
+        wm, CTX_wm_window(C), depsgraph, CTX_data_scene(C), region, v3d, NULL, NULL, NULL);
   }
 
-  ED_view3d_draw_setup_view(
-      wm, CTX_wm_window(C), depsgraph, CTX_data_scene(C), region, v3d, NULL, NULL, NULL);
+  /* When selection bias is needed, this function will run again with `use_depth_test` enabled. */
+  int hit_found = -1;
 
-  if (use_select_bias && (hits > 1)) {
+  if (has_3d_select_bias && use_depth_test && (hits > 1)) {
     float co_direction[3];
     float co_screen[3] = {co[0], co[1], 0.0f};
     ED_view3d_win_to_vector(region, (float[2]){UNPACK2(co)}, co_direction);
@@ -614,7 +651,6 @@ static int gizmo_find_intersected_3d_intern(wmGizmo **visible_gizmos,
     GPU_matrix_unproject_3fv(co_screen, rv3d->viewinv, rv3d->winmat, viewport, co_3d_origin);
 
     uint *buf_iter = buffer;
-    int hit_found = -1;
     float dot_best = FLT_MAX;
 
     for (int i = 0; i < hits; i++, buf_iter += 4) {
@@ -634,11 +670,16 @@ static int gizmo_find_intersected_3d_intern(wmGizmo **visible_gizmos,
         hit_found = buf_iter[3];
       }
     }
-    return hit_found;
+  }
+  else {
+    const uint *hit_near = GPU_select_buffer_near(buffer, hits);
+    if (hit_near) {
+      hit_found = hit_near[3];
+    }
   }
 
-  const uint *hit_near = GPU_select_buffer_near(buffer, hits);
-  return hit_near ? hit_near[3] : -1;
+  *r_hits = hits;
+  return hit_found;
 }
 
 /**
@@ -661,6 +702,7 @@ static wmGizmo *gizmo_find_intersected_3d(bContext *C,
 
   /* Search for 3D gizmo's that use the 2D callback for checking intersections. */
   bool has_3d = false;
+  bool has_3d_select_bias = false;
   {
     for (int select_id = 0; select_id < visible_gizmos_len; select_id++) {
       wmGizmo *gz = visible_gizmos[select_id];
@@ -676,6 +718,9 @@ static wmGizmo *gizmo_find_intersected_3d(bContext *C,
       }
       else if (gz->type->draw_select != NULL) {
         has_3d = true;
+        if (gz->select_bias != 0.0f) {
+          has_3d_select_bias = true;
+        }
       }
     }
   }
@@ -684,32 +729,78 @@ static wmGizmo *gizmo_find_intersected_3d(bContext *C,
    * This way we always use the first hit. */
   if (has_3d) {
 
-    /* The depth buffer is needed for for gizmos to obscure eachother.  */
-    GPUViewport *viewport = WM_draw_region_get_viewport(CTX_wm_region(C));
-    GPUTexture *depth_tx = GPU_viewport_depth_texture(viewport);
-    GPUFrameBuffer *depth_read_fb = NULL;
-    GPU_framebuffer_ensure_config(&depth_read_fb,
-                                  {
-                                      GPU_ATTACHMENT_TEXTURE(depth_tx),
-                                      GPU_ATTACHMENT_NONE,
-                                  });
-    GPU_framebuffer_bind(depth_read_fb);
-
+    /* NOTE(@campbellbarton): The selection logic here uses a fast-path that exits early
+     * where possible. This is important as this runs on cursor-motion in the 3D view-port.
+     *
+     * - First, don't use the depth buffer at all, use occlusion queries to detect any gizmos.
+     *   If there are no gizmos or only one - early exit, otherwise.
+     *
+     * - Bind the depth buffer and and use selection picking logic.
+     *   This is much slower than occlusion queries (since it's reading depths while drawing).
+     *   When there is a single gizmo under the cursor (quite common), early exit, otherwise.
+     *
+     * - Perform another pass at a reduced size (see: `hotspot_radii`),
+     *   since the result depths are cached this pass is practically free.
+     *
+     * Other notes:
+     *
+     * - If any of these passes fail, use the nearest result from the previous pass.
+     *
+     * - Drawing is only ever done twice.
+     */
+
+    /* Order largest to smallest so the first pass can be used as cache for
+     * later passes (when `use_depth_test == true`). */
     const int hotspot_radii[] = {
-        3 * U.pixelsize,
-        /* This runs on mouse move, careful doing too many tests! */
         10 * U.pixelsize,
+        /* This runs on mouse move, careful doing too many tests! */
+        3 * U.pixelsize,
     };
+
+    /* Narrowing may assign zero to `hit`, 

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list