gtk2/gsk/gskrendernode.c

474 lines
14 KiB
C
Raw Normal View History

/* GSK - The GTK Scene Kit
*
* Copyright 2016 Endless
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* SECTION:GskRenderNode
* @Title: GskRenderNode
* @Short_description: Simple scene graph element
*
2016-07-03 23:23:19 +00:00
* #GskRenderNode is the basic block in a scene graph to be
* rendered using #GskRenderer.
*
* Each node has a parent, except the top-level node; each node may have
* children nodes.
*
* Each node has an associated drawing surface, which has the size of
* the rectangle set using gsk_render_node_set_bounds().
2016-07-03 23:23:19 +00:00
*
* Render nodes are meant to be transient; once they have been associated
* to a #GskRenderer it's safe to release any reference you have on them.
* All #GskRenderNodes are immutable, you can only specify their properties
* during construction.
*/
#include "config.h"
#include "gskrendernodeprivate.h"
#include "gskdebugprivate.h"
#include "gskrendererprivate.h"
#include <graphene-gobject.h>
#include <math.h>
#include <gobject/gvaluecollector.h>
/**
2016-12-10 14:33:16 +00:00
* GskRenderNode: (ref-func gsk_render_node_ref) (unref-func gsk_render_node_unref)
*
* The `GskRenderNode` structure contains only private data.
*/
2016-12-10 14:33:16 +00:00
G_DEFINE_BOXED_TYPE (GskRenderNode, gsk_render_node,
gsk_render_node_ref,
gsk_render_node_unref)
G_DEFINE_QUARK (gsk-serialization-error-quark, gsk_serialization_error)
static void
gsk_render_node_finalize (GskRenderNode *self)
{
2016-12-11 03:18:25 +00:00
self->node_class->finalize (self);
2016-10-20 19:12:36 +00:00
g_clear_pointer (&self->name, g_free);
g_free (self);
}
2016-12-10 14:33:16 +00:00
/*< private >
* gsk_render_node_new:
* @node_class: class structure for this node
2016-12-10 14:33:16 +00:00
*
* Returns: (transfer full): the newly created #GskRenderNode
*/
GskRenderNode *
gsk_render_node_new (const GskRenderNodeClass *node_class, gsize extra_size)
{
GskRenderNode *self;
g_return_val_if_fail (node_class != NULL, NULL);
g_return_val_if_fail (node_class->node_type != GSK_NOT_A_RENDER_NODE, NULL);
self = g_malloc0 (node_class->struct_size + extra_size);
self->node_class = node_class;
self->ref_count = 1;
2016-12-10 14:33:16 +00:00
return self;
}
/**
* gsk_render_node_ref:
* @node: a #GskRenderNode
*
* Acquires a reference on the given #GskRenderNode.
*
* Returns: (transfer none): the #GskRenderNode with an additional reference
*/
GskRenderNode *
gsk_render_node_ref (GskRenderNode *node)
{
g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL);
g_atomic_int_inc (&node->ref_count);
return node;
}
/**
* gsk_render_node_unref:
* @node: a #GskRenderNode
*
* Releases a reference on the given #GskRenderNode.
*
* If the reference was the last, the resources associated to the @node are
* freed.
*/
void
gsk_render_node_unref (GskRenderNode *node)
{
g_return_if_fail (GSK_IS_RENDER_NODE (node));
if (g_atomic_int_dec_and_test (&node->ref_count))
2016-12-10 14:33:16 +00:00
gsk_render_node_finalize (node);
}
/**
* gsk_render_node_get_node_type:
* @node: a #GskRenderNode
*
* Returns the type of the @node.
*
* Returns: the type of the #GskRenderNode
*/
GskRenderNodeType
gsk_render_node_get_node_type (GskRenderNode *node)
{
g_return_val_if_fail (GSK_IS_RENDER_NODE (node), GSK_NOT_A_RENDER_NODE);
return node->node_class->node_type;
}
/**
* gsk_render_node_get_bounds:
* @node: a #GskRenderNode
* @bounds: (out caller-allocates): return location for the boundaries
*
* Retrieves the boundaries of the @node. The node will not draw outside
* of its boundaries.
*/
void
gsk_render_node_get_bounds (GskRenderNode *node,
graphene_rect_t *bounds)
{
g_return_if_fail (GSK_IS_RENDER_NODE (node));
g_return_if_fail (bounds != NULL);
graphene_rect_init_from_rect (bounds, &node->bounds);
}
/**
* gsk_render_node_set_name:
* @node: a #GskRenderNode
* @name: (nullable): a name for the node
*
* Sets the name of the node.
*
* A name is generally useful for debugging purposes.
*/
void
gsk_render_node_set_name (GskRenderNode *node,
const char *name)
{
g_return_if_fail (GSK_IS_RENDER_NODE (node));
g_free (node->name);
node->name = g_strdup (name);
}
/**
* gsk_render_node_get_name:
* @node: a #GskRenderNode
*
* Retrieves the name previously set via gsk_render_node_set_name().
* If no name has been set, %NULL is returned.
*
* Returns: (nullable): The name previously set via
* gsk_render_node_set_name() or %NULL
**/
const char *
gsk_render_node_get_name (GskRenderNode *node)
{
g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL);
return node->name;
}
/**
* gsk_render_node_draw:
* @node: a #GskRenderNode
* @cr: cairo context to draw to
*
* Draw the contents of @node to the given cairo context.
*
* Typically, you'll use this function to implement fallback rendering
* of #GskRenderNodes on an intermediate Cairo context, instead of using
GdkWindow -> GdkSurface initial type rename This renames the GdkWindow class and related classes (impl, backend subclasses) to surface. Additionally it renames related types: GdkWindowAttr, GdkWindowPaint, GdkWindowWindowClass, GdkWindowType, GdkWindowTypeHint, GdkWindowHints, GdkWindowState, GdkWindowEdge This is an automatic conversion using the below commands: git sed -f g GdkWindowWindowClass GdkSurfaceSurfaceClass git sed -f g GdkWindow GdkSurface git sed -f g "gdk_window\([ _\(\),;]\|$\)" "gdk_surface\1" # Avoid hitting gdk_windowing git sed -f g "GDK_WINDOW\([ _\(]\|$\)" "GDK_SURFACE\1" # Avoid hitting GDK_WINDOWING git sed "GDK_\([A-Z]*\)IS_WINDOW\([_ (]\|$\)" "GDK_\1IS_SURFACE\2" git sed GDK_TYPE_WINDOW GDK_TYPE_SURFACE git sed -f g GdkPointerWindowInfo GdkPointerSurfaceInfo git sed -f g "BROADWAY_WINDOW" "BROADWAY_SURFACE" git sed -f g "broadway_window" "broadway_surface" git sed -f g "BroadwayWindow" "BroadwaySurface" git sed -f g "WAYLAND_WINDOW" "WAYLAND_SURFACE" git sed -f g "wayland_window" "wayland_surface" git sed -f g "WaylandWindow" "WaylandSurface" git sed -f g "X11_WINDOW" "X11_SURFACE" git sed -f g "x11_window" "x11_surface" git sed -f g "X11Window" "X11Surface" git sed -f g "WIN32_WINDOW" "WIN32_SURFACE" git sed -f g "win32_window" "win32_surface" git sed -f g "Win32Window" "Win32Surface" git sed -f g "QUARTZ_WINDOW" "QUARTZ_SURFACE" git sed -f g "quartz_window" "quartz_surface" git sed -f g "QuartzWindow" "QuartzSurface" git checkout NEWS* po-properties
2018-03-20 10:40:08 +00:00
* the drawing context associated to a #GdkSurface's rendering buffer.
*
* For advanced nodes that cannot be supported using Cairo, in particular
* for nodes doing 3D operations, this function may fail.
**/
void
gsk_render_node_draw (GskRenderNode *node,
cairo_t *cr)
{
g_return_if_fail (GSK_IS_RENDER_NODE (node));
g_return_if_fail (cr != NULL);
g_return_if_fail (cairo_status (cr) == CAIRO_STATUS_SUCCESS);
cairo_save (cr);
#ifdef G_ENABLE_DEBUG
if (!GSK_DEBUG_CHECK (GEOMETRY))
{
GSK_NOTE (CAIRO, g_message ("CLIP = { .x = %g, .y = %g, .width = %g, .height = %g }",
node->bounds.origin.x, node->bounds.origin.y,
node->bounds.size.width, node->bounds.size.height));
cairo_rectangle (cr, node->bounds.origin.x, node->bounds.origin.y, node->bounds.size.width, node->bounds.size.height);
cairo_clip (cr);
}
#endif
GSK_NOTE (CAIRO, g_message ("Rendering node %s[%p]",
node->name ? node->name : node->node_class->type_name,
node));
node->node_class->draw (node, cr);
#ifdef G_ENABLE_DEBUG
if (GSK_DEBUG_CHECK (GEOMETRY))
{
cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
cairo_rectangle (cr, node->bounds.origin.x - 1, node->bounds.origin.y - 1,
node->bounds.size.width + 2, node->bounds.size.height + 2);
cairo_set_line_width (cr, 2);
cairo_set_source_rgba (cr, 0, 0, 0, 0.5);
cairo_stroke (cr);
}
#endif
cairo_restore (cr);
if (cairo_status (cr))
{
g_warning ("drawing failure for render node %s '%s': %s",
node->node_class->type_name,
gsk_render_node_get_name (node),
cairo_status_to_string (cairo_status (cr)));
}
}
/*
* gsk_render_node_can_diff:
* @node1: a #GskRenderNode
* @node2: the #GskRenderNode to compare with
*
* Checks if 2 render nodes can be expected to be compared via
* gsk_render_node_diff(). The node diffing algorithm uses this function
* to match up similar nodes to compare when trying to minimze the
* resulting region.
*
* Nodes of different type always return %FALSE here.
*
* Returns: %TRUE if @node1 and @node2 can be expected to be compared
**/
gboolean
gsk_render_node_can_diff (GskRenderNode *node1,
GskRenderNode *node2)
{
if (node1 == node2)
return TRUE;
if (gsk_render_node_get_node_type (node1) != gsk_render_node_get_node_type (node2))
return FALSE;
return node1->node_class->can_diff (node1, node2);
}
static void
rectangle_init_from_graphene (cairo_rectangle_int_t *cairo,
const graphene_rect_t *graphene)
{
cairo->x = floorf (graphene->origin.x);
cairo->y = floorf (graphene->origin.y);
cairo->width = ceilf (graphene->origin.x + graphene->size.width) - cairo->x;
cairo->height = ceilf (graphene->origin.y + graphene->size.height) - cairo->y;
}
void
gsk_render_node_diff_impossible (GskRenderNode *node1,
GskRenderNode *node2,
cairo_region_t *region)
{
cairo_rectangle_int_t rect;
rectangle_init_from_graphene (&rect, &node1->bounds);
cairo_region_union_rectangle (region, &rect);
rectangle_init_from_graphene (&rect, &node2->bounds);
cairo_region_union_rectangle (region, &rect);
}
/**
* gsk_render_node_diff:
* @node1: a #GskRenderNode
* @node2: the #GskRenderNode to compare with
* @region: a #cairo_region_t to add the differences to
*
* Compares @node1 and @node2 trying to compute the minimal region of changes.
* In the worst case, this is the union of the bounds of @node1 and @node2.
*
* This function is used to compute the area that needs to be redrawn when
* the previous contents were drawn by @node1 and the new contents should
* correspond to @node2. As such, it is important that this comparison is
* faster than the time it takes to actually do the redraw.
*
* Note that the passed in @region may already contain previous results from
* previous node comparisons, so this function call will only add to it.
**/
void
gsk_render_node_diff (GskRenderNode *node1,
GskRenderNode *node2,
cairo_region_t *region)
{
if (node1 == node2)
return;
if (gsk_render_node_get_node_type (node1) != gsk_render_node_get_node_type (node2))
return gsk_render_node_diff_impossible (node1, node2, region);
return node1->node_class->diff (node1, node2, region);
}
#define GSK_RENDER_NODE_SERIALIZATION_VERSION 0
#define GSK_RENDER_NODE_SERIALIZATION_ID "GskRenderNode"
/**
* gsk_render_node_serialize:
* @node: a #GskRenderNode
*
* Serializes the @node for later deserialization via
* gsk_render_node_deserialize(). No guarantees are made about the format
* used other than that the same version of GTK+ will be able to deserialize
* the result of a call to gsk_render_node_serialize() and
* gsk_render_node_deserialize() will correctly reject files it cannot open
* that were created with previous versions of GTK+.
*
* The intended use of this functions is testing, benchmarking and debugging.
* The format is not meant as a permanent storage format.
*
* Returns: a #GBytes representing the node.
**/
GBytes *
gsk_render_node_serialize (GskRenderNode *node)
{
GVariant *node_variant, *variant;
GBytes *result;
node_variant = gsk_render_node_serialize_node (node);
variant = g_variant_new ("(suuv)",
GSK_RENDER_NODE_SERIALIZATION_ID,
(guint32) GSK_RENDER_NODE_SERIALIZATION_VERSION,
(guint32) gsk_render_node_get_node_type (node),
node_variant);
result = g_variant_get_data_as_bytes (variant);
g_variant_unref (variant);
return result;
}
/**
* gsk_render_node_write_to_file:
* @node: a #GskRenderNode
* @filename: the file to save it to.
* @error: Return location for a potential error
*
* This function is equivalent to calling gsk_render_node_serialize()
* followed by g_file_set_contents(). See those two functions for details
* on the arguments.
*
* It is mostly intended for use inside a debugger to quickly dump a render
* node to a file for later inspection.
*
* Returns: %TRUE if saving was successful
**/
gboolean
gsk_render_node_write_to_file (GskRenderNode *node,
const char *filename,
GError **error)
{
GBytes *bytes;
gboolean result;
g_return_val_if_fail (GSK_IS_RENDER_NODE (node), FALSE);
g_return_val_if_fail (filename != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
bytes = gsk_render_node_serialize (node);
result = g_file_set_contents (filename,
g_bytes_get_data (bytes, NULL),
g_bytes_get_size (bytes),
error);
g_bytes_unref (bytes);
return result;
}
/**
* gsk_render_node_deserialize:
* @bytes: the bytes containing the data
* @error: (allow-none): location to store error or %NULL
*
* Loads data previously created via gsk_render_node_serialize(). For a
* discussion of the supported format, see that function.
*
* Returns: (nullable) (transfer full): a new #GskRenderNode or %NULL on
* error.
**/
GskRenderNode *
gsk_render_node_deserialize (GBytes *bytes,
GError **error)
{
char *id_string;
guint32 version, node_type;
GVariant *variant, *node_variant;
GskRenderNode *node = NULL;
variant = g_variant_new_from_bytes (G_VARIANT_TYPE ("(suuv)"), bytes, FALSE);
g_variant_get (variant, "(suuv)", &id_string, &version, &node_type, &node_variant);
if (!g_str_equal (id_string, GSK_RENDER_NODE_SERIALIZATION_ID))
{
g_set_error (error, GSK_SERIALIZATION_ERROR, GSK_SERIALIZATION_UNSUPPORTED_FORMAT,
"Data not in GskRenderNode serialization format.");
goto out;
}
if (version != GSK_RENDER_NODE_SERIALIZATION_VERSION)
{
g_set_error (error, GSK_SERIALIZATION_ERROR, GSK_SERIALIZATION_UNSUPPORTED_VERSION,
"Format version %u not supported.", version);
goto out;
}
node = gsk_render_node_deserialize_node (node_type, node_variant, error);
out:
g_free (id_string);
g_variant_unref (node_variant);
g_variant_unref (variant);
return node;
}