From 4b1ea7c4a1c64ca52d844cfa41fc412c4917bdb6 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 26 Aug 2020 16:55:56 +0200 Subject: [PATCH 1/2] broadway: Track the clip region in the renderer If we know what can't possibly be visible we may can later decide to not expose a node. --- gsk/broadway/gskbroadwayrenderer.c | 55 ++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/gsk/broadway/gskbroadwayrenderer.c b/gsk/broadway/gskbroadwayrenderer.c index df5028169e..b56a095f73 100644 --- a/gsk/broadway/gskbroadwayrenderer.c +++ b/gsk/broadway/gskbroadwayrenderer.c @@ -467,14 +467,21 @@ get_colorized_texture (GdkTexture *texture, /* Note: This tracks the offset so that we can convert - the absolute coordinates of the GskRenderNodes to - parent-relative which is what the dom uses, and - which is good for re-using subtrees. */ + * the absolute coordinates of the GskRenderNodes to + * parent-relative which is what the dom uses, and + * which is good for re-using subtrees. + * + * We also track the clip bounds which is a best-effort + * clip region tracking (i.e. can be unset or larger + * than real clip, but not smaller). This can be used + * to avoid sending completely clipped nodes. + */ static void gsk_broadway_renderer_add_node (GskRenderer *renderer, GskRenderNode *node, float offset_x, - float offset_y) + float offset_y, + graphene_rect_t *clip_bounds) { GdkDisplay *display = gdk_surface_get_display (gsk_renderer_get_surface (renderer)); GdkBroadwayDisplay *broadway_display = GDK_BROADWAY_DISPLAY (display); @@ -617,7 +624,7 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer, } gsk_broadway_renderer_add_node (renderer, gsk_shadow_node_get_child (node), - offset_x, offset_y); + offset_x, offset_y, clip_bounds); } return; @@ -627,7 +634,7 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer, add_float (nodes, gsk_opacity_node_get_opacity (node)); gsk_broadway_renderer_add_node (renderer, gsk_opacity_node_get_child (node), - offset_x, offset_y); + offset_x, offset_y, clip_bounds); } return; @@ -635,12 +642,17 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer, if (add_new_node (renderer, node, BROADWAY_NODE_ROUNDED_CLIP)) { const GskRoundedRect *rclip = gsk_rounded_clip_node_peek_clip (node); + graphene_rect_t child_bounds = rclip->bounds; + + if (clip_bounds) + graphene_rect_intersection (&child_bounds, clip_bounds, &child_bounds); add_rounded_rect (nodes, rclip, offset_x, offset_y); gsk_broadway_renderer_add_node (renderer, gsk_rounded_clip_node_get_child (node), rclip->bounds.origin.x, - rclip->bounds.origin.y); + rclip->bounds.origin.y, + &child_bounds); } return; @@ -648,12 +660,17 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer, if (add_new_node (renderer, node, BROADWAY_NODE_CLIP)) { const graphene_rect_t *clip = gsk_clip_node_peek_clip (node); + graphene_rect_t child_bounds = *clip; + + if (clip_bounds) + graphene_rect_intersection (&child_bounds, clip_bounds, &child_bounds); add_rect (nodes, clip, offset_x, offset_y); gsk_broadway_renderer_add_node (renderer, gsk_clip_node_get_child (node), clip->origin.x, - clip->origin.y); + clip->origin.y, + &child_bounds); } return; @@ -666,13 +683,22 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer, if (category >= GSK_TRANSFORM_CATEGORY_2D_TRANSLATE) { float dx, dy; - gsk_transform_to_translate (transform, &dx, &dy); + graphene_rect_t child_bounds; + graphene_rect_t *child_bounds_p = NULL; + gsk_transform_to_translate (transform, &dx, &dy); add_uint32 (nodes, 0); // Translate add_xy (nodes, dx, dy, 0, 0); + + if (clip_bounds) + { + graphene_rect_offset_r (clip_bounds, -dx, -dy, &child_bounds); + child_bounds_p = &child_bounds; + } + gsk_broadway_renderer_add_node (renderer, gsk_transform_node_get_child (node), - 0, 0); + 0, 0, child_bounds_p); } else { @@ -681,9 +707,10 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer, gsk_transform_to_matrix (transform, &matrix); add_uint32 (nodes, 1); // General transform add_matrix (nodes, &matrix); + // We just drop the clip bounds here to make things simpler gsk_broadway_renderer_add_node (renderer, gsk_transform_node_get_child (node), - 0, 0); + 0, 0, NULL); } } } @@ -695,7 +722,7 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer, const char *message = gsk_debug_node_get_message (node); add_string (nodes, message); gsk_broadway_renderer_add_node (renderer, - gsk_debug_node_get_child (node), offset_x, offset_y); + gsk_debug_node_get_child (node), offset_x, offset_y, clip_bounds); } return; @@ -709,7 +736,7 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer, add_uint32 (nodes, gsk_container_node_get_n_children (node)); for (i = 0; i < gsk_container_node_get_n_children (node); i++) gsk_broadway_renderer_add_node (renderer, - gsk_container_node_get_child (node, i), offset_x, offset_y); + gsk_container_node_get_child (node, i), offset_x, offset_y, clip_bounds); } return; @@ -798,7 +825,7 @@ gsk_broadway_renderer_render (GskRenderer *renderer, self->nodes = self->draw_context->nodes; self->node_textures = self->draw_context->node_textures; - gsk_broadway_renderer_add_node (renderer, root, 0, 0); + gsk_broadway_renderer_add_node (renderer, root, 0, 0, NULL); self->nodes = NULL; self->node_textures = NULL; From d57e6b754f00bf9ec6e49812f57195c66a8a6f9e Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 26 Aug 2020 18:08:00 +0200 Subject: [PATCH 2/2] broadway: Prune fully clipped render nodes If some node is fully outside the clip region we don't send it to the daemon. This helps a lot in how much data we send for scrolling viewports. However, sending partial trees makes node reuse a bit more tricky. We can't save for reuse any node that could possibly clip different depending on the clip region, as that could be different next frame. So, unless the node is fully contained in the current clip (and we thus know it is not parial) we don't allow reusing that next frame. This fixes #3086 --- gdk/broadway/broadway-protocol.h | 2 +- gsk/broadway/gskbroadwayrenderer.c | 132 +++++++++++++++++++++++------ 2 files changed, 107 insertions(+), 27 deletions(-) diff --git a/gdk/broadway/broadway-protocol.h b/gdk/broadway/broadway-protocol.h index 35fb02cab2..9b56dff6df 100644 --- a/gdk/broadway/broadway-protocol.h +++ b/gdk/broadway/broadway-protocol.h @@ -8,7 +8,7 @@ typedef struct { gint32 width, height; } BroadwayRect; -typedef enum { /* Sync changes with broadway.js */ +typedef enum { /* Sync changes with broadway.js and node_type_is_container() */ BROADWAY_NODE_TEXTURE = 0, BROADWAY_NODE_CONTAINER = 1, BROADWAY_NODE_COLOR = 2, diff --git a/gsk/broadway/gskbroadwayrenderer.c b/gsk/broadway/gskbroadwayrenderer.c index b56a095f73..e18b03f826 100644 --- a/gsk/broadway/gskbroadwayrenderer.c +++ b/gsk/broadway/gskbroadwayrenderer.c @@ -90,6 +90,23 @@ add_uint32 (GArray *nodes, guint32 v) g_array_append_val (nodes, v); } +static guint +add_uint32_placeholder (GArray *nodes) +{ + guint pos = nodes->len; + guint32 v = 0; + + g_array_append_val (nodes, v); + return pos; +} + +static void +set_uint32_at (GArray *nodes, guint index, guint32 v) +{ + g_array_index (nodes, guint32, index) = v; +} + + static void add_float (GArray *nodes, float f) { @@ -212,10 +229,9 @@ collect_reused_node (GskRenderer *renderer, if (self->last_node_lookup && (old_id = GPOINTER_TO_INT(g_hash_table_lookup (self->last_node_lookup, node))) != 0) - { - g_hash_table_insert (self->node_lookup, node, GINT_TO_POINTER (old_id)); - collect_reused_child_nodes (renderer, node); - } + g_hash_table_insert (self->node_lookup, node, GINT_TO_POINTER (old_id)); + + collect_reused_child_nodes (renderer, node); } @@ -298,10 +314,46 @@ collect_reused_child_nodes (GskRenderer *renderer, } } +static gboolean +node_is_visible (GskRenderNode *node, + graphene_rect_t *clip_bounds) +{ + if (clip_bounds == NULL || + graphene_rect_intersection (clip_bounds, &node->bounds, NULL)) + return TRUE; + + return FALSE; +} + +static gboolean +node_is_fully_visible (GskRenderNode *node, + graphene_rect_t *clip_bounds) +{ + if (clip_bounds == NULL || + graphene_rect_contains_rect (clip_bounds, &node->bounds)) + return TRUE; + + return FALSE; +} + +static gboolean +node_type_is_container (BroadwayNodeType type) +{ + return + type == BROADWAY_NODE_SHADOW || + type == BROADWAY_NODE_OPACITY || + type == BROADWAY_NODE_ROUNDED_CLIP || + type == BROADWAY_NODE_CLIP || + type == BROADWAY_NODE_TRANSFORM || + type == BROADWAY_NODE_DEBUG || + type == BROADWAY_NODE_CONTAINER; +} + static gboolean add_new_node (GskRenderer *renderer, GskRenderNode *node, - BroadwayNodeType type) + BroadwayNodeType type, + graphene_rect_t *clip_bounds) { GskBroadwayRenderer *self = GSK_BROADWAY_RENDERER (renderer); guint32 id, old_id; @@ -319,7 +371,21 @@ add_new_node (GskRenderer *renderer, } id = ++self->next_node_id; - g_hash_table_insert (self->node_lookup, node, GINT_TO_POINTER(id)); + + /* Never try to reuse partially visible container types the next + * frame, as they could be be partial due to pruning against clip_bounds, + * and the clip_bounds may be different the next frame. However, anything + * that is fully visible will not be pruned, so is ok to reuse. + * + * Note: its quite possible that the node is fully visible, but contains + * a clip node which means the tree under that partial. That is fine and we can + * still reuse *this* node next frame, but we can't use the child that is + * partial, for example in a different place, because then it might see + * the partial region of the tree. + */ + if (!node_type_is_container (type) || + node_is_fully_visible (node, clip_bounds)) + g_hash_table_insert (self->node_lookup, node, GINT_TO_POINTER(id)); add_uint32 (self->nodes, type); add_uint32 (self->nodes, id); @@ -497,7 +563,7 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer, /* Leaf nodes */ case GSK_TEXTURE_NODE: - if (add_new_node (renderer, node, BROADWAY_NODE_TEXTURE)) + if (add_new_node (renderer, node, BROADWAY_NODE_TEXTURE, clip_bounds)) { GdkTexture *texture = gsk_texture_node_get_texture (node); guint32 texture_id; @@ -512,7 +578,7 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer, return; case GSK_CAIRO_NODE: - if (add_new_node (renderer, node, BROADWAY_NODE_TEXTURE)) + if (add_new_node (renderer, node, BROADWAY_NODE_TEXTURE, clip_bounds)) { cairo_surface_t *surface = gsk_cairo_node_peek_surface (node); cairo_surface_t *image_surface = NULL; @@ -548,7 +614,7 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer, return; case GSK_COLOR_NODE: - if (add_new_node (renderer, node, BROADWAY_NODE_COLOR)) + if (add_new_node (renderer, node, BROADWAY_NODE_COLOR, clip_bounds)) { add_rect (nodes, &node->bounds, offset_x, offset_y); add_rgba (nodes, gsk_color_node_peek_color (node)); @@ -556,7 +622,7 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer, return; case GSK_BORDER_NODE: - if (add_new_node (renderer, node, BROADWAY_NODE_BORDER)) + if (add_new_node (renderer, node, BROADWAY_NODE_BORDER, clip_bounds)) { int i; add_rounded_rect (nodes, gsk_border_node_peek_outline (node), offset_x, offset_y); @@ -568,7 +634,7 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer, return; case GSK_OUTSET_SHADOW_NODE: - if (add_new_node (renderer, node, BROADWAY_NODE_OUTSET_SHADOW)) + if (add_new_node (renderer, node, BROADWAY_NODE_OUTSET_SHADOW, clip_bounds)) { add_rounded_rect (nodes, gsk_outset_shadow_node_peek_outline (node), offset_x, offset_y); add_rgba (nodes, gsk_outset_shadow_node_peek_color (node)); @@ -580,7 +646,7 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer, return; case GSK_INSET_SHADOW_NODE: - if (add_new_node (renderer, node, BROADWAY_NODE_INSET_SHADOW)) + if (add_new_node (renderer, node, BROADWAY_NODE_INSET_SHADOW, clip_bounds)) { add_rounded_rect (nodes, gsk_inset_shadow_node_peek_outline (node), offset_x, offset_y); add_rgba (nodes, gsk_inset_shadow_node_peek_color (node)); @@ -592,7 +658,7 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer, return; case GSK_LINEAR_GRADIENT_NODE: - if (add_new_node (renderer, node, BROADWAY_NODE_LINEAR_GRADIENT)) + if (add_new_node (renderer, node, BROADWAY_NODE_LINEAR_GRADIENT, clip_bounds)) { guint i, n; @@ -609,7 +675,7 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer, /* Bin nodes */ case GSK_SHADOW_NODE: - if (add_new_node (renderer, node, BROADWAY_NODE_SHADOW)) + if (add_new_node (renderer, node, BROADWAY_NODE_SHADOW, clip_bounds)) { gsize i, n_shadows = gsk_shadow_node_get_n_shadows (node); @@ -629,7 +695,7 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer, return; case GSK_OPACITY_NODE: - if (add_new_node (renderer, node, BROADWAY_NODE_OPACITY)) + if (add_new_node (renderer, node, BROADWAY_NODE_OPACITY, clip_bounds)) { add_float (nodes, gsk_opacity_node_get_opacity (node)); gsk_broadway_renderer_add_node (renderer, @@ -639,7 +705,7 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer, return; case GSK_ROUNDED_CLIP_NODE: - if (add_new_node (renderer, node, BROADWAY_NODE_ROUNDED_CLIP)) + if (add_new_node (renderer, node, BROADWAY_NODE_ROUNDED_CLIP, clip_bounds)) { const GskRoundedRect *rclip = gsk_rounded_clip_node_peek_clip (node); graphene_rect_t child_bounds = rclip->bounds; @@ -657,7 +723,7 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer, return; case GSK_CLIP_NODE: - if (add_new_node (renderer, node, BROADWAY_NODE_CLIP)) + if (add_new_node (renderer, node, BROADWAY_NODE_CLIP, clip_bounds)) { const graphene_rect_t *clip = gsk_clip_node_peek_clip (node); graphene_rect_t child_bounds = *clip; @@ -679,7 +745,7 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer, GskTransform *transform = gsk_transform_node_get_transform (node); GskTransformCategory category = gsk_transform_get_category (transform); - if (add_new_node (renderer, node, BROADWAY_NODE_TRANSFORM)) { + if (add_new_node (renderer, node, BROADWAY_NODE_TRANSFORM, clip_bounds)) { if (category >= GSK_TRANSFORM_CATEGORY_2D_TRANSLATE) { float dx, dy; @@ -717,7 +783,7 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer, return; case GSK_DEBUG_NODE: - if (add_new_node (renderer, node, BROADWAY_NODE_DEBUG)) + if (add_new_node (renderer, node, BROADWAY_NODE_DEBUG, clip_bounds)) { const char *message = gsk_debug_node_get_message (node); add_string (nodes, message); @@ -729,14 +795,28 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer, /* Generic nodes */ case GSK_CONTAINER_NODE: - if (add_new_node (renderer, node, BROADWAY_NODE_CONTAINER)) + if (add_new_node (renderer, node, BROADWAY_NODE_CONTAINER, clip_bounds)) { - guint i; + guint i, placeholder; + guint32 n_children = 0; - add_uint32 (nodes, gsk_container_node_get_n_children (node)); + placeholder = add_uint32_placeholder (nodes); for (i = 0; i < gsk_container_node_get_n_children (node); i++) - gsk_broadway_renderer_add_node (renderer, - gsk_container_node_get_child (node, i), offset_x, offset_y, clip_bounds); + { + /* We prune fully clipped children, but we only do this for container_node, as + * we don't have a way for any other nodes to say there are children missing (i.e. + * bins always assume there is a child). + * Pruning is really only useful for large sets of children anyway, so thats + * probably fine. */ + GskRenderNode *child = gsk_container_node_get_child (node, i); + if (node_is_visible (child, clip_bounds)) + { + n_children++; + gsk_broadway_renderer_add_node (renderer, + child, offset_x, offset_y, clip_bounds); + } + } + set_uint32_at (nodes, placeholder, n_children); } return; @@ -749,7 +829,7 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer, const graphene_vec4_t *color_offset = gsk_color_matrix_node_peek_color_offset (node); GdkTexture *texture = gsk_texture_node_get_texture (child); GdkTexture *colorized_texture = get_colorized_texture (texture, color_matrix, color_offset); - if (add_new_node (renderer, node, BROADWAY_NODE_TEXTURE)) + if (add_new_node (renderer, node, BROADWAY_NODE_TEXTURE, clip_bounds)) { guint32 texture_id = gdk_broadway_display_ensure_texture (display, colorized_texture); add_rect (nodes, &child->bounds, offset_x, offset_y); @@ -771,7 +851,7 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer, break; /* Fallback */ } - if (add_new_node (renderer, node, BROADWAY_NODE_TEXTURE)) + if (add_new_node (renderer, node, BROADWAY_NODE_TEXTURE, clip_bounds)) { GdkTexture *texture; cairo_surface_t *surface;