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
This commit is contained in:
Alexander Larsson 2020-08-26 18:08:00 +02:00
parent 4b1ea7c4a1
commit d57e6b754f
2 changed files with 107 additions and 27 deletions

View File

@ -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,

View File

@ -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,11 +229,10 @@ 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);
}
}
static void
@ -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,6 +371,20 @@ add_new_node (GskRenderer *renderer,
}
id = ++self->next_node_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);
@ -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++)
{
/* 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,
gsk_container_node_get_child (node, i), offset_x, offset_y, clip_bounds);
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;