gtk/gsk/gskbroadwayrenderer.c
Alexander Larsson fbefec52a5 Broadway: Add id for nodes and reuse old ones
When sending render nodes from the client to the daemon we add an id,
and whenever we're about to re-send the entire tree node we instead
send the old id. We track all the nodes for the previous frame
of the surface this way.

Having the id on the daemon side will allow us do to much better deltas.
2019-03-26 17:07:47 +01:00

653 lines
20 KiB
C

#include "config.h"
#include "gskbroadwayrendererprivate.h"
#include "broadway/gdkprivate-broadway.h"
#include "gskdebugprivate.h"
#include "gsktransformprivate.h"
#include "gskrendererprivate.h"
#include "gskrendernodeprivate.h"
#include "gdk/gdktextureprivate.h"
struct _GskBroadwayRenderer
{
GskRenderer parent_instance;
GdkBroadwayDrawContext *draw_context;
guint32 next_node_id;
/* Set during rendering */
GArray *nodes; /* Owned by draw_contex */
GPtrArray *node_textures; /* Owned by draw_contex */
GHashTable *node_lookup;
/* Kept from last frame */
GHashTable *last_node_lookup;
GskRenderNode *last_root; /* Owning refs to the things in last_node_lookup */
};
struct _GskBroadwayRendererClass
{
GskRendererClass parent_class;
};
G_DEFINE_TYPE (GskBroadwayRenderer, gsk_broadway_renderer, GSK_TYPE_RENDERER)
static gboolean
gsk_broadway_renderer_realize (GskRenderer *renderer,
GdkSurface *surface,
GError **error)
{
GskBroadwayRenderer *self = GSK_BROADWAY_RENDERER (renderer);
if (!GDK_IS_BROADWAY_SURFACE (surface))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"Broadway renderer only works for broadway surfaces");
return FALSE;
}
self->draw_context = gdk_broadway_draw_context_context (surface);
return TRUE;
}
static void
gsk_broadway_renderer_unrealize (GskRenderer *renderer)
{
GskBroadwayRenderer *self = GSK_BROADWAY_RENDERER (renderer);
g_clear_object (&self->draw_context);
}
static GdkTexture *
gsk_broadway_renderer_render_texture (GskRenderer *renderer,
GskRenderNode *root,
const graphene_rect_t *viewport)
{
GdkTexture *texture;
cairo_surface_t *surface;
cairo_t *cr;
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, ceil (viewport->size.width), ceil (viewport->size.height));
cr = cairo_create (surface);
cairo_translate (cr, - viewport->origin.x, - viewport->origin.y);
gsk_render_node_draw (root, cr);
cairo_destroy (cr);
texture = gdk_texture_new_for_surface (surface);
cairo_surface_destroy (surface);
return texture;
}
/* uint32 is sent in native endianness, and then converted to little endian in broadwayd when sending to browser */
static void
add_uint32 (GArray *nodes, guint32 v)
{
g_array_append_val (nodes, v);
}
static void
add_float (GArray *nodes, float f)
{
union {
float f;
guint32 i;
} u;
u.f = f;
g_array_append_val (nodes, u.i);
}
static guint32
rgba_to_uint32 (const GdkRGBA *rgba)
{
return
((guint32)(0.5 + CLAMP (rgba->alpha, 0., 1.) * 255.) << 24) |
((guint32)(0.5 + CLAMP (rgba->red, 0., 1.) * 255.) << 16) |
((guint32)(0.5 + CLAMP (rgba->green, 0., 1.) * 255.) << 8) |
((guint32)(0.5 + CLAMP (rgba->blue, 0., 1.) * 255.) << 0);
}
static void
add_rgba (GArray *nodes, const GdkRGBA *rgba)
{
guint32 c = rgba_to_uint32 (rgba);
g_array_append_val (nodes, c);
}
static void
add_xy (GArray *nodes, float x, float y, float offset_x, float offset_y)
{
add_float (nodes, x - offset_x);
add_float (nodes, y - offset_y);
}
static void
add_point (GArray *nodes, const graphene_point_t *point, float offset_x, float offset_y)
{
add_xy (nodes, point->x, point->y, offset_x, offset_y);
}
static void
add_size (GArray *nodes, const graphene_size_t *size)
{
add_float (nodes, size->width);
add_float (nodes, size->height);
}
static void
add_rect (GArray *nodes, const graphene_rect_t *rect, float offset_x, float offset_y)
{
add_point (nodes, &rect->origin, offset_x, offset_y);
add_size (nodes, &rect->size);
}
static void
add_rounded_rect (GArray *nodes, const GskRoundedRect *rrect, float offset_x, float offset_y)
{
int i;
add_rect (nodes, &rrect->bounds, offset_x, offset_y);
for (i = 0; i < 4; i++)
add_size (nodes, &rrect->corner[i]);
}
static void
add_color_stop (GArray *nodes, const GskColorStop *stop)
{
add_float (nodes, stop->offset);
add_rgba (nodes, &stop->color);
}
static void
add_string (GArray *nodes, const char *str)
{
guint32 len = strlen(str);
guint32 v, c;
add_uint32 (nodes, len);
v = 0;
c = 0;
while (*str != 0)
{
v |= (*str++) << 8*c++;
if (c == 4)
{
add_uint32 (nodes, v);
v = 0;
c = 0;
}
}
if (c != 0)
add_uint32 (nodes, v);
}
static void
collect_reused_child_nodes (GskRenderer *renderer,
GskRenderNode *node);
static void
collect_reused_node (GskRenderer *renderer,
GskRenderNode *node)
{
GskBroadwayRenderer *self = GSK_BROADWAY_RENDERER (renderer);
guint32 old_id;
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
collect_reused_child_nodes (GskRenderer *renderer,
GskRenderNode *node)
{
guint i;
switch (gsk_render_node_get_node_type (node))
{
case GSK_NOT_A_RENDER_NODE:
g_assert_not_reached ();
return;
/* Leaf nodes */
case GSK_TEXTURE_NODE:
case GSK_CAIRO_NODE:
case GSK_COLOR_NODE:
case GSK_BORDER_NODE:
case GSK_OUTSET_SHADOW_NODE:
case GSK_INSET_SHADOW_NODE:
case GSK_LINEAR_GRADIENT_NODE:
/* Fallbacks (=> leaf for now */
case GSK_COLOR_MATRIX_NODE:
case GSK_TEXT_NODE:
case GSK_REPEATING_LINEAR_GRADIENT_NODE:
case GSK_REPEAT_NODE:
case GSK_BLEND_NODE:
case GSK_CROSS_FADE_NODE:
case GSK_BLUR_NODE:
default:
break;
/* Bin nodes */
case GSK_SHADOW_NODE:
collect_reused_node (renderer,
gsk_shadow_node_get_child (node));
break;
case GSK_OPACITY_NODE:
collect_reused_node (renderer,
gsk_opacity_node_get_child (node));
break;
case GSK_ROUNDED_CLIP_NODE:
collect_reused_node (renderer,
gsk_rounded_clip_node_get_child (node));
break;
case GSK_CLIP_NODE:
collect_reused_node (renderer,
gsk_clip_node_get_child (node));
break;
case GSK_TRANSFORM_NODE:
collect_reused_node (renderer,
gsk_transform_node_get_child (node));
break;
case GSK_DEBUG_NODE:
collect_reused_node (renderer,
gsk_debug_node_get_child (node));
break;
/* Generic nodes */
case GSK_CONTAINER_NODE:
for (i = 0; i < gsk_container_node_get_n_children (node); i++)
collect_reused_node (renderer,
gsk_container_node_get_child (node, i));
break;
break; /* Fallback */
}
}
static gboolean
add_new_node (GskRenderer *renderer,
GskRenderNode *node,
BroadwayNodeType type)
{
GskBroadwayRenderer *self = GSK_BROADWAY_RENDERER (renderer);
guint32 id, old_id;
if (self->last_node_lookup &&
(old_id = GPOINTER_TO_INT (g_hash_table_lookup (self->last_node_lookup, node))) != 0)
{
add_uint32 (self->nodes, BROADWAY_NODE_REUSE);
add_uint32 (self->nodes, old_id);
g_hash_table_insert (self->node_lookup, node, GINT_TO_POINTER(old_id));
collect_reused_child_nodes (renderer, node);
return FALSE;
}
id = ++self->next_node_id;
g_hash_table_insert (self->node_lookup, node, GINT_TO_POINTER(id));
add_uint32 (self->nodes, type);
add_uint32 (self->nodes, id);
return TRUE;
}
/* 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. */
static void
gsk_broadway_renderer_add_node (GskRenderer *renderer,
GskRenderNode *node,
float offset_x,
float offset_y)
{
GdkDisplay *display = gdk_surface_get_display (gsk_renderer_get_surface (renderer));
GskBroadwayRenderer *self = GSK_BROADWAY_RENDERER (renderer);
GArray *nodes = self->nodes;
switch (gsk_render_node_get_node_type (node))
{
case GSK_NOT_A_RENDER_NODE:
g_assert_not_reached ();
return;
/* Leaf nodes */
case GSK_TEXTURE_NODE:
if (add_new_node (renderer, node, BROADWAY_NODE_TEXTURE))
{
GdkTexture *texture = gsk_texture_node_get_texture (node);
guint32 texture_id;
/* No need to add to self->node_textures here, the node will keep it alive until end of frame. */
texture_id = gdk_broadway_display_ensure_texture (display, texture);
add_rect (nodes, &node->bounds, offset_x, offset_y);
add_uint32 (nodes, texture_id);
}
return;
case GSK_CAIRO_NODE:
if (add_new_node (renderer, node, BROADWAY_NODE_TEXTURE))
{
cairo_surface_t *surface = (cairo_surface_t *)gsk_cairo_node_peek_surface (node);
cairo_surface_t *image_surface = NULL;
GdkTexture *texture;
guint32 texture_id;
if (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE)
image_surface = cairo_surface_reference (surface);
else
{
cairo_t *cr;
image_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
ceilf (node->bounds.size.width),
ceilf (node->bounds.size.height));
cr = cairo_create (image_surface);
cairo_set_source_surface (cr, surface, 0, 0);
cairo_rectangle (cr, 0, 0, node->bounds.size.width, node->bounds.size.height);
cairo_fill (cr);
cairo_destroy (cr);
}
texture = gdk_texture_new_for_surface (image_surface);
g_ptr_array_add (self->node_textures, g_object_ref (texture)); /* Transfers ownership to node_textures */
texture_id = gdk_broadway_display_ensure_texture (display, texture);
add_rect (nodes, &node->bounds, offset_x, offset_y);
add_uint32 (nodes, texture_id);
cairo_surface_destroy (image_surface);
}
return;
case GSK_COLOR_NODE:
if (add_new_node (renderer, node, BROADWAY_NODE_COLOR))
{
add_rect (nodes, &node->bounds, offset_x, offset_y);
add_rgba (nodes, gsk_color_node_peek_color (node));
}
return;
case GSK_BORDER_NODE:
if (add_new_node (renderer, node, BROADWAY_NODE_BORDER))
{
int i;
add_rounded_rect (nodes, gsk_border_node_peek_outline (node), offset_x, offset_y);
for (i = 0; i < 4; i++)
add_float (nodes, gsk_border_node_peek_widths (node)[i]);
for (i = 0; i < 4; i++)
add_rgba (nodes, &gsk_border_node_peek_colors (node)[i]);
}
return;
case GSK_OUTSET_SHADOW_NODE:
if (add_new_node (renderer, node, BROADWAY_NODE_OUTSET_SHADOW))
{
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));
add_float (nodes, gsk_outset_shadow_node_get_dx (node));
add_float (nodes, gsk_outset_shadow_node_get_dy (node));
add_float (nodes, gsk_outset_shadow_node_get_spread (node));
add_float (nodes, gsk_outset_shadow_node_get_blur_radius (node));
}
return;
case GSK_INSET_SHADOW_NODE:
if (add_new_node (renderer, node, BROADWAY_NODE_INSET_SHADOW))
{
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));
add_float (nodes, gsk_inset_shadow_node_get_dx (node));
add_float (nodes, gsk_inset_shadow_node_get_dy (node));
add_float (nodes, gsk_inset_shadow_node_get_spread (node));
add_float (nodes, gsk_inset_shadow_node_get_blur_radius (node));
}
return;
case GSK_LINEAR_GRADIENT_NODE:
if (add_new_node (renderer, node, BROADWAY_NODE_LINEAR_GRADIENT))
{
guint i, n;
add_rect (nodes, &node->bounds, offset_x, offset_y);
add_point (nodes, gsk_linear_gradient_node_peek_start (node), offset_x, offset_y);
add_point (nodes, gsk_linear_gradient_node_peek_end (node), offset_x, offset_y);
n = gsk_linear_gradient_node_get_n_color_stops (node);
add_uint32 (nodes, n);
for (i = 0; i < n; i++)
add_color_stop (nodes, &gsk_linear_gradient_node_peek_color_stops (node)[i]);
}
return;
/* Bin nodes */
case GSK_SHADOW_NODE:
if (add_new_node (renderer, node, BROADWAY_NODE_SHADOW))
{
gsize i, n_shadows = gsk_shadow_node_get_n_shadows (node);
add_uint32 (nodes, n_shadows);
for (i = 0; i < n_shadows; i++)
{
const GskShadow *shadow = gsk_shadow_node_peek_shadow (node, i);
add_rgba (nodes, &shadow->color);
add_float (nodes, shadow->dx);
add_float (nodes, shadow->dy);
add_float (nodes, shadow->radius);
}
gsk_broadway_renderer_add_node (renderer,
gsk_shadow_node_get_child (node),
offset_x, offset_y);
}
return;
case GSK_OPACITY_NODE:
if (add_new_node (renderer, node, BROADWAY_NODE_OPACITY))
{
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);
}
return;
case GSK_ROUNDED_CLIP_NODE:
if (add_new_node (renderer, node, BROADWAY_NODE_ROUNDED_CLIP))
{
const GskRoundedRect *rclip = gsk_rounded_clip_node_peek_clip (node);
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);
}
return;
case GSK_CLIP_NODE:
if (add_new_node (renderer, node, BROADWAY_NODE_CLIP))
{
const graphene_rect_t *clip = gsk_clip_node_peek_clip (node);
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);
}
return;
case GSK_TRANSFORM_NODE:
{
GskTransform *transform = gsk_transform_node_get_transform (node);
GskTransformCategory category = gsk_transform_get_category (transform);
if (category >= GSK_TRANSFORM_CATEGORY_2D_TRANSLATE)
{
if (add_new_node (renderer, node, BROADWAY_NODE_TRANSLATE)) {
float dx, dy;
gsk_transform_to_translate (transform, &dx, &dy);
add_xy (nodes, dx, dy, offset_x, offset_y);
gsk_broadway_renderer_add_node (renderer,
gsk_transform_node_get_child (node),
0, 0);
}
}
else
{
/* Fallback to texture for now */
break;
}
}
return;
case GSK_DEBUG_NODE:
if (add_new_node (renderer, node, BROADWAY_NODE_DEBUG))
{
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);
}
return;
/* Generic nodes */
case GSK_CONTAINER_NODE:
if (add_new_node (renderer, node, BROADWAY_NODE_CONTAINER))
{
guint i;
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);
}
return;
case GSK_COLOR_MATRIX_NODE:
case GSK_TEXT_NODE:
case GSK_REPEATING_LINEAR_GRADIENT_NODE:
case GSK_REPEAT_NODE:
case GSK_BLEND_NODE:
case GSK_CROSS_FADE_NODE:
case GSK_BLUR_NODE:
default:
break; /* Fallback */
}
if (add_new_node (renderer, node, BROADWAY_NODE_TEXTURE))
{
GdkTexture *texture;
cairo_surface_t *surface;
cairo_t *cr;
guint32 texture_id;
int x = floorf (node->bounds.origin.x);
int y = floorf (node->bounds.origin.y);
int width = ceil (node->bounds.origin.x + node->bounds.size.width) - x;
int height = ceil (node->bounds.origin.y + node->bounds.size.height) - y;
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
cr = cairo_create (surface);
cairo_translate (cr, -x, -y);
gsk_render_node_draw (node, cr);
cairo_destroy (cr);
texture = gdk_texture_new_for_surface (surface);
g_ptr_array_add (self->node_textures, texture); /* Transfers ownership to node_textures */
texture_id = gdk_broadway_display_ensure_texture (display, texture);
add_float (nodes, x - offset_x);
add_float (nodes, y - offset_y);
add_float (nodes, gdk_texture_get_width (texture));
add_float (nodes, gdk_texture_get_height (texture));
add_uint32 (nodes, texture_id);
}
}
static void
gsk_broadway_renderer_render (GskRenderer *renderer,
GskRenderNode *root,
const cairo_region_t *update_area)
{
GskBroadwayRenderer *self = GSK_BROADWAY_RENDERER (renderer);
self->node_lookup = g_hash_table_new (g_direct_hash, g_direct_equal);
gdk_draw_context_begin_frame (GDK_DRAW_CONTEXT (self->draw_context), update_area);
/* These are owned by the draw context between begin and end, but
cache them here for easier access during the render */
self->nodes = self->draw_context->nodes;
self->node_textures = self->draw_context->node_textures;
gsk_broadway_renderer_add_node (renderer, root, 0, 0);
self->nodes = NULL;
self->node_textures = NULL;
gdk_draw_context_end_frame (GDK_DRAW_CONTEXT (self->draw_context));
if (self->last_node_lookup)
g_hash_table_unref (self->last_node_lookup);
self->last_node_lookup = self->node_lookup;
self->node_lookup = NULL;
if (self->last_root)
gsk_render_node_unref (self->last_root);
self->last_root = gsk_render_node_ref (root);
if (self->next_node_id > G_MAXUINT32 / 2)
{
/* We're "near" a wrap of the ids, lets avoid reusing any of
* these nodes next frame, then we can reset the id counter
* without risk of any old nodes sticking around and conflicting. */
g_hash_table_remove_all (self->last_node_lookup);
self->next_node_id = 0;
}
}
static void
gsk_broadway_renderer_class_init (GskBroadwayRendererClass *klass)
{
GskRendererClass *renderer_class = GSK_RENDERER_CLASS (klass);
renderer_class->realize = gsk_broadway_renderer_realize;
renderer_class->unrealize = gsk_broadway_renderer_unrealize;
renderer_class->render = gsk_broadway_renderer_render;
renderer_class->render_texture = gsk_broadway_renderer_render_texture;
}
static void
gsk_broadway_renderer_init (GskBroadwayRenderer *self)
{
}