[mutter] clutter/stage-view: Add tile based shadow damage detection



commit c2c4f749230c2f25a9497af2f102b6176a8af8fc
Author: Jonas Ã…dahl <jadahl gmail com>
Date:   Wed May 6 22:14:17 2020 +0200

    clutter/stage-view: Add tile based shadow damage detection
    
    Compare, tile by tile, whether actual damage actually changed any
    pixels. While this requires mmap():ing DMA buffers and comparing their
    content, we should only ever use shadow buffers when we're using the
    software renderer, meaning mmap() is cheap as it doesn't involve any
    downloading.
    
    This works by making the shadow framebuffer double buffered, while
    keeping track of damage history. When we're about to swap the onscreen
    buffer, we compare what part of the posted damage actually changed,
    records that into a damage history, then given the onscreen buffer age,
    collect all actual damage for that age. The intersection of these tiles,
    and the actual damage, is then used when blitting the shadow buffer to
    the onscreen framebuffer.
    
    Closes: https://gitlab.gnome.org/GNOME/mutter/-/issues/1157
    
    https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237

 clutter/clutter/clutter-stage-view.c | 475 +++++++++++++++++++++++++++++++----
 1 file changed, 431 insertions(+), 44 deletions(-)
---
diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c
index ac75e8dcdd..efc9d67a85 100644
--- a/clutter/clutter/clutter-stage-view.c
+++ b/clutter/clutter/clutter-stage-view.c
@@ -23,6 +23,7 @@
 #include <cairo-gobject.h>
 #include <math.h>
 
+#include "clutter/clutter-damage-history.h"
 #include "clutter/clutter-private.h"
 #include "clutter/clutter-mutter.h"
 #include "cogl/cogl.h"
@@ -56,6 +57,12 @@ typedef struct _ClutterStageViewPrivate
 
   gboolean use_shadowfb;
   struct {
+    struct {
+      CoglDmaBufHandle *handles[2];
+      int current_idx;
+      ClutterDamageHistory *damage_history;
+    } dma_buf;
+
     CoglOffscreen *framebuffer;
   } shadow;
 
@@ -268,6 +275,66 @@ paint_transformed_framebuffer (ClutterStageView     *view,
   cogl_framebuffer_pop_matrix (dst_framebuffer);
 }
 
+static gboolean
+is_shadowfb_double_buffered (ClutterStageView *view)
+{
+  ClutterStageViewPrivate *priv =
+    clutter_stage_view_get_instance_private (view);
+
+  return priv->shadow.dma_buf.handles[0] && priv->shadow.dma_buf.handles[1];
+}
+
+static gboolean
+init_dma_buf_shadowfbs (ClutterStageView  *view,
+                        CoglContext       *cogl_context,
+                        int                width,
+                        int                height,
+                        GError           **error)
+{
+  ClutterStageViewPrivate *priv =
+    clutter_stage_view_get_instance_private (view);
+  CoglRenderer *cogl_renderer = cogl_context_get_renderer (cogl_context);
+  CoglFramebuffer *initial_shadowfb;
+
+  if (!cogl_clutter_winsys_has_feature (COGL_WINSYS_FEATURE_BUFFER_AGE))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                   "Buffer age not supported");
+      return FALSE;
+    }
+
+  if (!cogl_is_onscreen (priv->framebuffer))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                   "Tried to use shadow buffer without onscreen");
+      return FALSE;
+    }
+
+  priv->shadow.dma_buf.handles[0] = cogl_renderer_create_dma_buf (cogl_renderer,
+                                                                  width, height,
+                                                                  error);
+  if (!priv->shadow.dma_buf.handles[0])
+    return FALSE;
+
+  priv->shadow.dma_buf.handles[1] = cogl_renderer_create_dma_buf (cogl_renderer,
+                                                                  width, height,
+                                                                  error);
+  if (!priv->shadow.dma_buf.handles[1])
+    {
+      g_clear_pointer (&priv->shadow.dma_buf.handles[0],
+                       cogl_dma_buf_handle_free);
+      return FALSE;
+    }
+
+  priv->shadow.dma_buf.damage_history = clutter_damage_history_new ();
+
+  initial_shadowfb =
+    cogl_dma_buf_handle_get_framebuffer (priv->shadow.dma_buf.handles[0]);
+  priv->shadow.framebuffer = cogl_object_ref (initial_shadowfb);
+
+  return TRUE;
+}
+
 static CoglOffscreen *
 create_offscreen_framebuffer (CoglContext  *context,
                               int           width,
@@ -299,11 +366,11 @@ create_offscreen_framebuffer (CoglContext  *context,
 }
 
 static gboolean
-init_offscreen_shadowfb (ClutterStageView  *view,
-                         CoglContext       *cogl_context,
-                         int                width,
-                         int                height,
-                         GError           **error)
+init_fallback_shadowfb (ClutterStageView  *view,
+                        CoglContext       *cogl_context,
+                        int                width,
+                        int                height,
+                        GError           **error)
 {
   ClutterStageViewPrivate *priv =
     clutter_stage_view_get_instance_private (view);
@@ -331,7 +398,17 @@ init_shadowfb (ClutterStageView *view)
   height = cogl_framebuffer_get_height (priv->framebuffer);
   cogl_context = cogl_framebuffer_get_context (priv->framebuffer);
 
-  if (!init_offscreen_shadowfb (view, cogl_context, width, height, &error))
+  if (init_dma_buf_shadowfbs (view, cogl_context, width, height, &error))
+    {
+      g_message ("Initialized double buffered shadow fb for %s", priv->name);
+      return;
+    }
+
+  g_warning ("Failed to initialize double buffered shadow fb for %s: %s",
+             priv->name, error->message);
+  g_clear_error (&error);
+
+  if (!init_fallback_shadowfb (view, cogl_context, width, height, &error))
     {
       g_warning ("Failed to initialize single buffered shadow fb for %s: %s",
                  priv->name, error->message);
@@ -372,54 +449,287 @@ clutter_stage_view_after_paint (ClutterStageView *view,
     }
 }
 
-void
-clutter_stage_view_before_swap_buffer (ClutterStageView     *view,
-                                       const cairo_region_t *swap_region)
+static gboolean
+is_tile_dirty (cairo_rectangle_int_t *tile,
+               uint8_t               *current_data,
+               uint8_t               *prev_data,
+               int                    bpp,
+               int                    stride)
+{
+  int y;
+
+  for (y = tile->y; y < tile->y + tile->height; y++)
+    {
+      if (memcmp (prev_data + y * stride + tile->x * bpp,
+                  current_data + y * stride + tile->x * bpp,
+                  tile->width * bpp) != 0)
+        return TRUE;
+    }
+
+  return FALSE;
+}
+
+static int
+flip_dma_buf_idx (int idx)
+{
+  return (idx + 1) % 2;
+}
+
+static cairo_region_t *
+find_damaged_tiles (ClutterStageView      *view,
+                    const cairo_region_t  *damage_region,
+                    GError               **error)
 {
   ClutterStageViewPrivate *priv =
     clutter_stage_view_get_instance_private (view);
-  g_autoptr (GError) error = NULL;
+  cairo_region_t *tile_damage_region;
+  cairo_rectangle_int_t damage_extents;
+  cairo_rectangle_int_t fb_rect;
+  int prev_dma_buf_idx;
+  CoglDmaBufHandle *prev_dma_buf_handle;
+  uint8_t *prev_data;
+  int current_dma_buf_idx;
+  CoglDmaBufHandle *current_dma_buf_handle;
+  uint8_t *current_data;
+  int width, height, stride, bpp;
+  int tile_x_min, tile_x_max;
+  int tile_y_min, tile_y_max;
+  int tile_x, tile_y;
+  const int tile_size = 16;
+
+  prev_dma_buf_idx = flip_dma_buf_idx (priv->shadow.dma_buf.current_idx);
+  prev_dma_buf_handle = priv->shadow.dma_buf.handles[prev_dma_buf_idx];
+
+  current_dma_buf_idx = priv->shadow.dma_buf.current_idx;
+  current_dma_buf_handle = priv->shadow.dma_buf.handles[current_dma_buf_idx];
+
+  width = cogl_dma_buf_handle_get_width (current_dma_buf_handle);
+  height = cogl_dma_buf_handle_get_height (current_dma_buf_handle);
+  stride = cogl_dma_buf_handle_get_stride (current_dma_buf_handle);
+  bpp = cogl_dma_buf_handle_get_bpp (current_dma_buf_handle);
+
+  cogl_framebuffer_finish (priv->shadow.framebuffer);
+
+  if (!cogl_dma_buf_handle_sync_read_start (prev_dma_buf_handle, error))
+    return NULL;
+
+  if (!cogl_dma_buf_handle_sync_read_start (current_dma_buf_handle, error))
+    goto err_sync_read_current;
+
+  prev_data = cogl_dma_buf_handle_mmap (prev_dma_buf_handle, error);
+  if (!prev_data)
+    goto err_mmap_prev;
+  current_data = cogl_dma_buf_handle_mmap (current_dma_buf_handle, error);
+  if (!current_data)
+    goto err_mmap_current;
+
+  fb_rect = (cairo_rectangle_int_t) {
+    .width = width,
+    .height = height,
+  };
+
+  cairo_region_get_extents (damage_region, &damage_extents);
+
+  tile_x_min = damage_extents.x / tile_size;
+  tile_x_max = ((damage_extents.x + damage_extents.width + tile_size - 1) /
+                tile_size);
+  tile_y_min = damage_extents.y / tile_size;
+  tile_y_max = ((damage_extents.y + damage_extents.height + tile_size - 1) /
+                tile_size);
+
+  tile_damage_region = cairo_region_create ();
+
+  for (tile_y = tile_y_min; tile_y <= tile_y_max; tile_y++)
+    {
+      for (tile_x = tile_x_min; tile_x <= tile_x_max; tile_x++)
+        {
+          cairo_rectangle_int_t tile = {
+            .x = tile_x * tile_size,
+            .y = tile_y * tile_size,
+            .width = tile_size,
+            .height = tile_size,
+          };
 
-  if (!priv->shadow.framebuffer)
-    return;
+          if (cairo_region_contains_rectangle (damage_region, &tile) ==
+              CAIRO_REGION_OVERLAP_OUT)
+            continue;
 
-  if (cairo_region_is_empty (swap_region))
+          _clutter_util_rectangle_intersection (&tile, &fb_rect, &tile);
+
+          if (is_tile_dirty (&tile, current_data, prev_data, bpp, stride))
+            cairo_region_union_rectangle (tile_damage_region, &tile);
+        }
+    }
+
+  if (!cogl_dma_buf_handle_sync_read_end (prev_dma_buf_handle, error))
     {
-      int width, height;
+      g_warning ("Failed to end DMA buffer read synchronization: %s",
+                 (*error)->message);
+      g_clear_error (error);
+    }
 
-      width = cogl_framebuffer_get_width (priv->framebuffer);
-      height = cogl_framebuffer_get_height (priv->framebuffer);
-      if (!cogl_blit_framebuffer (priv->shadow.framebuffer,
-                                  priv->framebuffer,
-                                  0, 0,
-                                  0, 0,
-                                  width, height,
-                                  &error))
-        g_warning ("Failed to blit shadow buffer: %s", error->message);
+  if (!cogl_dma_buf_handle_sync_read_end (current_dma_buf_handle, error))
+    {
+      g_warning ("Failed to end DMA buffer read synchronization: %s",
+                 (*error)->message);
+      g_clear_error (error);
+    }
+
+  cogl_dma_buf_handle_munmap (prev_dma_buf_handle, prev_data, NULL);
+  cogl_dma_buf_handle_munmap (current_dma_buf_handle, current_data, NULL);
+
+  cairo_region_intersect (tile_damage_region, damage_region);
+
+  return tile_damage_region;
+
+err_mmap_current:
+  cogl_dma_buf_handle_munmap (prev_dma_buf_handle, prev_data, NULL);
+
+err_mmap_prev:
+  cogl_dma_buf_handle_sync_read_end (current_dma_buf_handle, NULL);
+
+err_sync_read_current:
+  cogl_dma_buf_handle_sync_read_end (prev_dma_buf_handle, NULL);
+
+  return NULL;
+}
+
+static void
+swap_dma_buf_framebuffer (ClutterStageView *view)
+{
+  ClutterStageViewPrivate *priv =
+    clutter_stage_view_get_instance_private (view);
+  int next_idx;
+  CoglDmaBufHandle *next_dma_buf_handle;
+  CoglOffscreen *next_framebuffer;
+
+  next_idx = ((priv->shadow.dma_buf.current_idx + 1) %
+              G_N_ELEMENTS (priv->shadow.dma_buf.handles));
+  priv->shadow.dma_buf.current_idx = next_idx;
+
+  next_dma_buf_handle = priv->shadow.dma_buf.handles[next_idx];
+  next_framebuffer =
+    cogl_dma_buf_handle_get_framebuffer (next_dma_buf_handle);
+  cogl_clear_object (&priv->shadow.framebuffer);
+  priv->shadow.framebuffer = cogl_object_ref (next_framebuffer);
+}
+
+static void
+copy_shadowfb_to_onscreen (ClutterStageView     *view,
+                           const cairo_region_t *swap_region)
+{
+  ClutterStageViewPrivate *priv =
+    clutter_stage_view_get_instance_private (view);
+  ClutterDamageHistory *damage_history = priv->shadow.dma_buf.damage_history;
+  cairo_region_t *damage_region;
+  int age;
+  int i;
+
+  if (cairo_region_is_empty (swap_region))
+    {
+      cairo_rectangle_int_t full_damage = {
+        .width = cogl_framebuffer_get_width (priv->framebuffer),
+        .height = cogl_framebuffer_get_height (priv->framebuffer),
+      };
+      damage_region = cairo_region_create_rectangle (&full_damage);
     }
   else
     {
-      int n_rects;
-      int i;
+      damage_region = cairo_region_copy (swap_region);
+    }
+
+  if (is_shadowfb_double_buffered (view))
+    {
+      CoglOnscreen *onscreen = COGL_ONSCREEN (priv->framebuffer);
+      cairo_region_t *changed_region;
 
-      n_rects = cairo_region_num_rectangles (swap_region);
-      for (i = 0; i < n_rects; i++)
+      if (cogl_onscreen_get_frame_counter (onscreen) >= 1)
         {
-          cairo_rectangle_int_t rect;
-
-          cairo_region_get_rectangle (swap_region, i, &rect);
-          if (!cogl_blit_framebuffer (priv->shadow.framebuffer,
-                                      priv->framebuffer,
-                                      rect.x, rect.y,
-                                      rect.x, rect.y,
-                                      rect.width, rect.height,
-                                      &error))
+          g_autoptr (GError) error = NULL;
+
+          changed_region = find_damaged_tiles (view, damage_region, &error);
+          if (!changed_region)
             {
-              g_warning ("Failed to blit shadow buffer: %s", error->message);
-              return;
+              int other_dma_buf_idx;
+
+              g_warning ("Disabling actual damage detection: %s",
+                         error->message);
+
+              other_dma_buf_idx =
+                flip_dma_buf_idx (priv->shadow.dma_buf.current_idx);
+              g_clear_pointer (&priv->shadow.dma_buf.handles[other_dma_buf_idx],
+                               cogl_dma_buf_handle_free);
             }
         }
+      else
+        {
+          changed_region = cairo_region_copy (damage_region);
+        }
+
+      if (changed_region)
+        {
+          int buffer_age;
+
+          clutter_damage_history_record (damage_history, changed_region);
+
+          buffer_age = cogl_onscreen_get_buffer_age (onscreen);
+          if (clutter_damage_history_is_age_valid (damage_history, buffer_age))
+            {
+              for (age = 1; age <= buffer_age; age++)
+                {
+                  const cairo_region_t *old_damage;
+
+                  old_damage = clutter_damage_history_lookup (damage_history, age);
+                  cairo_region_union (changed_region, old_damage);
+                }
+
+              cairo_region_destroy (damage_region);
+              damage_region = g_steal_pointer (&changed_region);
+            }
+          else
+            {
+              cairo_region_destroy (changed_region);
+            }
+
+          clutter_damage_history_step (damage_history);
+        }
+    }
+
+  for (i = 0; i < cairo_region_num_rectangles (damage_region); i++)
+    {
+      g_autoptr (GError) error = NULL;
+      cairo_rectangle_int_t rect;
+
+      cairo_region_get_rectangle (damage_region, i, &rect);
+
+      if (!cogl_blit_framebuffer (priv->shadow.framebuffer,
+                                  priv->framebuffer,
+                                  rect.x, rect.y,
+                                  rect.x, rect.y,
+                                  rect.width, rect.height,
+                                  &error))
+        {
+          g_warning ("Failed to blit shadow buffer: %s", error->message);
+          cairo_region_destroy (damage_region);
+          return;
+        }
     }
+
+  cairo_region_destroy (damage_region);
+
+  if (is_shadowfb_double_buffered (view))
+    swap_dma_buf_framebuffer (view);
+}
+
+void
+clutter_stage_view_before_swap_buffer (ClutterStageView     *view,
+                                       const cairo_region_t *swap_region)
+{
+  ClutterStageViewPrivate *priv =
+    clutter_stage_view_get_instance_private (view);
+
+  if (priv->shadow.framebuffer)
+    copy_shadowfb_to_onscreen (view, swap_region);
 }
 
 float
@@ -431,6 +741,47 @@ clutter_stage_view_get_scale (ClutterStageView *view)
   return priv->scale;
 }
 
+typedef void (*FrontBufferCallback) (CoglFramebuffer *framebuffer,
+                                     gconstpointer    user_data);
+
+static void
+clutter_stage_view_foreach_front_buffer (ClutterStageView    *view,
+                                         FrontBufferCallback  callback,
+                                         gconstpointer        user_data)
+{
+  ClutterStageViewPrivate *priv =
+    clutter_stage_view_get_instance_private (view);
+
+  if (priv->offscreen)
+    {
+      callback (priv->offscreen, user_data);
+    }
+  else if (priv->shadow.framebuffer)
+    {
+      if (is_shadowfb_double_buffered (view))
+        {
+          int i;
+
+          for (i = 0; i < G_N_ELEMENTS (priv->shadow.dma_buf.handles); i++)
+            {
+              CoglDmaBufHandle *handle = priv->shadow.dma_buf.handles[i];
+              CoglFramebuffer *framebuffer =
+                cogl_dma_buf_handle_get_framebuffer (handle);
+
+              callback (framebuffer, user_data);
+            }
+        }
+      else
+        {
+          callback (priv->shadow.framebuffer, user_data);
+        }
+    }
+  else
+    {
+      callback (priv->framebuffer, user_data);
+    }
+}
+
 gboolean
 clutter_stage_view_is_dirty_viewport (ClutterStageView *view)
 {
@@ -449,6 +800,19 @@ clutter_stage_view_invalidate_viewport (ClutterStageView *view)
   priv->dirty_viewport = TRUE;
 }
 
+static void
+set_framebuffer_viewport (CoglFramebuffer *framebuffer,
+                          gconstpointer    user_data)
+{
+  const graphene_rect_t *rect = user_data;
+
+  cogl_framebuffer_set_viewport (framebuffer,
+                                 rect->origin.x,
+                                 rect->origin.y,
+                                 rect->size.width,
+                                 rect->size.height);
+}
+
 void
 clutter_stage_view_set_viewport (ClutterStageView *view,
                                  float             x,
@@ -458,11 +822,17 @@ clutter_stage_view_set_viewport (ClutterStageView *view,
 {
   ClutterStageViewPrivate *priv =
     clutter_stage_view_get_instance_private (view);
-  CoglFramebuffer *framebuffer;
+  graphene_rect_t rect;
 
   priv->dirty_viewport = FALSE;
-  framebuffer = clutter_stage_view_get_framebuffer (view);
-  cogl_framebuffer_set_viewport (framebuffer, x, y, width, height);
+
+  rect = (graphene_rect_t) {
+    .origin = { .x = x, .y = y },
+    .size = { .width = width, .height = height },
+  };
+  clutter_stage_view_foreach_front_buffer (view,
+                                           set_framebuffer_viewport,
+                                           &rect);
 }
 
 gboolean
@@ -474,6 +844,13 @@ clutter_stage_view_is_dirty_projection (ClutterStageView *view)
   return priv->dirty_projection;
 }
 
+static void
+set_framebuffer_projection_matrix (CoglFramebuffer *framebuffer,
+                                   gconstpointer    user_data)
+{
+  cogl_framebuffer_set_projection_matrix (framebuffer, user_data);
+}
+
 void
 clutter_stage_view_invalidate_projection (ClutterStageView *view)
 {
@@ -489,11 +866,11 @@ clutter_stage_view_set_projection (ClutterStageView *view,
 {
   ClutterStageViewPrivate *priv =
     clutter_stage_view_get_instance_private (view);
-  CoglFramebuffer *framebuffer;
 
   priv->dirty_projection = FALSE;
-  framebuffer = clutter_stage_view_get_framebuffer (view);
-  cogl_framebuffer_set_projection_matrix (framebuffer, matrix);
+  clutter_stage_view_foreach_front_buffer (view,
+                                           set_framebuffer_projection_matrix,
+                                           matrix);
 }
 
 void
@@ -716,10 +1093,20 @@ clutter_stage_view_dispose (GObject *object)
   ClutterStageView *view = CLUTTER_STAGE_VIEW (object);
   ClutterStageViewPrivate *priv =
     clutter_stage_view_get_instance_private (view);
+  int i;
 
   g_clear_pointer (&priv->name, g_free);
   g_clear_pointer (&priv->framebuffer, cogl_object_unref);
+
   g_clear_pointer (&priv->shadow.framebuffer, cogl_object_unref);
+  for (i = 0; i < G_N_ELEMENTS (priv->shadow.dma_buf.handles); i++)
+    {
+      g_clear_pointer (&priv->shadow.dma_buf.handles[i],
+                       cogl_dma_buf_handle_free);
+    }
+  g_clear_pointer (&priv->shadow.dma_buf.damage_history,
+                   clutter_damage_history_free);
+
   g_clear_pointer (&priv->offscreen, cogl_object_unref);
   g_clear_pointer (&priv->offscreen_pipeline, cogl_object_unref);
   g_clear_pointer (&priv->redraw_clip, cairo_region_destroy);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]