From c2c4f749230c2f25a9497af2f102b6176a8af8fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Wed, 6 May 2020 22:14:17 +0200 Subject: [PATCH] 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 | 493 ++++++++++++++++++++++++--- 1 file changed, 440 insertions(+), 53 deletions(-) diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c index ac75e8dcd..efc9d67a8 100644 --- a/clutter/clutter/clutter-stage-view.c +++ b/clutter/clutter/clutter-stage-view.c @@ -23,6 +23,7 @@ #include #include +#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, } } +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); + 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 (cairo_region_contains_rectangle (damage_region, &tile) == + CAIRO_REGION_OVERLAP_OUT) + continue; + + _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)) + { + g_warning ("Failed to end DMA buffer read synchronization: %s", + (*error)->message); + g_clear_error (error); + } + + 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 + { + damage_region = cairo_region_copy (swap_region); + } + + if (is_shadowfb_double_buffered (view)) + { + CoglOnscreen *onscreen = COGL_ONSCREEN (priv->framebuffer); + cairo_region_t *changed_region; + + if (cogl_onscreen_get_frame_counter (onscreen) >= 1) + { + g_autoptr (GError) error = NULL; + + changed_region = find_damaged_tiles (view, damage_region, &error); + if (!changed_region) + { + 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); - g_autoptr (GError) error = NULL; - if (!priv->shadow.framebuffer) - return; - - if (cairo_region_is_empty (swap_region)) - { - int width, height; - - 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); - } - else - { - int n_rects; - int i; - - n_rects = cairo_region_num_rectangles (swap_region); - for (i = 0; i < n_rects; i++) - { - 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_warning ("Failed to blit shadow buffer: %s", error->message); - return; - } - } - } + 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);