gtk2/gsk/gskrenderer.c
Emmanuele Bassi 7afdd3fdb5 Initial implementation of GSK rendering pipeline
GSK is conceptually split into two scene graphs:

 * a simple rendering tree of operations
 * a complex set of logical layers

The latter is built on the former, and adds convenience and high level
API for application developers.

The lower layer, though, is what gets transformed into the rendering
pipeline, as it's simple and thus can be transformed into appropriate
rendering commands with minimal state changes.

The lower layer is also suitable for reuse from more complex higher
layers, like the CSS machinery in GTK, without necessarily port those
layers to the GSK high level API.

This lower layer is based on GskRenderNode instances, which represent
the tree of rendering operations; and a GskRenderer instance, which
takes the render nodes and submits them (after potentially reordering
and transforming them to a more appropriate representation) to the
underlying graphic system.
2016-10-18 11:29:34 +01:00

1378 lines
36 KiB
C

/* 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:GskRenderer
* @title: GskRenderer
* @Short_desc: Renders a scene with a simplified graph
*
* TODO
*/
#include "config.h"
#include "gskrendererprivate.h"
#include "gskdebugprivate.h"
#include "gskcairorendererprivate.h"
#include "gskglrendererprivate.h"
#include "gskrendernodeprivate.h"
#include "gskenumtypes.h"
#include <graphene-gobject.h>
#include <cairo-gobject.h>
#include <gdk/gdk.h>
#ifdef GDK_WINDOWING_X11
#include <gdk/x11/gdkx.h>
#endif
#ifdef GDK_WINDOWING_WAYLAND
#include <gdk/wayland/gdkwayland.h>
#endif
typedef struct
{
GObject parent_instance;
GdkDisplay *display;
GdkWindow *window;
graphene_rect_t viewport;
graphene_matrix_t modelview;
graphene_matrix_t projection;
GskScalingFilter min_filter;
GskScalingFilter mag_filter;
GskRenderNode *root_node;
cairo_surface_t *surface;
cairo_t *draw_context;
gboolean is_realized : 1;
gboolean needs_viewport_resize : 1;
gboolean needs_modelview_update : 1;
gboolean needs_projection_update : 1;
gboolean needs_tree_validation : 1;
gboolean auto_clear : 1;
gboolean use_alpha : 1;
} GskRendererPrivate;
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GskRenderer, gsk_renderer, G_TYPE_OBJECT)
enum {
PROP_VIEWPORT = 1,
PROP_MODELVIEW,
PROP_PROJECTION,
PROP_MINIFICATION_FILTER,
PROP_MAGNIFICATION_FILTER,
PROP_AUTO_CLEAR,
PROP_ROOT_NODE,
PROP_DISPLAY,
PROP_WINDOW,
PROP_SURFACE,
PROP_USE_ALPHA,
N_PROPS
};
static GParamSpec *gsk_renderer_properties[N_PROPS];
#define GSK_RENDERER_WARN_NOT_IMPLEMENTED_METHOD(obj,method) \
g_critical ("Renderer of type '%s' does not implement GskRenderer::" # method, G_OBJECT_TYPE_NAME (obj))
static gboolean
gsk_renderer_real_realize (GskRenderer *self)
{
GSK_RENDERER_WARN_NOT_IMPLEMENTED_METHOD (self, realize);
return FALSE;
}
static void
gsk_renderer_real_unrealize (GskRenderer *self)
{
GSK_RENDERER_WARN_NOT_IMPLEMENTED_METHOD (self, unrealize);
}
static void
gsk_renderer_real_render (GskRenderer *self)
{
GSK_RENDERER_WARN_NOT_IMPLEMENTED_METHOD (self, render);
}
static void
gsk_renderer_real_resize_viewport (GskRenderer *self,
const graphene_rect_t *viewport)
{
}
static void
gsk_renderer_real_update (GskRenderer *self,
const graphene_matrix_t *mv,
const graphene_matrix_t *proj)
{
}
static void
gsk_renderer_real_validate_tree (GskRenderer *self,
GskRenderNode *root)
{
}
static void
gsk_renderer_dispose (GObject *gobject)
{
GskRenderer *self = GSK_RENDERER (gobject);
GskRendererPrivate *priv = gsk_renderer_get_instance_private (self);
gsk_renderer_unrealize (self);
g_clear_pointer (&priv->surface, cairo_surface_destroy);
g_clear_pointer (&priv->draw_context, cairo_destroy);
g_clear_object (&priv->window);
g_clear_object (&priv->root_node);
g_clear_object (&priv->display);
G_OBJECT_CLASS (gsk_renderer_parent_class)->dispose (gobject);
}
static void
gsk_renderer_set_property (GObject *gobject,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GskRenderer *self = GSK_RENDERER (gobject);
GskRendererPrivate *priv = gsk_renderer_get_instance_private (self);
switch (prop_id)
{
case PROP_VIEWPORT:
gsk_renderer_set_viewport (self, g_value_get_boxed (value));
break;
case PROP_MODELVIEW:
gsk_renderer_set_modelview (self, g_value_get_boxed (value));
break;
case PROP_PROJECTION:
gsk_renderer_set_projection (self, g_value_get_boxed (value));
break;
case PROP_MINIFICATION_FILTER:
gsk_renderer_set_scaling_filters (self, g_value_get_enum (value), priv->mag_filter);
break;
case PROP_MAGNIFICATION_FILTER:
gsk_renderer_set_scaling_filters (self, priv->min_filter, g_value_get_enum (value));
break;
case PROP_AUTO_CLEAR:
gsk_renderer_set_auto_clear (self, g_value_get_boolean (value));
break;
case PROP_ROOT_NODE:
gsk_renderer_set_root_node (self, g_value_get_object (value));
break;
case PROP_SURFACE:
gsk_renderer_set_surface (self, g_value_get_boxed (value));
break;
case PROP_WINDOW:
gsk_renderer_set_window (self, g_value_get_object (value));
break;
case PROP_DISPLAY:
priv->display = g_value_dup_object (value);
break;
case PROP_USE_ALPHA:
gsk_renderer_set_use_alpha (self, g_value_get_boolean (value));
break;
}
}
static void
gsk_renderer_get_property (GObject *gobject,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GskRenderer *self = GSK_RENDERER (gobject);
GskRendererPrivate *priv = gsk_renderer_get_instance_private (self);
switch (prop_id)
{
case PROP_VIEWPORT:
g_value_set_boxed (value, &priv->viewport);
break;
case PROP_MODELVIEW:
g_value_set_boxed (value, &priv->modelview);
break;
case PROP_PROJECTION:
g_value_set_boxed (value, &priv->projection);
break;
case PROP_MINIFICATION_FILTER:
g_value_set_enum (value, priv->min_filter);
break;
case PROP_MAGNIFICATION_FILTER:
g_value_set_enum (value, priv->mag_filter);
break;
case PROP_AUTO_CLEAR:
g_value_set_boolean (value, priv->auto_clear);
break;
case PROP_ROOT_NODE:
g_value_set_object (value, priv->root_node);
break;
case PROP_SURFACE:
g_value_set_boxed (value, priv->surface);
break;
case PROP_DISPLAY:
g_value_set_object (value, priv->display);
break;
case PROP_WINDOW:
g_value_set_object (value, priv->window);
break;
case PROP_USE_ALPHA:
g_value_set_boolean (value, priv->use_alpha);
break;
}
}
static void
gsk_renderer_constructed (GObject *gobject)
{
GskRenderer *self = GSK_RENDERER (gobject);
GskRendererPrivate *priv = gsk_renderer_get_instance_private (self);
if (priv->display == NULL)
{
GdkDisplayManager *manager = gdk_display_manager_get ();
priv->display = gdk_display_manager_get_default_display (manager);
g_assert (priv->display != NULL);
}
G_OBJECT_CLASS (gsk_renderer_parent_class)->constructed (gobject);
}
static void
gsk_renderer_class_init (GskRendererClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
klass->realize = gsk_renderer_real_realize;
klass->unrealize = gsk_renderer_real_unrealize;
klass->resize_viewport = gsk_renderer_real_resize_viewport;
klass->update = gsk_renderer_real_update;
klass->validate_tree = gsk_renderer_real_validate_tree;
klass->render = gsk_renderer_real_render;
gobject_class->constructed = gsk_renderer_constructed;
gobject_class->set_property = gsk_renderer_set_property;
gobject_class->get_property = gsk_renderer_get_property;
gobject_class->dispose = gsk_renderer_dispose;
/**
* GskRenderer:viewport:
*
* The visible area used by the #GskRenderer to render its contents.
*
* Since: 3.22
*/
gsk_renderer_properties[PROP_VIEWPORT] =
g_param_spec_boxed ("viewport",
"Viewport",
"The visible area used by the renderer",
GRAPHENE_TYPE_RECT,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS |
G_PARAM_EXPLICIT_NOTIFY);
/**
* GskRenderer:modelview:
*
* The initial modelview matrix used by the #GskRenderer.
*
* If set to %NULL, the identity matrix:
*
* |[<!-- language="plain"
* | 1.0, 0.0, 0.0, 0.0 |
* | 0.0, 1.0, 0.0, 0.0 |
* | 0.0, 0.0, 1.0, 0.0 |
* | 0.0, 0.0, 0.0, 1.0 |
* ]|
*
* Is used instead.
*
* Since: 3.22
*/
gsk_renderer_properties[PROP_MODELVIEW] =
g_param_spec_boxed ("modelview",
"Modelview",
"The modelview matrix used by the renderer",
GRAPHENE_TYPE_MATRIX,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS |
G_PARAM_EXPLICIT_NOTIFY);
/**
* GskRenderer:projection:
*
* The projection matrix used by the #GskRenderer.
*
* If set to %NULL, the identity matrix:
*
* |[<!-- language="plain"
* | 1.0, 0.0, 0.0, 0.0 |
* | 0.0, 1.0, 0.0, 0.0 |
* | 0.0, 0.0, 1.0, 0.0 |
* | 0.0, 0.0, 0.0, 1.0 |
* ]|
*
* Is used instead.
*
* Since: 3.22
*/
gsk_renderer_properties[PROP_PROJECTION] =
g_param_spec_boxed ("projection",
"Projection",
"The projection matrix used by the renderer",
GRAPHENE_TYPE_MATRIX,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS |
G_PARAM_EXPLICIT_NOTIFY);
/**
* GskRenderer:minification-filter:
*
* The filter to be used when scaling textures down.
*
* See also: gsk_renderer_set_scaling_filters()
*
* Since: 3.22
*/
gsk_renderer_properties[PROP_MINIFICATION_FILTER] =
g_param_spec_enum ("minification-filter",
"Minification Filter",
"The minification filter used by the renderer for texture targets",
GSK_TYPE_SCALING_FILTER,
GSK_SCALING_FILTER_LINEAR,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS |
G_PARAM_EXPLICIT_NOTIFY);
/**
* GskRenderer:magnification-filter:
*
* The filter to be used when scaling textures up.
*
* See also: gsk_renderer_set_scaling_filters()
*
* Since: 3.22
*/
gsk_renderer_properties[PROP_MAGNIFICATION_FILTER] =
g_param_spec_enum ("magnification-filter",
"Magnification Filter",
"The magnification filter used by the renderer for texture targets",
GSK_TYPE_SCALING_FILTER,
GSK_SCALING_FILTER_LINEAR,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS |
G_PARAM_EXPLICIT_NOTIFY);
/**
* GskRenderer:auto-clear:
*
* Automatically clear the rendering surface when rendering.
*
* Setting this property to %FALSE assumes that the owner of the
* rendering surface will have cleared it prior to calling
* gsk_renderer_render().
*
* Since: 3.22
*/
gsk_renderer_properties[PROP_AUTO_CLEAR] =
g_param_spec_boolean ("auto-clear",
"Auto Clear",
"Automatically clears the rendering target on render",
TRUE,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS |
G_PARAM_EXPLICIT_NOTIFY);
/**
* GskRenderer:root-node:
*
* The root #GskRenderNode of the scene to be rendered.
*
* Since: 3.22
*/
gsk_renderer_properties[PROP_ROOT_NODE] =
g_param_spec_object ("root-node",
"Root Node",
"The root render node to render",
GSK_TYPE_RENDER_NODE,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS |
G_PARAM_EXPLICIT_NOTIFY);
/**
* GskRenderer:surface:
*
* The target rendering surface.
*
* See also: #GskRenderer:window.
*
* Since: 3.22
*/
gsk_renderer_properties[PROP_SURFACE] =
g_param_spec_boxed ("surface",
"Surface",
"The Cairo surface used to render to",
CAIRO_GOBJECT_TYPE_SURFACE,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS |
G_PARAM_EXPLICIT_NOTIFY);
/**
* GskRenderer:display:
*
* The #GdkDisplay used by the #GskRenderer.
*
* Since: 3.22
*/
gsk_renderer_properties[PROP_DISPLAY] =
g_param_spec_object ("display",
"Display",
"The GdkDisplay object used by the renderer",
GDK_TYPE_DISPLAY,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS);
/**
* GskRenderer:window:
*
* The #GdkWindow used to create a target surface, if #GskRenderer:surface
* is not explicitly set.
*
* Since: 3.22
*/
gsk_renderer_properties[PROP_WINDOW] =
g_param_spec_object ("window",
"Window",
"The GdkWindow associated to the renderer",
GDK_TYPE_WINDOW,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS |
G_PARAM_EXPLICIT_NOTIFY);
/**
* GskRenderer:use-alpha:
*
* Whether the #GskRenderer should use the alpha channel when rendering.
*
* Since: 3.22
*/
gsk_renderer_properties[PROP_USE_ALPHA] =
g_param_spec_boolean ("use-alpha",
"Use Alpha",
"Whether the renderer should use the alpha channel when rendering",
FALSE,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS |
G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (gobject_class, N_PROPS, gsk_renderer_properties);
}
static void
gsk_renderer_init (GskRenderer *self)
{
GskRendererPrivate *priv = gsk_renderer_get_instance_private (self);
graphene_matrix_init_identity (&priv->modelview);
graphene_matrix_init_identity (&priv->projection);
priv->auto_clear = TRUE;
priv->min_filter = GSK_SCALING_FILTER_LINEAR;
priv->mag_filter = GSK_SCALING_FILTER_LINEAR;
}
/**
* gsk_renderer_set_viewport:
* @renderer: a #GskRenderer
* @viewport: (nullable): the viewport rectangle used by the @renderer
*
* Sets the visible rectangle to be used as the viewport for
* the rendering.
*
* Since: 3.22
*/
void
gsk_renderer_set_viewport (GskRenderer *renderer,
const graphene_rect_t *viewport)
{
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
g_return_if_fail (GSK_IS_RENDERER (renderer));
if (viewport == NULL)
{
graphene_rect_init (&priv->viewport, 0.f, 0.f, 0.f, 0.f);
g_object_notify_by_pspec (G_OBJECT (renderer), gsk_renderer_properties[PROP_VIEWPORT]);
return;
}
if (graphene_rect_equal (viewport, &priv->viewport))
return;
graphene_rect_init_from_rect (&priv->viewport, viewport);
priv->needs_viewport_resize = TRUE;
priv->needs_modelview_update = TRUE;
priv->needs_projection_update = TRUE;
g_object_notify_by_pspec (G_OBJECT (renderer), gsk_renderer_properties[PROP_VIEWPORT]);
}
/**
* gsk_renderer_get_viewport:
* @renderer: a #GskRenderer
* @viewport: (out caller-allocates): return location for the viewport rectangle
*
* Retrieves the viewport of the #GskRenderer.
*
* Since: 3.22
*/
void
gsk_renderer_get_viewport (GskRenderer *renderer,
graphene_rect_t *viewport)
{
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
g_return_if_fail (GSK_IS_RENDERER (renderer));
g_return_if_fail (viewport != NULL);
graphene_rect_init_from_rect (viewport, &priv->viewport);
}
/**
* gsk_renderer_set_modelview:
* @renderer: a #GskRenderer
* @modelview: the modelview matrix used by the @renderer
*
* Sets the initial modelview matrix used by the #GskRenderer.
*
* A modelview matrix defines the initial transformation imposed
* on the scene graph.
*
* Since: 3.22
*/
void
gsk_renderer_set_modelview (GskRenderer *renderer,
const graphene_matrix_t *modelview)
{
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
g_return_if_fail (GSK_IS_RENDERER (renderer));
if (modelview == NULL)
graphene_matrix_init_identity (&priv->modelview);
else
graphene_matrix_init_from_matrix (&priv->modelview, modelview);
priv->needs_modelview_update = TRUE;
g_object_notify_by_pspec (G_OBJECT (renderer), gsk_renderer_properties[PROP_MODELVIEW]);
}
/**
* gsk_renderer_get_modelview:
* @renderer: a #GskRenderer
* @modelview: (out caller-allocates): return location for the modelview matrix
*
* Retrieves the modelview matrix used by the #GskRenderer.
*
* Since: 3.22
*/
void
gsk_renderer_get_modelview (GskRenderer *renderer,
graphene_matrix_t *modelview)
{
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
g_return_if_fail (GSK_IS_RENDERER (renderer));
g_return_if_fail (modelview != NULL);
graphene_matrix_init_from_matrix (modelview, &priv->modelview);
}
/**
* gsk_renderer_set_projection:
* @renderer: a #GskRenderer
* @projection: the projection matrix used by the @renderer
*
* Sets the projection matrix used by the #GskRenderer.
*
* Since: 3.22
*/
void
gsk_renderer_set_projection (GskRenderer *renderer,
const graphene_matrix_t *projection)
{
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
g_return_if_fail (GSK_IS_RENDERER (renderer));
if (projection == NULL)
graphene_matrix_init_identity (&priv->projection);
else
graphene_matrix_init_from_matrix (&priv->projection, projection);
priv->needs_projection_update = TRUE;
g_object_notify_by_pspec (G_OBJECT (renderer), gsk_renderer_properties[PROP_PROJECTION]);
}
/**
* gsk_renderer_get_projection:
* @renderer: a #GskRenderer
* @projection: (out caller-allocates): return location for the projection matrix
*
* Retrieves the projection matrix used by the #GskRenderer.
*
* Since: 3.22
*/
void
gsk_renderer_get_projection (GskRenderer *renderer,
graphene_matrix_t *projection)
{
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
g_return_if_fail (GSK_IS_RENDERER (renderer));
g_return_if_fail (projection != NULL);
graphene_matrix_init_from_matrix (projection, &priv->projection);
}
static void
gsk_renderer_invalidate_tree (GskRenderNode *node,
gpointer data)
{
GskRenderer *self = data;
GskRendererPrivate *priv = gsk_renderer_get_instance_private (self);
GSK_NOTE (RENDERER, g_print ("Invalidating tree.\n"));
/* Since the scene graph has changed in some way, we need to re-validate it. */
priv->needs_tree_validation = TRUE;
}
/**
* gsk_renderer_set_root_node:
* @renderer: a #GskRenderer
* @root: (nullable): a #GskRenderNode
*
* Sets the root node of the scene graph to be rendered.
*
* The #GskRenderer will acquire a reference on @root.
*
* Since: 3.22
*/
void
gsk_renderer_set_root_node (GskRenderer *renderer,
GskRenderNode *root)
{
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
GskRenderNode *old_root;
g_return_if_fail (GSK_IS_RENDERER (renderer));
g_return_if_fail (GSK_IS_RENDER_NODE (root));
old_root = priv->root_node != NULL ? g_object_ref (priv->root_node) : NULL;
if (g_set_object (&priv->root_node, root))
{
/* We need to unset the invalidate function on the old instance */
if (old_root != NULL)
{
gsk_render_node_set_invalidate_func (old_root, NULL, NULL, NULL);
g_object_unref (old_root);
}
if (priv->root_node != NULL)
gsk_render_node_set_invalidate_func (priv->root_node,
gsk_renderer_invalidate_tree,
renderer,
NULL);
/* If we don't have a root node, there's really no point in validating a
* tree that it's not going to be drawn
*/
priv->needs_tree_validation = priv->root_node != NULL;
g_object_notify_by_pspec (G_OBJECT (renderer), gsk_renderer_properties[PROP_ROOT_NODE]);
}
}
/**
* gsk_renderer_set_scaling_filters:
* @renderer: a #GskRenderer
* @min_filter: the minification scaling filter
* @mag_filter: the magnification scaling filter
*
* Sets the scaling filters to be applied when scaling textures
* up and down.
*
* Since: 3.22
*/
void
gsk_renderer_set_scaling_filters (GskRenderer *renderer,
GskScalingFilter min_filter,
GskScalingFilter mag_filter)
{
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
GObject *gobject;
g_return_if_fail (GSK_IS_RENDERER (renderer));
gobject = G_OBJECT (renderer);
g_object_freeze_notify (gobject);
if (priv->min_filter != min_filter)
{
priv->min_filter = min_filter;
g_object_notify_by_pspec (gobject, gsk_renderer_properties[PROP_MINIFICATION_FILTER]);
}
if (priv->mag_filter != mag_filter)
{
priv->mag_filter = mag_filter;
g_object_notify_by_pspec (gobject, gsk_renderer_properties[PROP_MAGNIFICATION_FILTER]);
}
g_object_thaw_notify (gobject);
}
/**
* gsk_renderer_get_scaling_filters:
* @renderer: a #GskRenderer
* @min_filter: (out) (nullable): return location for the minification filter
* @mag_filter: (out) (nullable): return location for the magnification filter
*
* Retrieves the minification and magnification filters used by the #GskRenderer.
*
* Since: 3.22
*/
void
gsk_renderer_get_scaling_filters (GskRenderer *renderer,
GskScalingFilter *min_filter,
GskScalingFilter *mag_filter)
{
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
g_return_if_fail (GSK_IS_RENDERER (renderer));
if (min_filter != NULL)
*min_filter = priv->min_filter;
if (mag_filter != NULL)
*mag_filter = priv->mag_filter;
}
/**
* gsk_renderer_set_surface:
* @renderer: a #GskRenderer
* @surface: (nullable): a Cairo surface
*
* Sets the #cairo_surface_t used as the target rendering surface.
*
* This function will acquire a reference to @surface.
*
* See also: gsk_renderer_set_window()
*
* Since: 3.22
*/
void
gsk_renderer_set_surface (GskRenderer *renderer,
cairo_surface_t *surface)
{
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
g_return_if_fail (GSK_IS_RENDERER (renderer));
if (priv->surface == surface)
return;
g_clear_pointer (&priv->surface, cairo_surface_destroy);
if (surface != NULL)
priv->surface = cairo_surface_reference (surface);
g_object_notify_by_pspec (G_OBJECT (renderer), gsk_renderer_properties[PROP_SURFACE]);
}
/**
* gsk_renderer_get_surface:
* @renderer: a #GskRenderer
*
* Retrieve the target rendering surface used by @renderer.
*
* If you did not use gsk_renderer_set_surface(), a compatible surface
* will be created by using the #GdkWindow passed to gsk_renderer_set_window().
*
* Returns: (transfer none) (nullable): a Cairo surface
*
* Since: 3.22
*/
cairo_surface_t *
gsk_renderer_get_surface (GskRenderer *renderer)
{
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
g_return_val_if_fail (GSK_IS_RENDERER (renderer), NULL);
if (priv->surface != NULL)
return priv->surface;
if (priv->window != NULL)
{
int scale = gdk_window_get_scale_factor (priv->window);
int width = gdk_window_get_width (priv->window);
int height = gdk_window_get_height (priv->window);
cairo_content_t content;
if (priv->use_alpha)
content = CAIRO_CONTENT_COLOR_ALPHA;
else
content = CAIRO_CONTENT_COLOR;
GSK_NOTE (RENDERER, g_print ("Creating surface from window [%p] (w:%d, h:%d, s:%d, a:%s)\n",
priv->window,
width, height, scale,
priv->use_alpha ? "y" : "n"));
priv->surface = gdk_window_create_similar_surface (priv->window,
content,
width, height);
}
return priv->surface;
}
void
gsk_renderer_set_draw_context (GskRenderer *renderer,
cairo_t *cr)
{
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
g_return_if_fail (GSK_IS_RENDERER (renderer));
if (priv->draw_context == cr)
return;
g_clear_pointer (&priv->draw_context, cairo_destroy);
priv->draw_context = cr != NULL ? cairo_reference (cr) : NULL;
}
cairo_t *
gsk_renderer_get_draw_context (GskRenderer *renderer)
{
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
g_return_val_if_fail (GSK_IS_RENDERER (renderer), NULL);
if (priv->draw_context != NULL)
return priv->draw_context;
return cairo_create (gsk_renderer_get_surface (renderer));
}
/**
* gsk_renderer_get_display:
* @renderer: a #GskRenderer
*
* Retrieves the #GdkDisplay used when creating the #GskRenderer.
*
* Returns: (transfer none): a #GdkDisplay
*
* Since: 3.22
*/
GdkDisplay *
gsk_renderer_get_display (GskRenderer *renderer)
{
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
g_return_val_if_fail (GSK_IS_RENDERER (renderer), NULL);
return priv->display;
}
/**
* gsk_renderer_get_root_node:
* @renderer: a #GskRenderer
*
* Retrieves the root node of the scene graph.
*
* Returns: (transfer none) (nullable): a #GskRenderNode
*
* Since: 3.22
*/
GskRenderNode *
gsk_renderer_get_root_node (GskRenderer *renderer)
{
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
g_return_val_if_fail (GSK_IS_RENDERER (renderer), NULL);
return priv->root_node;
}
/*< private >
* gsk_renderer_is_realized:
* @renderer: a #GskRenderer
*
* Checks whether the @renderer is realized or not.
*
* Returns: %TRUE if the #GskRenderer was realized, and %FALSE otherwise
*
* Since: 3.22
*/
gboolean
gsk_renderer_is_realized (GskRenderer *renderer)
{
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
g_return_val_if_fail (GSK_IS_RENDERER (renderer), FALSE);
return priv->is_realized;
}
/**
* gsk_renderer_set_window:
* @renderer: a #GskRenderer
* @window: (nullable): a #GdkWindow
*
* Sets the #GdkWindow used to create the target rendering surface.
*
* See also: gsk_renderer_set_surface()
*
* Since: 3.22
*/
void
gsk_renderer_set_window (GskRenderer *renderer,
GdkWindow *window)
{
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
g_return_if_fail (GSK_IS_RENDERER (renderer));
g_return_if_fail (window == NULL || GDK_IS_WINDOW (window));
g_return_if_fail (!priv->is_realized);
if (g_set_object (&priv->window, window))
g_object_notify_by_pspec (G_OBJECT (renderer), gsk_renderer_properties[PROP_WINDOW]);
}
/**
* gsk_renderer_get_window:
* @renderer: a #GskRenderer
*
* Retrieves the #GdkWindow set with gsk_renderer_set_window().
*
* Returns: (transfer none) (nullable): a #GdkWindow
*
* Since: 3.22
*/
GdkWindow *
gsk_renderer_get_window (GskRenderer *renderer)
{
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
g_return_val_if_fail (GSK_IS_RENDERER (renderer), NULL);
return priv->window;
}
/**
* gsk_renderer_realize:
* @renderer: a #GskRenderer
*
* Creates the resources needed by the @renderer to render the scene
* graph.
*
* Since: 3.22
*/
gboolean
gsk_renderer_realize (GskRenderer *renderer)
{
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
g_return_val_if_fail (GSK_IS_RENDERER (renderer), FALSE);
if (priv->is_realized)
return TRUE;
if (priv->window == NULL && priv->surface == NULL)
{
g_critical ("No rendering surface has been set.");
return FALSE;
}
priv->is_realized = GSK_RENDERER_GET_CLASS (renderer)->realize (renderer);
return priv->is_realized;
}
/**
* gsk_renderer_unrealize:
* @renderer: a #GskRenderer
*
* Releases all the resources created by gsk_renderer_realize().
*
* Since: 3.22
*/
void
gsk_renderer_unrealize (GskRenderer *renderer)
{
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
g_return_if_fail (GSK_IS_RENDERER (renderer));
if (!priv->is_realized)
return;
GSK_RENDERER_GET_CLASS (renderer)->unrealize (renderer);
priv->is_realized = FALSE;
}
/*< private >
* gsk_renderer_maybe_resize_viewport:
* @renderer: a #GskRenderer
*
* Optionally resize the viewport of @renderer.
*
* This function should be called by gsk_renderer_render().
*
* This function may call @GskRendererClass.resize_viewport().
*/
void
gsk_renderer_maybe_resize_viewport (GskRenderer *renderer)
{
GskRendererClass *renderer_class = GSK_RENDERER_GET_CLASS (renderer);
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
if (priv->needs_viewport_resize)
{
renderer_class->resize_viewport (renderer, &priv->viewport);
priv->needs_viewport_resize = FALSE;
GSK_NOTE (RENDERER, g_print ("Viewport size: %g x %g\n",
priv->viewport.size.width,
priv->viewport.size.height));
/* If the target surface has been created from a window, we need
* to clear it, so that it gets recreated with the right size
*/
if (priv->window != NULL && priv->surface != NULL)
g_clear_pointer (&priv->surface, cairo_surface_destroy);
}
}
/*< private >
* gsk_renderer_maybe_update:
* @renderer: a #GskRenderer
*
* Optionally recomputes the modelview-projection matrix used by
* the @renderer.
*
* This function should be called by gsk_renderer_render().
*
* This function may call @GskRendererClass.update().
*/
void
gsk_renderer_maybe_update (GskRenderer *renderer)
{
GskRendererClass *renderer_class = GSK_RENDERER_GET_CLASS (renderer);
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
if (priv->needs_modelview_update || priv->needs_projection_update)
{
renderer_class->update (renderer, &priv->modelview, &priv->projection);
priv->needs_modelview_update = FALSE;
priv->needs_projection_update = FALSE;
}
}
/*< private >
* gsk_renderer_maybe_validate_tree:
* @renderer: a #GskRenderer
*
* Optionally validates the #GskRenderNode scene graph, and uses it
* to generate more efficient intermediate representations depending
* on the type of @renderer.
*
* This function should be called by gsk_renderer_render().
*
* This function may call @GskRendererClas.validate_tree().
*/
void
gsk_renderer_maybe_validate_tree (GskRenderer *renderer)
{
GskRendererClass *renderer_class = GSK_RENDERER_GET_CLASS (renderer);
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
if (priv->root_node == NULL)
return;
/* Ensure that the render nodes are valid; this will change the
* needs_tree_validation flag on the renderer, if needed
*/
gsk_render_node_validate (priv->root_node);
if (priv->needs_tree_validation)
{
/* Ensure that the Renderer can update itself */
renderer_class->validate_tree (renderer, priv->root_node);
priv->needs_tree_validation = FALSE;
}
}
/*< private >
* gsk_renderer_maybe_clear:
* @renderer: a #GskRenderer
*
* Optionally calls @GskRendererClass.clear(), depending on the value
* of #GskRenderer:auto-clear.
*
* This function should be called by gsk_renderer_render().
*/
void
gsk_renderer_maybe_clear (GskRenderer *renderer)
{
GskRendererClass *renderer_class = GSK_RENDERER_GET_CLASS (renderer);
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
if (priv->auto_clear)
renderer_class->clear (renderer);
}
/**
* gsk_renderer_render:
* @renderer: a#GskRenderer
*
* Renders the scene graph associated to @renderer, using the
* given target surface.
*
* Since: 3.22
*/
void
gsk_renderer_render (GskRenderer *renderer)
{
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
g_return_if_fail (GSK_IS_RENDERER (renderer));
g_return_if_fail (priv->is_realized);
g_return_if_fail (priv->root_node != NULL);
/* We need to update the viewport and the modelview, to allow renderers
* to update their clip region and/or frustum; this allows them to cull
* render nodes in the tree validation phase
*/
gsk_renderer_maybe_resize_viewport (renderer);
gsk_renderer_maybe_update (renderer);
gsk_renderer_maybe_validate_tree (renderer);
/* Clear the output surface */
gsk_renderer_maybe_clear (renderer);
GSK_RENDERER_GET_CLASS (renderer)->render (renderer);
}
/**
* gsk_renderer_set_auto_clear:
* @renderer: a #GskRenderer
* @clear: whether the target surface should be cleared prior
* to rendering to it
*
* Sets whether the target surface used by @renderer should be cleared
* before rendering.
*
* If you pass a custom surface to gsk_renderer_set_surface(), you may
* want to manage the clearing manually; this is possible by passing
* %FALSE to this function.
*
* Since: 3.22
*/
void
gsk_renderer_set_auto_clear (GskRenderer *renderer,
gboolean clear)
{
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
g_return_if_fail (GSK_IS_RENDERER (renderer));
clear = !!clear;
if (clear == priv->auto_clear)
return;
priv->auto_clear = clear;
g_object_notify_by_pspec (G_OBJECT (renderer), gsk_renderer_properties[PROP_AUTO_CLEAR]);
}
/**
* gsk_renderer_get_auto_clear:
* @renderer: a #GskRenderer
*
* Retrieves the value set using gsk_renderer_set_auto_clear().
*
* Returns: %TRUE if the target surface should be cleared prior to rendering
*
* Since: 3.22
*/
gboolean
gsk_renderer_get_auto_clear (GskRenderer *renderer)
{
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
g_return_val_if_fail (GSK_IS_RENDERER (renderer), FALSE);
return priv->auto_clear;
}
/**
* gsk_renderer_set_use_alpha:
* @renderer: a #GskRenderer
* @use_alpha: whether to use the alpha channel of the target surface or not
*
* Sets whether the @renderer should use the alpha channel of the target surface
* or not.
*
* Since: 3.22
*/
void
gsk_renderer_set_use_alpha (GskRenderer *renderer,
gboolean use_alpha)
{
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
g_return_if_fail (GSK_IS_RENDERER (renderer));
g_return_if_fail (!priv->is_realized);
use_alpha = !!use_alpha;
if (use_alpha == priv->use_alpha)
return;
priv->use_alpha = use_alpha;
g_object_notify_by_pspec (G_OBJECT (renderer), gsk_renderer_properties[PROP_USE_ALPHA]);
}
/**
* gsk_renderer_get_use_alpha:
* @renderer: a #GskRenderer
*
* Retrieves the value set using gsk_renderer_set_use_alpha().
*
* Returns: %TRUE if the target surface should use an alpha channel
*
* Since: 3.22
*/
gboolean
gsk_renderer_get_use_alpha (GskRenderer *renderer)
{
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
g_return_val_if_fail (GSK_IS_RENDERER (renderer), FALSE);
return priv->use_alpha;
}
/**
* gsk_renderer_get_for_display:
* @display: a #GdkDisplay
*
* Creates an appropriate #GskRenderer instance for the given @display.
*
* Returns: (transfer full): a #GskRenderer
*
* Since: 3.22
*/
GskRenderer *
gsk_renderer_get_for_display (GdkDisplay *display)
{
static const char *use_software;
GType renderer_type = G_TYPE_INVALID;
if (use_software == NULL)
{
use_software = g_getenv ("GSK_USE_SOFTWARE");
if (use_software == NULL)
use_software = "0";
}
if (use_software[0] != '0')
{
renderer_type = GSK_TYPE_CAIRO_RENDERER;
goto out;
}
#ifdef GDK_WINDOWING_X11
if (GDK_IS_X11_DISPLAY (display))
renderer_type = GSK_TYPE_GL_RENDERER;
else
#endif
#ifdef GDK_WINDOWING_WAYLAND
if (GDK_IS_WAYLAND_DISPLAY (display))
renderer_type = GSK_TYPE_GL_RENDERER;
else
#endif
renderer_type = GSK_TYPE_CAIRO_RENDERER;
GSK_NOTE (RENDERER, g_print ("Creating renderer of type '%s' for display '%s'\n",
g_type_name (renderer_type),
G_OBJECT_TYPE_NAME (display)));
g_assert (renderer_type != G_TYPE_INVALID);
out:
return g_object_new (renderer_type, "display", display, NULL);
}