From 7afdd3fdb5c5fda9da87555b40a61e6a321c2414 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Thu, 17 Mar 2016 13:48:19 +0000 Subject: [PATCH] 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. --- gsk/Makefile.am | 142 +- gsk/gsk.h | 33 + gsk/gskcairorenderer.c | 195 +++ gsk/gskcairorendererprivate.h | 26 + gsk/gskdebug.c | 58 + gsk/gskdebugprivate.h | 43 + gsk/gskenums.h | 46 + gsk/gskenumtypes.c.template | 38 + gsk/gskenumtypes.h.template | 24 + gsk/gskglrenderer.c | 1092 +++++++++++++ gsk/gskglrendererprivate.h | 23 + gsk/gskprivate.c | 16 + gsk/gskprivate.h | 12 + gsk/gskrenderer.c | 1377 +++++++++++++++++ gsk/gskrenderer.h | 113 ++ gsk/gskrendererprivate.h | 62 + gsk/gskrendernode.c | 1246 +++++++++++++++ gsk/gskrendernode.h | 117 ++ gsk/gskrendernodeiter.c | 254 +++ gsk/gskrendernodeiter.h | 45 + gsk/gskrendernodeprivate.h | 124 ++ gsk/gsktypes.h | 29 + .../glsl/base-renderer-fragment.glsl | 13 + gsk/resources/glsl/base-renderer-vertex.glsl | 16 + tests/Makefile.am | 9 +- tests/testgskrenderer.c | 229 +++ 26 files changed, 5347 insertions(+), 35 deletions(-) create mode 100644 gsk/gsk.h create mode 100644 gsk/gskcairorenderer.c create mode 100644 gsk/gskcairorendererprivate.h create mode 100644 gsk/gskdebug.c create mode 100644 gsk/gskdebugprivate.h create mode 100644 gsk/gskenums.h create mode 100644 gsk/gskenumtypes.c.template create mode 100644 gsk/gskenumtypes.h.template create mode 100644 gsk/gskglrenderer.c create mode 100644 gsk/gskglrendererprivate.h create mode 100644 gsk/gskprivate.c create mode 100644 gsk/gskprivate.h create mode 100644 gsk/gskrenderer.c create mode 100644 gsk/gskrenderer.h create mode 100644 gsk/gskrendererprivate.h create mode 100644 gsk/gskrendernode.c create mode 100644 gsk/gskrendernode.h create mode 100644 gsk/gskrendernodeiter.c create mode 100644 gsk/gskrendernodeiter.h create mode 100644 gsk/gskrendernodeprivate.h create mode 100644 gsk/gsktypes.h create mode 100644 gsk/resources/glsl/base-renderer-fragment.glsl create mode 100644 gsk/resources/glsl/base-renderer-vertex.glsl create mode 100644 tests/testgskrenderer.c diff --git a/gsk/Makefile.am b/gsk/Makefile.am index 9f6f866c07..c72ba04155 100644 --- a/gsk/Makefile.am +++ b/gsk/Makefile.am @@ -1,23 +1,12 @@ include $(top_srcdir)/Makefile.decl --include $(INTROSPECTION_MAKEFILE) - -# Preamble -INTROSPECTION_GIRS = -INTROSPECTION_SCANNER_ARGS = \ - --add-include-path=../gdk \ - --warn-all -INTROSPECTION_COMPILER_ARGS = \ - --includedir=$(srcdir) \ - --includedir=. \ - --includedir=../gdk AM_CPPFLAGS = \ -DG_LOG_DOMAIN=\"Gsk\" \ -DGSK_COMPILATION \ - -I$(top_builddir) \ - -I$(top_builddir)/gsk \ -I$(top_srcdir) \ -I$(top_srcdir)/gdk \ + -I$(top_builddir) \ + -I$(top_builddir)/gsk \ $(GTK_DEBUG_FLAGS) \ $(GTK_WARN_FLAGS) \ $(GSK_DEP_CFLAGS) @@ -35,39 +24,124 @@ LDADD = \ BUILT_SOURCES = CLEANFILES = +DISTCLEANFILES = lib_LTLIBRARIES = -gsk_public_source_h = -gsk_private_source_h = -gsk_private_source_c = -gsk_source_c = +gsk_public_source_h = \ + gskenums.h \ + gskrenderer.h \ + gskrendernode.h \ + gskrendernodeiter.h \ + gsktypes.h +gsk_private_source_h = \ + gskcairorendererprivate.h \ + gskdebugprivate.h \ + gskglrendererprivate.h \ + gskrendererprivate.h \ + gskrendernodeprivate.h \ + gskprivate.h +gsk_private_source_c = \ + gskprivate.c +gsk_built_source_h = \ + gskenumtypes.h \ + gskresources.h +gsk_built_source_c = \ + gskenumtypes.c \ + gskresources.c +gsk_source_c = \ + gskcairorenderer.c \ + gskdebug.c \ + gskglrenderer.c \ + gskrenderer.c \ + gskrendernode.c \ + gskrendernodeiter.c -libgsk_3_la_SOURCES = $(all_sources) -libgsk_3_la_CFLAGS = $(AM_CFLAGS) $(GDK_HIDDEN_VISIBILITY_CFLAGS) -libgsk_3_la_LIBADD = $(GSK_DEP_LIBS) $(top_builddir)/gdk/libgdk-3.la -libgsk_3_la_LDFLAGS = $(LDADD) +all_sources = \ + $(gsk_public_source_h) \ + $(gsk_private_source_h) \ + $(gsk_built_source_h) \ + $(gsk_private_source_c) \ + $(gsk_source_c) -lib_LTLIBRARIES += libgsk-3.la +BUILT_SOURCES += $(gsk_built_source_h) $(gsk_built_source_c) gsk.resources.xml -gskincludedir = $(includedir)/gtk-3.0/gsk -gskinclude_HEADERS = $(gsk_public_source_h) gsk.h +gskenumtypes.h: $(gsk_public_source_h) gskenumtypes.h.template + $(AM_V_GEN) $(GLIB_MKENUMS) --template $(filter %.template,$^) $(filter-out %.template,$^) > \ + gskenumtypes.h.tmp && \ + mv gskenumtypes.h.tmp gskenumtypes.h + +gskenumtypes.c: $(gsk_public_source_h) gskenumtypes.c.template + $(AM_V_GEN) $(GLIB_MKENUMS) --template $(filter %.template,$^) $(filter-out %.template,$^) > \ + gskenumtypes.c.tmp && \ + mv gskenumtypes.c.tmp gskenumtypes.c + +EXTRA_DIST += gskenumtypes.h.template gskenumtypes.c.template +DISTCLEANFILES += gskenumtypes.h gskenumtypes.c + +resource_files = $(shell $(GLIB_COMPILE_RESOURCES) --sourcedir=$(srcdir) --generate-dependencies $(builddir)/gsk.resources.xml) + +gsk.resources.xml: Makefile.am + $(AM_V_GEN) echo "" > $@; \ + echo "" >> $@; \ + echo " " >> $@; \ + for f in $(top_srcdir)/gsk/resources/glsl/*; do \ + n=`basename $$f`; \ + echo " resources/glsl/$$n" >> $@; \ + done; \ + echo " " >> $@; \ + echo "" >> $@ + +gskresources.h: gsk.resources.xml + $(AM_V_GEN) $(GLIB_COMPILE_RESOURCES) $< \ + --target=$@ --sourcedir=$(srcdir) --c-name _gsk --generate-header --manual-register + +gskresources.c: gsk.resources.xml $(resource_files) + $(AM_V_GEN) $(GLIB_COMPILE_RESOURCES) $< \ + --target=$@ --sourcedir=$(srcdir) --c-name _gsk --generate-source --manual-register + +EXTRA_DIST += $(resource_files) +CLEANFILES += gsk.resources.xml +DISTCLEANFILES += gskresources.h gskresources.c + +libgsk_4_la_SOURCES = $(all_sources) +nodist_libgsk_4_la_SOURCES = $(gsk_built_source_h) $(gsk_built_source_c) +libgsk_4_la_CFLAGS = $(AM_CFLAGS) $(GDK_HIDDEN_VISIBILITY_CFLAGS) +libgsk_4_la_LIBADD = $(GSK_DEP_LIBS) $(top_builddir)/gdk/libgdk-4.la +libgsk_4_la_LDFLAGS = $(LDADD) + +lib_LTLIBRARIES += libgsk-4.la + +gskincludedir = $(includedir)/gtk-4.0/gsk +gskinclude_HEADERS = $(gsk_public_source_h) gskenumtypes.h gsk.h + +-include $(INTROSPECTION_MAKEFILE) +INTROSPECTION_GIRS = +INTROSPECTION_SCANNER_ENV = \ + CC="$(CC)" +INTROSPECTION_SCANNER_ARGS = \ + --add-include-path=../gdk \ + --warn-all +INTROSPECTION_COMPILER_ARGS = \ + --includedir=$(srcdir) \ + --includedir=. \ + --includedir=../gdk if HAVE_INTROSPECTION -introspection_files = $(gsk_source_c) $(gsk_public_source_h) +introspection_files = $(filter-out $(wildcard *private.h),$(all_sources)) -Gsk-3.0.gir: libgsk-3.la Makefile -Gsk_3_0_gir_SCANNERFLAGS = \ +Gsk-4.0.gir: libgsk-4.la Makefile +Gsk_4_0_gir_SCANNERFLAGS = \ --add-include-path=$(top_builddir)/gdk \ - --include-uninstalled=$(top_builddir)/gdk/Gdk-3.0.gir \ + --include-uninstalled=$(top_builddir)/gdk/Gdk-4.0.gir \ --c-include="gsk/gsk.h" -Gsk_3_0_gir_LIBS = libgsk-3.la -Gsk_3_0_gir_FILES = $(introspection_files) -Gsk_3_0_gir_CFLAGS = $(AM_CPPFLAGS) -Gsk_3_0_gir_EXPORT_PACKAGES = gsk-3.0 -Gsk_3_0_gir_INCLUDES = GObject-2.0 cairo-1.0 Graphene-1.0 -INTROSPECTION_GIRS += Gsk-3.0.gir +Gsk_4_0_gir_LIBS = libgsk-4.la $(top_builddir)/gdk/libgdk-4.la +Gsk_4_0_gir_FILES = $(introspection_files) +Gsk_4_0_gir_CFLAGS = $(AM_CPPFLAGS) +Gsk_4_0_gir_EXPORT_PACKAGES = gsk-4.0 +Gsk_4_0_gir_INCLUDES = GObject-2.0 cairo-1.0 Graphene-1.0 +INTROSPECTION_GIRS += Gsk-4.0.gir girdir = $(datadir)/gir-1.0 gir_DATA = $(INTROSPECTION_GIRS) diff --git a/gsk/gsk.h b/gsk/gsk.h new file mode 100644 index 0000000000..01c45693f5 --- /dev/null +++ b/gsk/gsk.h @@ -0,0 +1,33 @@ +/* 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 . + */ + +#ifndef __GSK_H__ +#define __GSK_H__ + +#define __GSK_H_INSIDE__ + +#include +#include +#include +#include + +#include +#include + +#undef __GSK_H_INSIDE__ + +#endif /* __GSK_H__ */ diff --git a/gsk/gskcairorenderer.c b/gsk/gskcairorenderer.c new file mode 100644 index 0000000000..62b7a47cfe --- /dev/null +++ b/gsk/gskcairorenderer.c @@ -0,0 +1,195 @@ +#include "config.h" + +#include "gskcairorendererprivate.h" + +#include "gskdebugprivate.h" +#include "gskrendererprivate.h" +#include "gskrendernodeiter.h" +#include "gskrendernodeprivate.h" + +struct _GskCairoRenderer +{ + GskRenderer parent_instance; + + graphene_rect_t viewport; +}; + +struct _GskCairoRendererClass +{ + GskRendererClass parent_class; +}; + +G_DEFINE_TYPE (GskCairoRenderer, gsk_cairo_renderer, GSK_TYPE_RENDERER) + +static gboolean +gsk_cairo_renderer_realize (GskRenderer *renderer) +{ + return TRUE; +} + +static void +gsk_cairo_renderer_unrealize (GskRenderer *renderer) +{ + +} + +static void +gsk_cairo_renderer_render_node (GskCairoRenderer *self, + GskRenderNode *node, + cairo_t *cr) +{ + GskRenderNodeIter iter; + GskRenderNode *child; + gboolean pop_group = FALSE; + graphene_matrix_t mvp; + cairo_matrix_t ctm; + graphene_rect_t frame; + + if (gsk_render_node_is_hidden (node)) + return; + + cairo_save (cr); + + gsk_render_node_get_world_matrix (node, &mvp); + if (graphene_matrix_to_2d (&mvp, &ctm.xx, &ctm.yx, &ctm.xy, &ctm.yy, &ctm.x0, &ctm.y0)) + { + GSK_NOTE (CAIRO, g_print ("CTM = { .xx = %g, .yx = %g, .xy = %g, .yy = %g, .x0 = %g, .y0 = %g }\n", + ctm.xx, ctm.yx, + ctm.xy, ctm.yy, + ctm.x0, ctm.y0)); + cairo_transform (cr, &ctm); + } + else + g_critical ("Invalid non-affine transformation for node %p", node); + + gsk_render_node_get_bounds (node, &frame); + GSK_NOTE (CAIRO, g_print ("CLIP = { .x = %g, .y = %g, .width = %g, .height = %g }\n", + frame.origin.x, frame.origin.y, + frame.size.width, frame.size.height)); + + if (!GSK_RENDER_MODE_CHECK (GEOMETRY)) + { + cairo_rectangle (cr, frame.origin.x, frame.origin.y, frame.size.width, frame.size.height); + cairo_clip (cr); + } + + if (!gsk_render_node_is_opaque (node) && gsk_render_node_get_opacity (node) != 1.0) + { + GSK_NOTE (CAIRO, g_print ("Pushing opacity group (opacity:%g)\n", + gsk_render_node_get_opacity (node))); + cairo_push_group (cr); + pop_group = TRUE; + } + + GSK_NOTE (CAIRO, g_print ("Rendering surface %p for node %p at %g, %g\n", + gsk_render_node_get_surface (node), + node, + frame.origin.x, frame.origin.y)); + cairo_set_source_surface (cr, gsk_render_node_get_surface (node), frame.origin.x, frame.origin.y); + cairo_paint (cr); + + if (GSK_RENDER_MODE_CHECK (GEOMETRY)) + { + cairo_save (cr); + cairo_set_operator (cr, CAIRO_OPERATOR_OVER); + cairo_rectangle (cr, frame.origin.x - 1, frame.origin.y - 1, frame.size.width + 2, frame.size.height + 2); + cairo_set_line_width (cr, 2); + cairo_set_source_rgba (cr, 0, 0, 0, 0.5); + cairo_stroke (cr); + cairo_restore (cr); + } + + cairo_matrix_invert (&ctm); + cairo_transform (cr, &ctm); + + if (gsk_render_node_get_n_children (node) != 0) + { + GSK_NOTE (CAIRO, g_print ("Drawing %d children of node [%p]\n", + gsk_render_node_get_n_children (node), + node)); + gsk_render_node_iter_init (&iter, node); + while (gsk_render_node_iter_next (&iter, &child)) + gsk_cairo_renderer_render_node (self, child, cr); + } + + if (pop_group) + { + cairo_pop_group_to_source (cr); + cairo_set_operator (cr, CAIRO_OPERATOR_OVER); + cairo_paint_with_alpha (cr, gsk_render_node_get_opacity (node)); + } + + cairo_restore (cr); +} + +static void +gsk_cairo_renderer_resize_viewport (GskRenderer *renderer, + const graphene_rect_t *viewport) +{ + GskCairoRenderer *self = GSK_CAIRO_RENDERER (renderer); + + self->viewport = *viewport; +} + +static void +gsk_cairo_renderer_render (GskRenderer *renderer) +{ + GskCairoRenderer *self = GSK_CAIRO_RENDERER (renderer); + cairo_surface_t *target = gsk_renderer_get_surface (renderer); + GskRenderNode *root = gsk_renderer_get_root_node (renderer); + cairo_t *cr = cairo_create (target); + + if (GSK_RENDER_MODE_CHECK (GEOMETRY)) + { + cairo_save (cr); + cairo_set_operator (cr, CAIRO_OPERATOR_OVER); + cairo_rectangle (cr, + self->viewport.origin.x, + self->viewport.origin.y, + self->viewport.size.width, + self->viewport.size.height); + cairo_set_source_rgba (cr, 0, 0, 0.85, 0.5); + cairo_stroke (cr); + cairo_restore (cr); + } + + gsk_cairo_renderer_render_node (self, root, cr); + + cairo_destroy (cr); +} + +static void +gsk_cairo_renderer_clear (GskRenderer *renderer) +{ + cairo_surface_t *surface = gsk_renderer_get_surface (renderer); + cairo_t *cr = cairo_create (surface); + + cairo_set_operator (cr, CAIRO_OPERATOR_OVER); + + if (gsk_renderer_get_use_alpha (renderer)) + cairo_set_source_rgba (cr, 0, 0, 0, 0); + else + cairo_set_source_rgb (cr, 0, 0, 0); + + cairo_paint (cr); + + cairo_destroy (cr); +} + +static void +gsk_cairo_renderer_class_init (GskCairoRendererClass *klass) +{ + GskRendererClass *renderer_class = GSK_RENDERER_CLASS (klass); + + renderer_class->realize = gsk_cairo_renderer_realize; + renderer_class->unrealize = gsk_cairo_renderer_unrealize; + renderer_class->resize_viewport = gsk_cairo_renderer_resize_viewport; + renderer_class->clear = gsk_cairo_renderer_clear; + renderer_class->render = gsk_cairo_renderer_render; +} + +static void +gsk_cairo_renderer_init (GskCairoRenderer *self) +{ + +} diff --git a/gsk/gskcairorendererprivate.h b/gsk/gskcairorendererprivate.h new file mode 100644 index 0000000000..7a9bd23456 --- /dev/null +++ b/gsk/gskcairorendererprivate.h @@ -0,0 +1,26 @@ +#ifndef __GSK_CAIRO_RENDERER_PRIVATE_H__ +#define __GSK_CAIRO_RENDERER_PRIVATE_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GSK_TYPE_CAIRO_RENDERER (gsk_cairo_renderer_get_type ()) + +#define GSK_CAIRO_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSK_TYPE_CAIRO_RENDERER, GskCairoRenderer)) +#define GSK_IS_CAIRO_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSK_TYPE_CAIRO_RENDERER)) +#define GSK_CAIRO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSK_TYPE_CAIRO_RENDERER, GskCairoRendererClass)) +#define GSK_IS_CAIRO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSK_TYPE_CAIRO_RENDERER)) +#define GSK_CAIRO_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSK_TYPE_CAIRO_RENDERER, GskCairoRendererClass)) + +typedef struct _GskCairoRenderer GskCairoRenderer; +typedef struct _GskCairoRendererClass GskCairoRendererClass; + +GType gsk_cairo_renderer_get_type (void) G_GNUC_CONST; + +GskRenderer *gsk_cairo_renderer_new (void); + +G_END_DECLS + +#endif /* __GSK_CAIRO_RENDERER_PRIVATE_H__ */ diff --git a/gsk/gskdebug.c b/gsk/gskdebug.c new file mode 100644 index 0000000000..ebc5366876 --- /dev/null +++ b/gsk/gskdebug.c @@ -0,0 +1,58 @@ +#include "gskdebugprivate.h" + +#ifdef G_ENABLE_DEBUG +static const GDebugKey gsk_debug_keys[] = { + { "rendernode", GSK_DEBUG_RENDER_NODE }, + { "renderer", GSK_DEBUG_RENDERER }, + { "cairo", GSK_DEBUG_CAIRO }, + { "opengl", GSK_DEBUG_OPENGL }, +}; +#endif + +static const GDebugKey gsk_rendering_keys[] = { + { "geometry", GSK_RENDERING_MODE_GEOMETRY }, +}; + +gboolean +gsk_check_debug_flags (GskDebugFlags flags) +{ +#ifdef G_ENABLE_DEBUG + static volatile gsize gsk_debug_flags__set; + static guint gsk_debug_flags; + + if (g_once_init_enter (&gsk_debug_flags__set)) + { + const char *env = g_getenv ("GSK_DEBUG"); + + gsk_debug_flags = g_parse_debug_string (env, + (GDebugKey *) gsk_debug_keys, + G_N_ELEMENTS (gsk_debug_keys)); + + g_once_init_leave (&gsk_debug_flags__set, TRUE); + } + + return (gsk_debug_flags & flags) != 0; +#else + return FALSE; +#endif +} + +gboolean +gsk_check_rendering_flags (GskRenderingMode flags) +{ + static volatile gsize gsk_rendering_flags__set; + static guint gsk_rendering_flags; + + if (g_once_init_enter (&gsk_rendering_flags__set)) + { + const char *env = g_getenv ("GSK_RENDERING_MODE"); + + gsk_rendering_flags = g_parse_debug_string (env, + (GDebugKey *) gsk_rendering_keys, + G_N_ELEMENTS (gsk_rendering_keys)); + + g_once_init_leave (&gsk_rendering_flags__set, TRUE); + } + + return (gsk_rendering_flags & flags) != 0; +} diff --git a/gsk/gskdebugprivate.h b/gsk/gskdebugprivate.h new file mode 100644 index 0000000000..439be0757b --- /dev/null +++ b/gsk/gskdebugprivate.h @@ -0,0 +1,43 @@ +#ifndef __GSK_DEBUG_PRIVATE_H__ +#define __GSK_DEBUG_PRIVATE_H__ + +#include + +G_BEGIN_DECLS + +typedef enum { + GSK_DEBUG_RENDER_NODE = 1 << 0, + GSK_DEBUG_RENDERER = 1 << 1, + GSK_DEBUG_CAIRO = 1 << 2, + GSK_DEBUG_OPENGL = 1 << 3 +} GskDebugFlags; + +typedef enum { + GSK_RENDERING_MODE_GEOMETRY = 1 << 0 +} GskRenderingMode; + +gboolean gsk_check_debug_flags (GskDebugFlags flags); + +gboolean gsk_check_rendering_flags (GskRenderingMode flags); + +#ifdef G_ENABLE_DEBUG + +#define GSK_DEBUG_CHECK(type) G_UNLIKELY (gsk_check_debug_flags (GSK_DEBUG_ ## type)) +#define GSK_RENDER_MODE_CHECK(type) G_UNLIKELY (gsk_check_rendering_flags (GSK_RENDERING_MODE_ ## type)) + +#define GSK_NOTE(type,action) G_STMT_START { \ + if (GSK_DEBUG_CHECK (type)) { \ + action; \ + } } G_STMT_END + +#else + +#define GSK_RENDER_MODE_CHECK(type) 0 +#define GSK_DEBUG_CHECK(type) 0 +#define GSK_NOTE(type,action) + +#endif + +G_END_DECLS + +#endif /* __GSK_DEBUG_PRIVATE_H__ */ diff --git a/gsk/gskenums.h b/gsk/gskenums.h new file mode 100644 index 0000000000..b831d4903e --- /dev/null +++ b/gsk/gskenums.h @@ -0,0 +1,46 @@ +/* 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 . + */ + +#ifndef __GSK_ENUMS_H__ +#define __GSK_ENUMS_H__ + +#if !defined (__GSK_H_INSIDE__) && !defined (GSK_COMPILATION) +#error "Only can be included directly." +#endif + +/** + * GskScalingFilter: + * @GSK_SCALING_FILTER_LINEAR: linear interpolation filter + * @GSK_SCALING_FILTER_NEAREST: nearest neighbor interpolation filter + * @GSK_SCALING_FILTER_TRILINEAR: linear interpolation along each axis, + * plus mipmap generation, with linear interpolation along the mipmap + * levels + * + * The filters used when scaling texture data. + * + * The actual implementation of each filter is deferred to the + * rendering pipeline. + * + * Since: 3.22 + */ +typedef enum { + GSK_SCALING_FILTER_LINEAR, + GSK_SCALING_FILTER_NEAREST, + GSK_SCALING_FILTER_TRILINEAR +} GskScalingFilter; + +#endif /* __GSK_TYPES_H__ */ diff --git a/gsk/gskenumtypes.c.template b/gsk/gskenumtypes.c.template new file mode 100644 index 0000000000..430ea8fd26 --- /dev/null +++ b/gsk/gskenumtypes.c.template @@ -0,0 +1,38 @@ +/*** BEGIN file-header ***/ +#include "config.h" +#include "gskenumtypes.h" +#include + +/*** END file-header ***/ + +/*** BEGIN file-production ***/ +/* enumerations from "@filename@" */ +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GType +@enum_name@_get_type (void) +{ + static volatile gsize g_define_type_id__volatile = 0; + + if (g_once_init_enter (&g_define_type_id__volatile)) + { + static const G@Type@Value values[] = { +/*** END value-header ***/ + +/*** BEGIN value-production ***/ + { @VALUENAME@, "@VALUENAME@", "@valuenick@" }, +/*** END value-production ***/ + +/*** BEGIN value-tail ***/ + { 0, NULL, NULL } + }; + GType g_define_type_id = + g_@type@_register_static (g_intern_static_string ("@EnumName@"), values); + g_once_init_leave (&g_define_type_id__volatile, g_define_type_id); + } + + return g_define_type_id__volatile; +} + +/*** END value-tail ***/ diff --git a/gsk/gskenumtypes.h.template b/gsk/gskenumtypes.h.template new file mode 100644 index 0000000000..15a8ac6525 --- /dev/null +++ b/gsk/gskenumtypes.h.template @@ -0,0 +1,24 @@ +/*** BEGIN file-header ***/ +#ifndef __GSK_ENUM_TYPES_H__ +#define __GSK_ENUM_TYPES_H__ + +#include + +G_BEGIN_DECLS +/*** END file-header ***/ + +/*** BEGIN file-production ***/ + +/* enumerations from "@filename@" */ +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GDK_AVAILABLE_IN_ALL GType @enum_name@_get_type (void) G_GNUC_CONST; +#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type ()) +/*** END value-header ***/ + +/*** BEGIN file-tail ***/ +G_END_DECLS + +#endif /* __GSK_ENUM_TYPES_H__ */ +/*** END file-tail ***/ diff --git a/gsk/gskglrenderer.c b/gsk/gskglrenderer.c new file mode 100644 index 0000000000..96f04afe7d --- /dev/null +++ b/gsk/gskglrenderer.c @@ -0,0 +1,1092 @@ +#include "config.h" + +#include "gskglrendererprivate.h" + +#include "gskdebugprivate.h" +#include "gskenums.h" +#include "gskrendererprivate.h" +#include "gskrendernodeprivate.h" +#include "gskrendernodeiter.h" + +#include "gskprivate.h" + +#include + +typedef struct { + /* Back pointer to the node, only meant for comparison */ + GskRenderNode *node; + + graphene_point3d_t min; + graphene_point3d_t max; + + graphene_size_t size; + + graphene_matrix_t mvp; + + gboolean opaque : 1; + float opacity; + float z; + + const char *name; + + guint vao_id; + guint texture_id; + guint program_id; + guint mvp_location; + guint map_location; + guint uv_location; + guint position_location; + guint alpha_location; + guint buffer_id; +} RenderItem; + +struct _GskGLRenderer +{ + GskRenderer parent_instance; + + GdkGLContext *context; + + graphene_matrix_t mvp; + graphene_frustum_t frustum; + + guint frame_buffer; + guint render_buffer; + guint depth_stencil_buffer; + guint texture_id; + + guint program_id; + guint mvp_location; + guint map_location; + guint uv_location; + guint position_location; + guint alpha_location; + + guint vao_id; + + GArray *opaque_render_items; + GArray *transparent_render_items; + + gboolean has_buffers : 1; + gboolean has_alpha : 1; + gboolean has_stencil_buffer : 1; + gboolean has_depth_buffer : 1; +}; + +struct _GskGLRendererClass +{ + GskRendererClass parent_class; +}; + +static void render_item_clear (gpointer data_); + +G_DEFINE_TYPE (GskGLRenderer, gsk_gl_renderer, GSK_TYPE_RENDERER) + +static void +gsk_gl_renderer_dispose (GObject *gobject) +{ + GskGLRenderer *self = GSK_GL_RENDERER (gobject); + + g_clear_object (&self->context); + + G_OBJECT_CLASS (gsk_gl_renderer_parent_class)->dispose (gobject); +} + +static void +gsk_gl_renderer_create_buffers (GskGLRenderer *self) +{ + if (self->has_buffers) + return; + + GSK_NOTE (OPENGL, g_print ("Creating buffers\n")); + + glGenFramebuffersEXT (1, &self->frame_buffer); + + if (gsk_renderer_get_use_alpha (GSK_RENDERER (self))) + { + if (self->texture_id == 0) + glGenTextures (1, &self->texture_id); + + if (self->render_buffer != 0) + { + glDeleteRenderbuffersEXT (1, &self->render_buffer); + self->render_buffer = 0; + } + } + else + { + if (self->render_buffer == 0) + glGenRenderbuffersEXT (1, &self->render_buffer); + + if (self->texture_id != 0) + { + glDeleteTextures (1, &self->texture_id); + self->texture_id = 0; + } + } + + if (self->has_depth_buffer || self->has_stencil_buffer) + { + if (self->depth_stencil_buffer == 0) + glGenRenderbuffersEXT (1, &self->depth_stencil_buffer); + } + else + { + if (self->depth_stencil_buffer != 0) + { + glDeleteRenderbuffersEXT (1, &self->depth_stencil_buffer); + self->depth_stencil_buffer = 0; + } + } + + /* We only have one VAO at the moment */ + glGenVertexArrays (1, &self->vao_id); + glBindVertexArray (self->vao_id); + + self->has_buffers = TRUE; +} + +static void +gsk_gl_renderer_allocate_buffers (GskGLRenderer *self, + int width, + int height) +{ + if (self->context == NULL) + return; + + if (self->texture_id != 0) + { + glBindTexture (GL_TEXTURE_2D, self->texture_id); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); + } + + if (self->render_buffer != 0) + { + glBindRenderbuffer (GL_RENDERBUFFER, self->render_buffer); + glRenderbufferStorage (GL_RENDERBUFFER, GL_RGB8, width, height); + } + + if (self->has_depth_buffer || self->has_stencil_buffer) + { + glBindRenderbuffer (GL_RENDERBUFFER, self->depth_stencil_buffer); + + if (self->has_stencil_buffer) + glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height); + else + glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height); + } +} + +static void +gsk_gl_renderer_attach_buffers (GskGLRenderer *self) +{ + gsk_gl_renderer_create_buffers (self); + + GSK_NOTE (OPENGL, g_print ("Attaching buffers\n")); + + glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, self->frame_buffer); + + if (self->texture_id != 0) + { + glFramebufferTexture2D (GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, + GL_TEXTURE_2D, self->texture_id, 0); + } + else if (self->render_buffer != 0) + { + glFramebufferRenderbufferEXT (GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, + GL_RENDERBUFFER_EXT, self->render_buffer); + } + + if (self->depth_stencil_buffer != 0) + { + if (self->has_depth_buffer) + glFramebufferRenderbufferEXT (GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, + GL_RENDERBUFFER_EXT, self->depth_stencil_buffer); + + if (self->has_stencil_buffer) + glFramebufferRenderbufferEXT (GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, + GL_RENDERBUFFER_EXT, self->depth_stencil_buffer); + } +} + +static void +gsk_gl_renderer_destroy_buffers (GskGLRenderer *self) +{ + if (self->context == NULL) + return; + + if (!self->has_buffers) + return; + + GSK_NOTE (OPENGL, g_print ("Destroying buffers\n")); + + gdk_gl_context_make_current (self->context); + + if (self->vao_id != 0) + { + glDeleteVertexArrays (1, &self->vao_id); + self->vao_id = 0; + } + + if (self->depth_stencil_buffer != 0) + { + glDeleteRenderbuffersEXT (1, &self->depth_stencil_buffer); + self->depth_stencil_buffer = 0; + } + + if (self->render_buffer != 0) + { + glDeleteRenderbuffersEXT (1, &self->render_buffer); + self->render_buffer = 0; + } + + if (self->texture_id != 0) + { + glDeleteTextures (1, &self->texture_id); + self->texture_id = 0; + } + + if (self->frame_buffer != 0) + { + glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, 0); + glDeleteFramebuffersEXT (1, &self->frame_buffer); + self->frame_buffer = 0; + } + + self->has_buffers = FALSE; +} + +static guint +create_shader (int type, + const char *code) +{ + guint shader; + int status; + + shader = glCreateShader (type); + glShaderSource (shader, 1, &code, NULL); + glCompileShader (shader); + + glGetShaderiv (shader, GL_COMPILE_STATUS, &status); + if (status == GL_FALSE) + { + int log_len; + char *buffer; + + glGetShaderiv (shader, GL_INFO_LOG_LENGTH, &log_len); + + buffer = g_malloc0 (log_len + 1); + glGetShaderInfoLog (shader, log_len, NULL, buffer); + + g_critical ("Compile failure in %s shader:\n%s", + type == GL_VERTEX_SHADER ? "vertex" : "fragment", + buffer); + g_free (buffer); + + glDeleteShader (shader); + + return 0; + } + + return shader; +} + +static void +gsk_gl_renderer_create_program (GskGLRenderer *self) +{ + guint vertex_shader = 0, fragment_shader = 0; + GBytes *source; + int status; + + GSK_NOTE (OPENGL, g_print ("Compiling vertex shader\n")); + source = g_resources_lookup_data ("/org/gtk/libgsk/glsl/base-renderer-vertex.glsl", 0, NULL); + vertex_shader = create_shader (GL_VERTEX_SHADER, g_bytes_get_data (source, NULL)); + g_bytes_unref (source); + if (vertex_shader == 0) + goto out; + + GSK_NOTE (OPENGL, g_print ("Compiling fragment shader\n")); + source = g_resources_lookup_data ("/org/gtk/libgsk/glsl/base-renderer-fragment.glsl", 0, NULL); + fragment_shader = create_shader (GL_FRAGMENT_SHADER, g_bytes_get_data (source, NULL)); + g_bytes_unref (source); + if (fragment_shader == 0) + goto out; + + self->program_id = glCreateProgram (); + glAttachShader (self->program_id, vertex_shader); + glAttachShader (self->program_id, fragment_shader); + glLinkProgram (self->program_id); + + glGetProgramiv (self->program_id, GL_LINK_STATUS, &status); + if (status == GL_FALSE) + { + char *buffer = NULL; + int log_len = 0; + + glGetProgramiv (self->program_id, GL_INFO_LOG_LENGTH, &log_len); + + buffer = g_malloc0 (log_len + 1); + glGetProgramInfoLog (self->program_id, log_len, NULL, buffer); + + g_critical ("Linking failure in shader:\n%s", buffer); + g_free (buffer); + + glDeleteProgram (self->program_id); + self->program_id = 0; + + goto out; + } + + /* Find the location of each uniform and attribute we use in our + * shaders + */ + self->mvp_location = glGetUniformLocation (self->program_id, "mvp"); + self->map_location = glGetUniformLocation (self->program_id, "map"); + self->alpha_location = glGetUniformLocation (self->program_id, "alpha"); + self->position_location = glGetAttribLocation (self->program_id, "position"); + self->uv_location = glGetAttribLocation (self->program_id, "uv"); + + GSK_NOTE (OPENGL, g_print ("Program [%d] { mvp:%u, map:%u, alpha:%u, position:%u, uv:%u }\n", + self->program_id, + self->mvp_location, + self->map_location, + self->alpha_location, + self->position_location, + self->uv_location)); + + /* We can detach and destroy the shaders from the linked program */ + glDetachShader (self->program_id, vertex_shader); + glDetachShader (self->program_id, fragment_shader); + +out: + if (vertex_shader != 0) + glDeleteShader (vertex_shader); + if (fragment_shader != 0) + glDeleteShader (fragment_shader); +} + +static void +gsk_gl_renderer_destroy_program (GskGLRenderer *self) +{ + if (self->program_id != 0) + { + glDeleteProgram (self->program_id); + self->program_id = 0; + } +} + +static gboolean +gsk_gl_renderer_realize (GskRenderer *renderer) +{ + GskGLRenderer *self = GSK_GL_RENDERER (renderer); + GError *error = NULL; + + /* If we didn't get a GdkGLContext before realization, try creating + * one now, for our exclusive use. + */ + if (self->context == NULL) + { + GdkWindow *window = gsk_renderer_get_window (renderer); + + if (window == NULL) + return FALSE; + + self->context = gdk_window_create_gl_context (window, &error); + if (error != NULL) + { + g_critical ("Unable to create GL context for renderer: %s", + error->message); + g_error_free (error); + + return FALSE; + } + } + + gdk_gl_context_realize (self->context, &error); + if (error != NULL) + { + g_critical ("Unable to realize GL renderer: %s", error->message); + g_error_free (error); + return FALSE; + } + + gdk_gl_context_make_current (self->context); + + GSK_NOTE (OPENGL, g_print ("Creating buffers and programs\n")); + + gsk_gl_renderer_create_buffers (self); + gsk_gl_renderer_create_program (self); + + self->opaque_render_items = g_array_sized_new (FALSE, FALSE, sizeof (RenderItem), 16); + g_array_set_clear_func (self->opaque_render_items, render_item_clear); + + self->transparent_render_items = g_array_sized_new (FALSE, FALSE, sizeof (RenderItem), 16); + g_array_set_clear_func (self->opaque_render_items, render_item_clear); + + return TRUE; +} + +static void +gsk_gl_renderer_unrealize (GskRenderer *renderer) +{ + GskGLRenderer *self = GSK_GL_RENDERER (renderer); + + if (self->context == NULL) + return; + + gdk_gl_context_make_current (self->context); + + g_clear_pointer (&self->opaque_render_items, g_array_unref); + g_clear_pointer (&self->transparent_render_items, g_array_unref); + + gsk_gl_renderer_destroy_buffers (self); + gsk_gl_renderer_destroy_program (self); + + if (self->context == gdk_gl_context_get_current ()) + gdk_gl_context_clear_current (); +} + +static void +gsk_gl_renderer_resize_viewport (GskRenderer *renderer, + const graphene_rect_t *viewport) +{ +} + +static void +gsk_gl_renderer_update (GskRenderer *renderer, + const graphene_matrix_t *modelview, + const graphene_matrix_t *projection) +{ + GskGLRenderer *self = GSK_GL_RENDERER (renderer); + + GSK_NOTE (OPENGL, g_print ("Updating the modelview/projection\n")); + + graphene_matrix_multiply (modelview, projection, &self->mvp); + + graphene_frustum_init_from_matrix (&self->frustum, &self->mvp); +} + +static void +render_item_clear (gpointer data_) +{ + RenderItem *item = data_; + + GSK_NOTE (OPENGL, g_print ("Destroying render item [%p] buffer %u\n", + item, + item->buffer_id)); + glDeleteBuffers (1, &item->buffer_id); + item->buffer_id = 0; + + GSK_NOTE (OPENGL, g_print ("Destroying render item [%p] texture %u\n", + item, + item->texture_id)); + glDeleteTextures (1, &item->texture_id); + item->texture_id = 0; + + graphene_matrix_init_identity (&item->mvp); + + item->opacity = 1; +} + +#define N_VERTICES 6 + +static void +render_item (RenderItem *item) +{ + struct vertex_info { + float position[2]; + float uv[2]; + }; + float mvp[16]; + + glBindVertexArray (item->vao_id); + + /* Generate the vertex buffer for the texture quad */ + if (item->buffer_id == 0) + { + struct vertex_info vertex_data[] = { + { { item->min.x, item->min.y }, { 0, 0 }, }, + { { item->min.x, item->max.y }, { 0, 1 }, }, + { { item->max.x, item->min.y }, { 1, 0 }, }, + + { { item->max.x, item->max.y }, { 1, 1 }, }, + { { item->min.x, item->max.y }, { 0, 1 }, }, + { { item->max.x, item->min.y }, { 1, 0 }, }, + }; + + GSK_NOTE (OPENGL, g_print ("Creating quad for render item [%p]\n", item)); + + glGenBuffers (1, &item->buffer_id); + glBindBuffer (GL_ARRAY_BUFFER, item->buffer_id); + + /* The data won't change */ + glBufferData (GL_ARRAY_BUFFER, sizeof (vertex_data), vertex_data, GL_STATIC_DRAW); + + /* Set up the buffers with the computed position and texels */ + glEnableVertexAttribArray (item->position_location); + glVertexAttribPointer (item->position_location, 2, GL_FLOAT, GL_FALSE, + sizeof (struct vertex_info), + (void *) G_STRUCT_OFFSET (struct vertex_info, position)); + glEnableVertexAttribArray (item->uv_location); + glVertexAttribPointer (item->uv_location, 2, GL_FLOAT, GL_FALSE, + sizeof (struct vertex_info), + (void *) G_STRUCT_OFFSET (struct vertex_info, uv)); + } + else + { + /* We already set up the vertex buffer, so we just need to reuse it */ + glBindBuffer (GL_ARRAY_BUFFER, item->buffer_id); + glEnableVertexAttribArray (item->position_location); + glEnableVertexAttribArray (item->uv_location); + } + + glUseProgram (item->program_id); + + /* Use texture unit 0 for the sampler */ + glActiveTexture (GL_TEXTURE0); + glBindTexture (GL_TEXTURE_2D, item->texture_id); + glUniform1i (item->map_location, 0); + + /* Pass the opacity component */ + glUniform1f (item->alpha_location, item->opaque ? 1 : item->opacity); + + /* Pass the mvp to the vertex shader */ + GSK_NOTE (OPENGL, graphene_matrix_print (&item->mvp)); + graphene_matrix_to_float (&item->mvp, mvp); + glUniformMatrix4fv (item->mvp_location, 1, GL_FALSE, mvp); + + /* Draw the quad */ + GSK_NOTE (OPENGL, g_print ("Drawing item <%s>[%p] with opacity: %g\n", + item->name, + item, + item->opaque ? 1 : item->opacity)); + + glDrawArrays (GL_TRIANGLES, 0, N_VERTICES); + + /* Reset the state */ + glBindTexture (GL_TEXTURE_2D, 0); + glDisableVertexAttribArray (item->position_location); + glDisableVertexAttribArray (item->uv_location); + glUseProgram (0); +} + +static void +surface_to_texture (cairo_surface_t *surface, + graphene_rect_t *clip, + int min_filter, + int mag_filter, + guint *texture_out) +{ + cairo_surface_t *tmp; + guint texture_id; + + cairo_surface_flush (surface); + + tmp = cairo_surface_map_to_image (surface, &(cairo_rectangle_int_t) { + 0, 0, + clip->size.width, + clip->size.height + }); + + glGenTextures (1, &texture_id); + glBindTexture (GL_TEXTURE_2D, texture_id); + + GSK_NOTE (OPENGL, g_print ("Uploading px@[%p] { w:%g, h:%g } to texid:%d\n", + cairo_image_surface_get_data (tmp), + clip->size.width, + clip->size.height, + texture_id)); + + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter); + + glPixelStorei (GL_UNPACK_ALIGNMENT, 4); + glPixelStorei (GL_UNPACK_ROW_LENGTH, cairo_image_surface_get_stride (tmp) / 4); + glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, + clip->size.width, + clip->size.height, + 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, + cairo_image_surface_get_data (tmp)); + glPixelStorei (GL_UNPACK_ROW_LENGTH, 0); + + if (min_filter != GL_NEAREST) + glGenerateMipmap (GL_TEXTURE_2D); + + GSK_NOTE (OPENGL, g_print ("New texture id %d from surface %p\n", texture_id, surface)); + + cairo_surface_unmap_image (surface, tmp); + + *texture_out = texture_id; +} + +static void +get_gl_scaling_filters (GskRenderer *renderer, + int *min_filter_r, + int *mag_filter_r) +{ + GskScalingFilter min_filter, mag_filter; + + gsk_renderer_get_scaling_filters (renderer, &min_filter, &mag_filter); + + switch (min_filter) + { + case GSK_SCALING_FILTER_NEAREST: + *min_filter_r = GL_NEAREST; + break; + + case GSK_SCALING_FILTER_LINEAR: + *min_filter_r = GL_LINEAR; + break; + + case GSK_SCALING_FILTER_TRILINEAR: + *min_filter_r = GL_LINEAR_MIPMAP_LINEAR; + break; + } + + switch (mag_filter) + { + case GSK_SCALING_FILTER_NEAREST: + *mag_filter_r = GL_NEAREST; + break; + + /* There's no point in using anything above GL_LINEAR for + * magnification filters + */ + case GSK_SCALING_FILTER_LINEAR: + case GSK_SCALING_FILTER_TRILINEAR: + *mag_filter_r = GL_LINEAR; + break; + } +} + +static gboolean +check_in_frustum (const graphene_frustum_t *frustum, + RenderItem *item) +{ + graphene_box_t aabb; + + graphene_box_init (&aabb, &item->min, &item->max); + graphene_matrix_transform_box (&item->mvp, &aabb, &aabb); + + return graphene_frustum_intersects_box (frustum, &aabb); +} + +static float +project_item (const graphene_matrix_t *projection, + const graphene_matrix_t *modelview) +{ + graphene_vec4_t vec; + + graphene_matrix_get_row (modelview, 3, &vec); + graphene_matrix_transform_vec4 (projection, &vec, &vec); + + return graphene_vec4_get_z (&vec) / graphene_vec4_get_w (&vec); +} + +static void +gsk_gl_renderer_add_render_item (GskGLRenderer *self, + GskRenderNode *node) +{ + graphene_rect_t viewport; + int gl_min_filter, gl_mag_filter; + cairo_surface_t *surface; + GskRenderNodeIter iter; + graphene_matrix_t mv, projection; + graphene_rect_t bounds; + GskRenderNode *child; + RenderItem item; + + if (gsk_render_node_is_hidden (node)) + { + GSK_NOTE (OPENGL, g_print ("Skipping hidden node <%s>[%p]\n", + node->name != NULL ? node->name : "unnamed", + node)); + return; + } + + gsk_renderer_get_viewport (GSK_RENDERER (self), &viewport); + + gsk_render_node_get_bounds (node, &bounds); + + item.node = node; + item.name = node->name != NULL ? node->name : "unnamed"; + + /* The texture size */ + item.size = bounds.size; + + /* Each render item is an axis-aligned bounding box that we + * transform using the given transformation matrix + */ + item.min.x = (bounds.origin.x * 2) / bounds.size.width - 1; + item.min.y = (bounds.origin.y * 2) / bounds.size.height - 1; + item.min.z = 0.f; + + item.max.x = (bounds.origin.x + bounds.size.width) * 2 / bounds.size.width - 1; + item.max.y = (bounds.origin.y + bounds.size.height) * 2 / bounds.size.height - 1; + item.max.z = 0.f; + + /* The location of the item, in normalized world coordinates */ + gsk_render_node_get_world_matrix (node, &mv); + item.mvp = mv; + + item.opaque = gsk_render_node_is_opaque (node); + item.opacity = gsk_render_node_get_opacity (node); + + /* GL objects */ + item.vao_id = self->vao_id; + item.buffer_id = 0; + item.program_id = self->program_id; + item.map_location = self->map_location; + item.mvp_location = self->mvp_location; + item.uv_location = self->uv_location; + item.position_location = self->position_location; + item.alpha_location = self->alpha_location; + + gsk_renderer_get_projection (GSK_RENDERER (self), &projection); + item.z = project_item (&projection, &mv); + + /* Discard the item if it's outside of the frustum as determined by the + * viewport and the projection matrix + */ +#if 0 + if (!check_in_frustum (&self->frustum, &item)) + { + GSK_NOTE (OPENGL, g_print ("Node <%s>[%p] culled by frustum\n", + node->name != NULL ? node->name : "unnamed", + node)); + return; + } +#endif + + /* TODO: This should really be an asset atlas, to avoid uploading a ton + * of textures. Ideally we could use a single Cairo surface to get around + * the GL texture limits and reorder the texture data on the CPU side and + * do a single upload; alternatively, we could use a separate FBO and + * render each texture into it + */ + get_gl_scaling_filters (GSK_RENDERER (self), &gl_min_filter, &gl_mag_filter); + surface = gsk_render_node_get_surface (node); + + /* If the node does not have any surface we skip drawing it, but we still + * recurse. + * + * XXX: This needs to be re-done if the opacity is != 0, in which case we + * need to composite the opacity level of the children + */ + if (surface == NULL) + goto recurse_children; + + surface_to_texture (surface, &bounds, gl_min_filter, gl_mag_filter, &item.texture_id); + + GSK_NOTE (OPENGL, g_print ("Adding node <%s>[%p] to render items\n", + node->name != NULL ? node->name : "unnamed", + node)); + if (gsk_render_node_is_opaque (node) && gsk_render_node_get_opacity (node) == 1.f) + g_array_append_val (self->opaque_render_items, item); + else + g_array_append_val (self->transparent_render_items, item); + +recurse_children: + gsk_render_node_iter_init (&iter, node); + while (gsk_render_node_iter_next (&iter, &child)) + gsk_gl_renderer_add_render_item (self, child); +} + +static int +opaque_item_cmp (gconstpointer _a, + gconstpointer _b) +{ + const RenderItem *a = _a; + const RenderItem *b = _b; + + if (a->z != b->z) + { + if (a->z > b->z) + return 1; + + return -1; + } + + if (a != b) + { + if ((gsize) a > (gsize) b) + return 1; + + return -1; + } + + return 0; +} + +static int +transparent_item_cmp (gconstpointer _a, + gconstpointer _b) +{ + const RenderItem *a = _a; + const RenderItem *b = _b; + + if (a->z != b->z) + { + if (a->z < b->z) + return 1; + + return -1; + } + + if (a != b) + { + if ((gsize) a < (gsize) b) + return 1; + + return -1; + } + + return 0; +} + +static void +gsk_gl_renderer_validate_tree (GskRenderer *renderer, + GskRenderNode *root) +{ + GskGLRenderer *self = GSK_GL_RENDERER (renderer); + gboolean clear_items = FALSE; + int i; + + if (self->context == NULL) + return; + + gdk_gl_context_make_current (self->context); + + if (self->opaque_render_items->len > 0 || self->transparent_render_items->len > 0) + { + /* If we only changed the opacity and transformations then there is no + * reason to clear the render items + */ + for (i = 0; i < self->opaque_render_items->len; i++) + { + RenderItem *item = &g_array_index (self->opaque_render_items, RenderItem, i); + GskRenderNodeChanges changes = gsk_render_node_get_last_state (item->node); + + if (changes == 0) + continue; + + if ((changes & GSK_RENDER_NODE_CHANGES_UPDATE_OPACITY) != 0) + { + item->opaque = gsk_render_node_is_opaque (item->node); + item->opacity = gsk_render_node_get_opacity (item->node); + changes &= ~GSK_RENDER_NODE_CHANGES_UPDATE_OPACITY; + } + + if (changes & GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM) + { + gsk_render_node_get_world_matrix (item->node, &item->mvp); + changes &= ~ GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM; + } + + if (changes != 0) + { + clear_items = TRUE; + break; + } + } + + for (i = 0; i < self->transparent_render_items->len; i++) + { + RenderItem *item = &g_array_index (self->transparent_render_items, RenderItem, i); + GskRenderNodeChanges changes = gsk_render_node_get_last_state (item->node); + + if (changes == 0) + continue; + + if ((changes & GSK_RENDER_NODE_CHANGES_UPDATE_OPACITY) != 0) + { + item->opaque = gsk_render_node_is_opaque (item->node); + item->opacity = gsk_render_node_get_opacity (item->node); + changes &= ~GSK_RENDER_NODE_CHANGES_UPDATE_OPACITY; + } + + if (changes & GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM) + { + gsk_render_node_get_world_matrix (item->node, &item->mvp); + changes &= ~ GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM; + } + + if (changes != 0) + { + clear_items = TRUE; + break; + } + } + } + else + clear_items = TRUE; + + if (!clear_items) + { + GSK_NOTE (OPENGL, g_print ("Tree is still valid\n")); + goto out; + } + + for (i = 0; i < self->opaque_render_items->len; i++) + render_item_clear (&g_array_index (self->opaque_render_items, RenderItem, i)); + for (i = 0; i < self->transparent_render_items->len; i++) + render_item_clear (&g_array_index (self->transparent_render_items, RenderItem, i)); + + g_array_set_size (self->opaque_render_items, 0); + g_array_set_size (self->transparent_render_items, 0); + + GSK_NOTE (OPENGL, g_print ("RenderNode -> RenderItem\n")); + gsk_gl_renderer_add_render_item (self, gsk_renderer_get_root_node (renderer)); + + GSK_NOTE (OPENGL, g_print ("Sorting render nodes\n")); + g_array_sort (self->opaque_render_items, opaque_item_cmp); + g_array_sort (self->transparent_render_items, transparent_item_cmp); + +out: + GSK_NOTE (OPENGL, g_print ("Total render items: %d (opaque:%d, transparent:%d)\n", + self->opaque_render_items->len + self->transparent_render_items->len, + self->opaque_render_items->len, + self->transparent_render_items->len)); +} + +static void +gsk_gl_renderer_clear (GskRenderer *renderer) +{ +} + +static void +gsk_gl_renderer_render (GskRenderer *renderer) +{ + GskGLRenderer *self = GSK_GL_RENDERER (renderer); + graphene_rect_t viewport; + int scale, status, clear_bits; + guint i; + + if (self->context == NULL) + return; + + gdk_gl_context_make_current (self->context); + + gsk_renderer_get_viewport (renderer, &viewport); + + gsk_gl_renderer_create_buffers (self); + gsk_gl_renderer_allocate_buffers (self, viewport.size.width, viewport.size.height); + gsk_gl_renderer_attach_buffers (self); + + if (self->has_depth_buffer) + glEnable (GL_DEPTH_TEST); + else + glDisable (GL_DEPTH_TEST); + + /* Ensure that the viewport is up to date */ + status = glCheckFramebufferStatusEXT (GL_FRAMEBUFFER_EXT); + if (status == GL_FRAMEBUFFER_COMPLETE_EXT) + { + GSK_NOTE (OPENGL, g_print ("glViewport(0, 0, %g, %g)\n", + viewport.size.width, + viewport.size.height)); + glViewport (0, 0, viewport.size.width, viewport.size.height); + } + + clear_bits = GL_COLOR_BUFFER_BIT; + if (self->has_depth_buffer) + clear_bits |= GL_DEPTH_BUFFER_BIT; + if (self->has_stencil_buffer) + clear_bits |= GL_STENCIL_BUFFER_BIT; + + GSK_NOTE (OPENGL, g_print ("Clearing viewport\n")); + glClearColor (0, 0, 0, 0); + glClear (clear_bits); + + /* Opaque pass: front-to-back */ + GSK_NOTE (OPENGL, g_print ("Rendering %u opaque items\n", self->opaque_render_items->len)); + for (i = 0; i < self->opaque_render_items->len; i++) + { + RenderItem *item = &g_array_index (self->opaque_render_items, RenderItem, i); + + render_item (item); + } + + glEnable (GL_BLEND); + + /* Transparent pass: back-to-front */ + GSK_NOTE (OPENGL, g_print ("Rendering %u transparent items\n", self->transparent_render_items->len)); + for (i = 0; i < self->transparent_render_items->len; i++) + { + RenderItem *item = &g_array_index (self->transparent_render_items, RenderItem, i); + + render_item (item); + } + + glDisable (GL_BLEND); + + /* Draw the output of the GL rendering to the window */ + GSK_NOTE (OPENGL, g_print ("Drawing GL content on Cairo surface using a %s\n", + self->texture_id != 0 ? "texture" : "renderbuffer")); + scale = 1; + gdk_cairo_draw_from_gl (gsk_renderer_get_draw_context (renderer), + gsk_renderer_get_window (renderer), + self->texture_id != 0 ? self->texture_id : self->render_buffer, + self->texture_id != 0 ? GL_TEXTURE : GL_RENDERBUFFER, + scale, + 0, 0, viewport.size.width, viewport.size.height); + + gdk_gl_context_make_current (self->context); +} + +static void +gsk_gl_renderer_class_init (GskGLRendererClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GskRendererClass *renderer_class = GSK_RENDERER_CLASS (klass); + + gobject_class->dispose = gsk_gl_renderer_dispose; + + renderer_class->realize = gsk_gl_renderer_realize; + renderer_class->unrealize = gsk_gl_renderer_unrealize; + renderer_class->resize_viewport = gsk_gl_renderer_resize_viewport; + renderer_class->update = gsk_gl_renderer_update; + renderer_class->clear = gsk_gl_renderer_clear; + renderer_class->validate_tree = gsk_gl_renderer_validate_tree; + renderer_class->render = gsk_gl_renderer_render; +} + +static void +gsk_gl_renderer_init (GskGLRenderer *self) +{ + gsk_ensure_resources (); + + graphene_matrix_init_identity (&self->mvp); + + self->has_depth_buffer = TRUE; + self->has_stencil_buffer = TRUE; +} + +void +gsk_gl_renderer_set_context (GskGLRenderer *renderer, + GdkGLContext *context) +{ + g_return_if_fail (GSK_IS_GL_RENDERER (renderer)); + g_return_if_fail (context == NULL || GDK_IS_GL_CONTEXT (context)); + + if (gsk_renderer_is_realized (GSK_RENDERER (renderer))) + return; + + if (gdk_gl_context_get_display (context) != gsk_renderer_get_display (GSK_RENDERER (renderer))) + return; + + g_set_object (&renderer->context, context); +} + +GdkGLContext * +gsk_gl_renderer_get_context (GskGLRenderer *renderer) +{ + g_return_val_if_fail (GSK_IS_GL_RENDERER (renderer), NULL); + + return renderer->context; +} diff --git a/gsk/gskglrendererprivate.h b/gsk/gskglrendererprivate.h new file mode 100644 index 0000000000..a30b20140a --- /dev/null +++ b/gsk/gskglrendererprivate.h @@ -0,0 +1,23 @@ +#ifndef __GSK_GL_RENDERER_PRIVATE_H__ +#define __GSK_GL_RENDERER_PRIVATE_H__ + +#include + +G_BEGIN_DECLS + +#define GSK_TYPE_GL_RENDERER (gsk_gl_renderer_get_type ()) + +#define GSK_GL_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSK_TYPE_GL_RENDERER, GskGLRenderer)) +#define GSK_IS_GL_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSK_TYPE_GL_RENDERER)) +#define GSK_GL_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSK_TYPE_GL_RENDERER, GskGLRendererClass)) +#define GSK_IS_GL_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSK_TYPE_GL_RENDERER)) +#define GSK_GL_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSK_TYPE_GL_RENDERER, GskGLRendererClass)) + +typedef struct _GskGLRenderer GskGLRenderer; +typedef struct _GskGLRendererClass GskGLRendererClass; + +GType gsk_gl_renderer_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* __GSK_GL_RENDERER_PRIVATE_H__ */ diff --git a/gsk/gskprivate.c b/gsk/gskprivate.c new file mode 100644 index 0000000000..9e82502da5 --- /dev/null +++ b/gsk/gskprivate.c @@ -0,0 +1,16 @@ +#include "gskresources.h" + +static gpointer +register_resources (gpointer data) +{ + _gsk_register_resource (); + return NULL; +} + +void +gsk_ensure_resources (void) +{ + static GOnce register_resources_once = G_ONCE_INIT; + + g_once (®ister_resources_once, register_resources, NULL); +} diff --git a/gsk/gskprivate.h b/gsk/gskprivate.h new file mode 100644 index 0000000000..84539c1557 --- /dev/null +++ b/gsk/gskprivate.h @@ -0,0 +1,12 @@ +#ifndef __GSK_PRIVATE_H__ +#define __GSK_PRIVATE_H__ + +#include + +G_BEGIN_DECLS + +void gsk_ensure_resources (void); + +G_END_DECLS + +#endif /* __GSK_PRIVATE_H__ */ diff --git a/gsk/gskrenderer.c b/gsk/gskrenderer.c new file mode 100644 index 0000000000..cf90e382d9 --- /dev/null +++ b/gsk/gskrenderer.c @@ -0,0 +1,1377 @@ +/* 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 . + */ + +/** + * 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 +#include +#include + +#ifdef GDK_WINDOWING_X11 +#include +#endif +#ifdef GDK_WINDOWING_WAYLAND +#include +#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: + * + * |[