diff options
Diffstat (limited to 'gsk')
-rw-r--r-- | gsk/Makefile.am | 146 | ||||
-rw-r--r-- | gsk/gsk.h | 33 | ||||
-rw-r--r-- | gsk/gskcairorenderer.c | 195 | ||||
-rw-r--r-- | gsk/gskcairorendererprivate.h | 26 | ||||
-rw-r--r-- | gsk/gskdebug.c | 58 | ||||
-rw-r--r-- | gsk/gskdebugprivate.h | 43 | ||||
-rw-r--r-- | gsk/gskenums.h | 46 | ||||
-rw-r--r-- | gsk/gskenumtypes.c.template | 38 | ||||
-rw-r--r-- | gsk/gskenumtypes.h.template | 24 | ||||
-rw-r--r-- | gsk/gskglrenderer.c | 1092 | ||||
-rw-r--r-- | gsk/gskglrendererprivate.h | 23 | ||||
-rw-r--r-- | gsk/gskprivate.c | 16 | ||||
-rw-r--r-- | gsk/gskprivate.h | 12 | ||||
-rw-r--r-- | gsk/gskrenderer.c | 1377 | ||||
-rw-r--r-- | gsk/gskrenderer.h | 113 | ||||
-rw-r--r-- | gsk/gskrendererprivate.h | 62 | ||||
-rw-r--r-- | gsk/gskrendernode.c | 1246 | ||||
-rw-r--r-- | gsk/gskrendernode.h | 117 | ||||
-rw-r--r-- | gsk/gskrendernodeiter.c | 254 | ||||
-rw-r--r-- | gsk/gskrendernodeiter.h | 45 | ||||
-rw-r--r-- | gsk/gskrendernodeprivate.h | 124 | ||||
-rw-r--r-- | gsk/gsktypes.h | 29 | ||||
-rw-r--r-- | gsk/resources/glsl/base-renderer-fragment.glsl | 13 | ||||
-rw-r--r-- | gsk/resources/glsl/base-renderer-vertex.glsl | 16 |
24 files changed, 5112 insertions, 36 deletions
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 = - -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) - -lib_LTLIBRARIES += libgsk-3.la +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 + +all_sources = \ + $(gsk_public_source_h) \ + $(gsk_private_source_h) \ + $(gsk_built_source_h) \ + $(gsk_private_source_c) \ + $(gsk_source_c) + +BUILT_SOURCES += $(gsk_built_source_h) $(gsk_built_source_c) gsk.resources.xml + +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 "<?xml version='1.0' encoding='UTF-8'?>" > $@; \ + echo "<gresources>" >> $@; \ + echo " <gresource prefix='/org/gtk/libgsk'>" >> $@; \ + for f in $(top_srcdir)/gsk/resources/glsl/*; do \ + n=`basename $$f`; \ + echo " <file alias='glsl/$$n'>resources/glsl/$$n</file>" >> $@; \ + done; \ + echo " </gresource>" >> $@; \ + echo "</gresources>" >> $@ + +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 -gskincludedir = $(includedir)/gtk-3.0/gsk -gskinclude_HEADERS = $(gsk_public_source_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 <http://www.gnu.org/licenses/>. + */ + +#ifndef __GSK_H__ +#define __GSK_H__ + +#define __GSK_H_INSIDE__ + +#include <gsk/gskenums.h> +#include <gsk/gskrenderer.h> +#include <gsk/gskrendernode.h> +#include <gsk/gskrendernodeiter.h> + +#include <gsk/gsktypes.h> +#include <gsk/gskenumtypes.h> + +#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 <cairo.h> +#include <gsk/gskrenderer.h> + +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 <glib.h> + +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 <http://www.gnu.org/licenses/>. + */ + +#ifndef __GSK_ENUMS_H__ +#define __GSK_ENUMS_H__ + +#if !defined (__GSK_H_INSIDE__) && !defined (GSK_COMPILATION) +#error "Only <gsk/gsk.h> 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 <gsk.h> + +/*** 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 <gdk/gdk.h> + +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 <epoxy/gl.h> + +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 <gsk/gskrenderer.h> + +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 <glib.h> + +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 <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); +} diff --git a/gsk/gskrenderer.h b/gsk/gskrenderer.h new file mode 100644 index 0000000000..a18ab9b19a --- /dev/null +++ b/gsk/gskrenderer.h @@ -0,0 +1,113 @@ +/* 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/>. + */ + +#ifndef __GSK_RENDERER_H__ +#define __GSK_RENDERER_H__ + +#if !defined (__GSK_H_INSIDE__) && !defined (GSK_COMPILATION) +#error "Only <gsk/gsk.h> can be included directly." +#endif + +#include <gsk/gsktypes.h> +#include <gsk/gskrendernode.h> + +G_BEGIN_DECLS + +#define GSK_TYPE_RENDERER (gsk_renderer_get_type ()) + +#define GSK_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSK_TYPE_RENDERER, GskRenderer)) +#define GSK_IS_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSK_TYPE_RENDERER)) + +typedef struct _GskRenderer GskRenderer; +typedef struct _GskRendererClass GskRendererClass; + +GDK_AVAILABLE_IN_3_22 +GType gsk_renderer_get_type (void) G_GNUC_CONST; + +GDK_AVAILABLE_IN_3_22 +GskRenderer * gsk_renderer_get_for_display (GdkDisplay *display); + +GDK_AVAILABLE_IN_3_22 +void gsk_renderer_set_viewport (GskRenderer *renderer, + const graphene_rect_t *viewport); +GDK_AVAILABLE_IN_3_22 +void gsk_renderer_get_viewport (GskRenderer *renderer, + graphene_rect_t *viewport); +GDK_AVAILABLE_IN_3_22 +void gsk_renderer_set_projection (GskRenderer *renderer, + const graphene_matrix_t *projection); +GDK_AVAILABLE_IN_3_22 +void gsk_renderer_get_projection (GskRenderer *renderer, + graphene_matrix_t *projection); +GDK_AVAILABLE_IN_3_22 +void gsk_renderer_set_modelview (GskRenderer *renderer, + const graphene_matrix_t *modelview); +GDK_AVAILABLE_IN_3_22 +void gsk_renderer_get_modelview (GskRenderer *renderer, + graphene_matrix_t *modelview); +GDK_AVAILABLE_IN_3_22 +void gsk_renderer_set_scaling_filters (GskRenderer *renderer, + GskScalingFilter min_filter, + GskScalingFilter mag_filter); +GDK_AVAILABLE_IN_3_22 +void gsk_renderer_get_scaling_filters (GskRenderer *renderer, + GskScalingFilter *min_filter, + GskScalingFilter *mag_filter); +GDK_AVAILABLE_IN_3_22 +void gsk_renderer_set_auto_clear (GskRenderer *renderer, + gboolean clear); +GDK_AVAILABLE_IN_3_22 +gboolean gsk_renderer_get_auto_clear (GskRenderer *renderer); +GDK_AVAILABLE_IN_3_22 +void gsk_renderer_set_root_node (GskRenderer *renderer, + GskRenderNode *root); +GDK_AVAILABLE_IN_3_22 +GskRenderNode * gsk_renderer_get_root_node (GskRenderer *renderer); +GDK_AVAILABLE_IN_3_22 +void gsk_renderer_set_surface (GskRenderer *renderer, + cairo_surface_t *surface); +GDK_AVAILABLE_IN_3_22 +cairo_surface_t * gsk_renderer_get_surface (GskRenderer *renderer); +GDK_AVAILABLE_IN_3_22 +void gsk_renderer_set_window (GskRenderer *renderer, + GdkWindow *window); +GDK_AVAILABLE_IN_3_22 +GdkWindow * gsk_renderer_get_window (GskRenderer *renderer); +GDK_AVAILABLE_IN_3_22 +void gsk_renderer_set_draw_context (GskRenderer *renderer, + cairo_t *cr); +GDK_AVAILABLE_IN_3_22 +cairo_t * gsk_renderer_get_draw_context (GskRenderer *renderer); +GDK_AVAILABLE_IN_3_22 +GdkDisplay * gsk_renderer_get_display (GskRenderer *renderer); +GDK_AVAILABLE_IN_3_22 +void gsk_renderer_set_use_alpha (GskRenderer *renderer, + gboolean use_alpha); +GDK_AVAILABLE_IN_3_22 +gboolean gsk_renderer_get_use_alpha (GskRenderer *renderer); + +GDK_AVAILABLE_IN_3_22 +gboolean gsk_renderer_realize (GskRenderer *renderer); +GDK_AVAILABLE_IN_3_22 +void gsk_renderer_unrealize (GskRenderer *renderer); +GDK_AVAILABLE_IN_3_22 +void gsk_renderer_render (GskRenderer *renderer); + +G_END_DECLS + +#endif /* __GSK_RENDERER_H__ */ diff --git a/gsk/gskrendererprivate.h b/gsk/gskrendererprivate.h new file mode 100644 index 0000000000..404502c5be --- /dev/null +++ b/gsk/gskrendererprivate.h @@ -0,0 +1,62 @@ +/* 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/>. + */ + +#ifndef __GSK_RENDERER_PRIVATE_H__ +#define __GSK_RENDERER_PRIVATE_H__ + +#include "gskrenderer.h" + +G_BEGIN_DECLS + +#define GSK_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSK_TYPE_RENDERER, GskRendererClass)) +#define GSK_IS_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSK_TYPE_RENDERER)) +#define GSK_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSK_TYPE_RENDERER, GskRendererClass)) + +struct _GskRenderer +{ + GObject parent_instance; +}; + +struct _GskRendererClass +{ + GObjectClass parent_class; + + gboolean (* realize) (GskRenderer *renderer); + void (* unrealize) (GskRenderer *renderer); + + void (* resize_viewport) (GskRenderer *renderer, + const graphene_rect_t *viewport); + void (* update) (GskRenderer *renderer, + const graphene_matrix_t *modelview, + const graphene_matrix_t *projection); + void (* validate_tree) (GskRenderer *renderer, + GskRenderNode *root); + void (* clear) (GskRenderer *renderer); + void (* render) (GskRenderer *renderer); +}; + +gboolean gsk_renderer_is_realized (GskRenderer *renderer); + +void gsk_renderer_maybe_resize_viewport (GskRenderer *renderer); +void gsk_renderer_maybe_update (GskRenderer *renderer); +void gsk_renderer_maybe_validate_tree (GskRenderer *renderer); +void gsk_renderer_maybe_clear (GskRenderer *renderer); + +G_END_DECLS + +#endif /* __GSK_RENDERER_PRIVATE_H__ */ diff --git a/gsk/gskrendernode.c b/gsk/gskrendernode.c new file mode 100644 index 0000000000..092b96a2e3 --- /dev/null +++ b/gsk/gskrendernode.c @@ -0,0 +1,1246 @@ +/* GSK - The GTK Scene Kit + * + * Copyright 2016 Endless + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * SECTION:GskRenderNode + * @title: GskRenderNode + * @Short_desc: Simple scene graph element + * + * TODO + */ + +#include "config.h" + +#include "gskrendernodeprivate.h" + +#include "gskdebugprivate.h" +#include "gskrendernodeiter.h" + +#include <graphene-gobject.h> + +G_DEFINE_TYPE (GskRenderNode, gsk_render_node, G_TYPE_OBJECT) + +static void +gsk_render_node_dispose (GObject *gobject) +{ + GskRenderNode *self = GSK_RENDER_NODE (gobject); + GskRenderNodeIter iter; + + gsk_render_node_set_invalidate_func (self, NULL, NULL, NULL); + + gsk_render_node_iter_init (&iter, self); + while (gsk_render_node_iter_next (&iter, NULL)) + gsk_render_node_iter_remove (&iter); + + G_OBJECT_CLASS (gsk_render_node_parent_class)->dispose (gobject); +} + +static void +gsk_render_node_real_resize (GskRenderNode *node) +{ +} + +static void +gsk_render_node_class_init (GskRenderNodeClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->dispose = gsk_render_node_dispose; + + klass->resize = gsk_render_node_real_resize; +} + +static void +gsk_render_node_init (GskRenderNode *self) +{ + graphene_rect_init_from_rect (&self->bounds, graphene_rect_zero ()); + + graphene_matrix_init_identity (&self->transform); + graphene_matrix_init_identity (&self->child_transform); + + self->opacity = 1.0; +} + +/** + * gsk_render_node_new: + * + * Creates a new #GskRenderNode, to be used with #GskRenderer. + * + * Returns: (transfer full): the newly created #GskRenderNode + * + * Since: 3.22 + */ +GskRenderNode * +gsk_render_node_new (void) +{ + return g_object_new (GSK_TYPE_RENDER_NODE, NULL); +} + +/** + * gsk_render_node_get_parent: + * @node: a #GskRenderNode + * + * Returns the parent of the @node. + * + * Returns: (transfer none): the parent of the #GskRenderNode + * + * Since: 3.22 + */ +GskRenderNode * +gsk_render_node_get_parent (GskRenderNode *node) +{ + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL); + + return node->parent; +} + +/** + * gsk_render_node_get_first_child: + * @node: a #GskRenderNode + * + * Returns the first child of @node. + * + * Returns: (transfer none): the first child of the #GskRenderNode + * + * Since: 3.22 + */ +GskRenderNode * +gsk_render_node_get_first_child (GskRenderNode *node) +{ + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL); + + return node->first_child; +} + +/** + * gsk_render_node_get_last_child: + * + * Returns the last child of @node. + * + * Returns: (transfer none): the last child of the #GskRenderNode + * + * Since: 3.22 + */ +GskRenderNode * +gsk_render_node_get_last_child (GskRenderNode *node) +{ + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL); + + return node->last_child; +} + +/** + * gsk_render_node_get_next_sibling: + * + * Returns the next sibling of @node. + * + * Returns: (transfer none): the next sibling of the #GskRenderNode + * + * Since: 3.22 + */ +GskRenderNode * +gsk_render_node_get_next_sibling (GskRenderNode *node) +{ + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL); + + return node->next_sibling; +} + +/** + * gsk_render_node_get_previous_sibling: + * + * Returns the previous sibling of @node. + * + * Returns: (transfer none): the previous sibling of the #GskRenderNode + * + * Since: 3.22 + */ +GskRenderNode * +gsk_render_node_get_previous_sibling (GskRenderNode *node) +{ + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL); + + return node->prev_sibling; +} + +typedef void (* InsertChildFunc) (GskRenderNode *node, + GskRenderNode *child, + gpointer user_data); + +static void +gsk_render_node_insert_child_internal (GskRenderNode *node, + GskRenderNode *child, + InsertChildFunc insert_func, + gpointer insert_func_data) +{ + if (node == child) + { + g_critical ("The render node of type '%s' cannot be added to itself.", + G_OBJECT_TYPE_NAME (node)); + return; + } + + if (child->parent != NULL) + { + g_critical ("The render node of type '%s' already has a parent of type '%s'; " + "render nodes cannot be added to multiple parents.", + G_OBJECT_TYPE_NAME (child), + G_OBJECT_TYPE_NAME (node)); + return; + } + + insert_func (node, child, insert_func_data); + + g_object_ref (child); + + child->parent = node; + child->age = 0; + child->needs_world_matrix_update = TRUE; + + node->n_children += 1; + node->age += 1; + node->needs_world_matrix_update = TRUE; + + /* Transfer invalidated children to the current top-level */ + if (child->invalidated_descendants != NULL) + { + if (node->parent == NULL) + node->invalidated_descendants = child->invalidated_descendants; + else + { + GskRenderNode *tmp = gsk_render_node_get_toplevel (node); + + tmp->invalidated_descendants = child->invalidated_descendants; + } + + child->invalidated_descendants = NULL; + } + + if (child->prev_sibling == NULL) + node->first_child = child; + if (child->next_sibling == NULL) + node->last_child = child; +} + +static void +insert_child_at_pos (GskRenderNode *node, + GskRenderNode *child, + gpointer user_data) +{ + int pos = GPOINTER_TO_INT (user_data); + + if (pos == 0) + { + GskRenderNode *tmp = node->first_child; + + if (tmp != NULL) + tmp->prev_sibling = child; + + child->prev_sibling = NULL; + child->next_sibling = tmp; + + return; + } + + if (pos < 0 || pos >= node->n_children) + { + GskRenderNode *tmp = node->last_child; + + if (tmp != NULL) + tmp->next_sibling = child; + + child->prev_sibling = tmp; + child->next_sibling = NULL; + + return; + } + + { + GskRenderNode *iter; + int i; + + for (iter = node->first_child, i = 0; + iter != NULL; + iter = iter->next_sibling, i++) + { + if (i == pos) + { + GskRenderNode *tmp = iter->prev_sibling; + + child->prev_sibling = tmp; + child->next_sibling = iter; + + iter->prev_sibling = child; + + if (tmp != NULL) + tmp->next_sibling = child; + + break; + } + } + } +} + +/** + * gsk_render_node_insert_child_at_pos: + * @node: a #GskRenderNode + * @child: a #GskRenderNode + * @index_: the index in the list of children where @child should be inserted at + * + * Inserts @child into the list of children of @node, using the given @index_. + * + * If @index_ is 0, the @child will be prepended to the list of children. + * + * If @index_ is less than zero, or equal to the number of children, the @child + * will be appended to the list of children. + * + * This function acquires a reference on @child. + * + * Returns: (transfer none): the #GskRenderNode + * + * Since: 3.22 + */ +GskRenderNode * +gsk_render_node_insert_child_at_pos (GskRenderNode *node, + GskRenderNode *child, + int index_) +{ + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL); + g_return_val_if_fail (GSK_IS_RENDER_NODE (child), node); + + gsk_render_node_insert_child_internal (node, child, + insert_child_at_pos, + GINT_TO_POINTER (index_)); + + return node; +} + +static void +insert_child_before (GskRenderNode *node, + GskRenderNode *child, + gpointer user_data) +{ + GskRenderNode *sibling = user_data; + + if (sibling == NULL) + sibling = node->first_child; + + child->next_sibling = sibling; + + if (sibling != NULL) + { + GskRenderNode *tmp = sibling->prev_sibling; + + child->prev_sibling = tmp; + + if (tmp != NULL) + tmp->next_sibling = child; + + sibling->prev_sibling = child; + } + else + child->prev_sibling = NULL; +} + +/** + * gsk_render_node_insert_child_before: + * @node: a #GskRenderNode + * @child: a #GskRenderNode + * @sibling: (nullable): a #GskRenderNode, or %NULL + * + * Inserts @child in the list of children of @node, before @sibling. + * + * If @sibling is %NULL, the @child will be inserted at the beginning of the + * list of children. + * + * This function acquires a reference of @child. + * + * Returns: (transfer none): the #GskRenderNode + * + * Since: 3.22 + */ +GskRenderNode * +gsk_render_node_insert_child_before (GskRenderNode *node, + GskRenderNode *child, + GskRenderNode *sibling) +{ + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL); + g_return_val_if_fail (GSK_IS_RENDER_NODE (child), node); + g_return_val_if_fail (sibling == NULL || GSK_IS_RENDER_NODE (sibling), node); + + gsk_render_node_insert_child_internal (node, child, insert_child_before, sibling); + + return node; +} + +static void +insert_child_after (GskRenderNode *node, + GskRenderNode *child, + gpointer user_data) +{ + GskRenderNode *sibling = user_data; + + if (sibling == NULL) + sibling = node->last_child; + + child->prev_sibling = sibling; + + if (sibling != NULL) + { + GskRenderNode *tmp = sibling->next_sibling; + + child->next_sibling = tmp; + + if (tmp != NULL) + tmp->prev_sibling = child; + + sibling->next_sibling = child; + } + else + child->next_sibling = NULL; +} + +/** + * gsk_render_node_insert_child_after: + * @node: a #GskRenderNode + * @child: a #GskRenderNode + * @sibling: (nullable): a #GskRenderNode, or %NULL + * + * Inserts @child in the list of children of @node, after @sibling. + * + * If @sibling is %NULL, the @child will be inserted at the end of the list + * of children. + * + * This function acquires a reference of @child. + * + * Returns: (transfer none): the #GskRenderNode + * + * Since: 3.22 + */ +GskRenderNode * +gsk_render_node_insert_child_after (GskRenderNode *node, + GskRenderNode *child, + GskRenderNode *sibling) +{ + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL); + g_return_val_if_fail (GSK_IS_RENDER_NODE (child), node); + g_return_val_if_fail (sibling == NULL || GSK_IS_RENDER_NODE (sibling), node); + + if (sibling != NULL) + g_return_val_if_fail (sibling->parent == node, node); + + gsk_render_node_insert_child_internal (node, child, insert_child_after, sibling); + + return node; +} + +typedef struct { + GskRenderNode *prev_sibling; + GskRenderNode *next_sibling; +} InsertBetween; + +static void +insert_child_between (GskRenderNode *node, + GskRenderNode *child, + gpointer data_) +{ + InsertBetween *data = data_; + + child->prev_sibling = data->prev_sibling; + child->next_sibling = data->next_sibling; + + if (data->prev_sibling != NULL) + data->prev_sibling->next_sibling = child; + + if (data->next_sibling != NULL) + data->next_sibling->prev_sibling = child; +} + +/** + * gsk_render_node_replace_child: + * @node: a #GskRenderNode + * @new_child: the #GskRenderNode to add + * @old_child: the #GskRenderNode to replace + * + * Replaces @old_child with @new_child in the list of children of @node. + * + * This function acquires a reference to @new_child, and releases a reference + * of @old_child. + * + * Returns: (transfer none): the #GskRenderNode + * + * Since: 3.22 + */ +GskRenderNode * +gsk_render_node_replace_child (GskRenderNode *node, + GskRenderNode *new_child, + GskRenderNode *old_child) +{ + InsertBetween clos; + + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL); + g_return_val_if_fail (GSK_IS_RENDER_NODE (new_child), node); + g_return_val_if_fail (GSK_IS_RENDER_NODE (old_child), node); + + g_return_val_if_fail (new_child->parent == NULL, node); + g_return_val_if_fail (old_child->parent == node, node); + + clos.prev_sibling = old_child->prev_sibling; + clos.next_sibling = old_child->next_sibling; + gsk_render_node_remove_child (node, old_child); + + gsk_render_node_insert_child_internal (node, new_child, insert_child_between, &clos); + + return node; +} + +/** + * gsk_render_node_remove_child: + * @node: a #GskRenderNode + * @child: a #GskRenderNode child of @node + * + * Removes @child from the list of children of @node. + * + * This function releases the reference acquired when adding @child to the + * list of children. + * + * Returns: (transfer none): the #GskRenderNode + */ +GskRenderNode * +gsk_render_node_remove_child (GskRenderNode *node, + GskRenderNode *child) +{ + GskRenderNode *prev_sibling, *next_sibling; + + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL); + g_return_val_if_fail (GSK_IS_RENDER_NODE (child), node); + + if (child->parent != node) + { + g_critical ("The render node of type '%s' is not a child of the render node of type '%s'", + G_OBJECT_TYPE_NAME (child), + G_OBJECT_TYPE_NAME (node)); + return node; + } + + prev_sibling = child->prev_sibling; + next_sibling = child->next_sibling; + + child->parent = NULL; + child->prev_sibling = NULL; + child->next_sibling = NULL; + child->age = 0; + + if (prev_sibling) + prev_sibling->next_sibling = next_sibling; + if (next_sibling) + next_sibling->prev_sibling = prev_sibling; + + node->age += 1; + node->n_children -= 1; + + if (node->first_child == child) + node->first_child = next_sibling; + if (node->last_child == child) + node->last_child = prev_sibling; + + g_object_unref (child); + + return node; +} + +/** + * gsk_render_node_remove_all_children: + * @node: a #GskRenderNode + * + * Removes all children of @node. + * + * See also: gsk_render_node_remove_child() + * + * Returns: (transfer none): the #GskRenderNode + * + * Since: 3.22 + */ +GskRenderNode * +gsk_render_node_remove_all_children (GskRenderNode *node) +{ + GskRenderNodeIter iter; + + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL); + + if (node->n_children == 0) + return node; + + gsk_render_node_iter_init (&iter, node); + while (gsk_render_node_iter_next (&iter, NULL)) + gsk_render_node_iter_remove (&iter); + + g_assert (node->n_children == 0); + g_assert (node->first_child == NULL); + g_assert (node->last_child == NULL); + + return node; +} + +/** + * gsk_render_node_get_n_children: + * @node: a #GskRenderNode + * + * Retrieves the number of direct children of @node. + * + * Returns: the number of children of the #GskRenderNode + * + * Since: 3.22 + */ +guint +gsk_render_node_get_n_children (GskRenderNode *node) +{ + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), 0); + + return node->n_children; +} + +/** + * gsk_render_node_set_bounds: + * @node: a #GskRenderNode + * @bounds: (nullable): the boundaries of @node + * + * Sets the boundaries of @node, which describe the geometry of the + * render node, and are used to clip the surface associated to it + * when rendering. + * + * Since: 3.22 + */ +void +gsk_render_node_set_bounds (GskRenderNode *node, + const graphene_rect_t *bounds) +{ + g_return_if_fail (GSK_IS_RENDER_NODE (node)); + + if (bounds == NULL) + graphene_rect_init_from_rect (&node->bounds, graphene_rect_zero ()); + else + graphene_rect_init_from_rect (&node->bounds, bounds); + + gsk_render_node_queue_invalidate (node, GSK_RENDER_NODE_CHANGES_UPDATE_BOUNDS); +} + +/** + * gsk_render_node_get_bounds: + * @node: a #GskRenderNode + * @bounds: (out caller-allocates): return location for the boundaries + * + * Retrieves the boundaries set using gsk_render_node_set_bounds(). + * + * Since: 3.22 + */ +void +gsk_render_node_get_bounds (GskRenderNode *node, + graphene_rect_t *bounds) +{ + g_return_if_fail (GSK_IS_RENDER_NODE (node)); + g_return_if_fail (bounds != NULL); + + *bounds = node->bounds; +} + +/** + * gsk_render_node_set_transform: + * @node: a #GskRenderNode + * @transform: (nullable): a transformation matrix + * + * Sets the transformation matrix used when rendering the @node. + * + * Since: 3.22 + */ +void +gsk_render_node_set_transform (GskRenderNode *node, + const graphene_matrix_t *transform) +{ + g_return_if_fail (GSK_IS_RENDER_NODE (node)); + + if (transform == NULL) + graphene_matrix_init_identity (&node->transform); + else + graphene_matrix_init_from_matrix (&node->transform, transform); + + node->transform_set = !graphene_matrix_is_identity (&node->transform); + gsk_render_node_queue_invalidate (node, GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM); +} + +/** + * gsk_render_node_set_child_transform: + * @node: a #GskRenderNode + * @transform: (nullable): a transformation matrix + * + * Sets the transformation matrix used when rendering the children + * of @node. + * + * Since: 3.22 + */ +void +gsk_render_node_set_child_transform (GskRenderNode *node, + const graphene_matrix_t *transform) +{ + GskRenderNodeIter iter; + GskRenderNode *child; + + g_return_if_fail (GSK_IS_RENDER_NODE (node)); + + if (transform == NULL) + graphene_matrix_init_identity (&node->child_transform); + else + graphene_matrix_init_from_matrix (&node->child_transform, transform); + + node->child_transform_set = !graphene_matrix_is_identity (&node->child_transform); + + /* We need to invalidate the world matrix for our children */ + gsk_render_node_iter_init (&iter, node); + while (gsk_render_node_iter_next (&iter, &child)) + gsk_render_node_queue_invalidate (child, GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM); +} + +/** + * gsk_render_node_set_opacity: + * @node: a #GskRenderNode + * @opacity: the opacity of the node, between 0 (fully transparent) and + * 1 (fully opaque) + * + * Sets the opacity of the @node. + * + * Since: 3.22 + */ +void +gsk_render_node_set_opacity (GskRenderNode *node, + double opacity) +{ + g_return_if_fail (GSK_IS_RENDER_NODE (node)); + + node->opacity = CLAMP (opacity, 0.0, 1.0); + + gsk_render_node_queue_invalidate (node, GSK_RENDER_NODE_CHANGES_UPDATE_OPACITY); +} + +/** + * gsk_render_node_get_opacity: + * @node: a #GskRenderNode + * + * Retrieves the opacity set using gsk_render_node_set_opacity(). + * + * Returns: the opacity of the #GskRenderNode + * + * Since: 3.22 + */ +double +gsk_render_node_get_opacity (GskRenderNode *node) +{ + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), 0.0); + + return node->opacity; +} + +/** + * gsk_render_node_set_hidden: + * @node: a #GskRenderNode + * @hidden: whether the @node should be hidden or not + * + * Sets whether the @node should be hidden. + * + * Hidden nodes, and their descendants, are not rendered. + * + * Since: 3.22 + */ +void +gsk_render_node_set_hidden (GskRenderNode *node, + gboolean hidden) +{ + g_return_if_fail (GSK_IS_RENDER_NODE (node)); + + node->hidden = !!hidden; + + gsk_render_node_queue_invalidate (node, GSK_RENDER_NODE_CHANGES_UPDATE_VISIBILITY); +} + +/** + * gsk_render_node_is_hidden: + * @node: a #GskRenderNode + * + * Checks whether a @node is hidden. + * + * Returns: %TRUE if the #GskRenderNode is hidden + * + * Since: 3.22 + */ +gboolean +gsk_render_node_is_hidden (GskRenderNode *node) +{ + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), TRUE); + + return node->hidden; +} + +/** + * gsk_render_node_set_opaque: + * @node: a #GskRenderNode + * @opaque: whether the node is fully opaque or not + * + * Sets whether the node is known to be fully opaque. + * + * Fully opaque nodes will ignore the opacity set using gsk_render_node_set_opacity(), + * but if their parent is not opaque they may still be rendered with an opacity. + * + * Renderers may use this information to optimize the rendering pipeline. + * + * Since: 3.22 + */ +void +gsk_render_node_set_opaque (GskRenderNode *node, + gboolean opaque) +{ + g_return_if_fail (GSK_IS_RENDER_NODE (node)); + + node->opaque = !!opaque; + + gsk_render_node_queue_invalidate (node, GSK_RENDER_NODE_CHANGES_UPDATE_OPACITY); +} + +/** + * gsk_render_node_is_opaque: + * @node: a #GskRenderNode + * + * Retrieves the value set using gsk_render_node_set_opaque(). + * + * Returns: %TRUE if the #GskRenderNode is fully opaque + * + * Since: 3.22 + */ +gboolean +gsk_render_node_is_opaque (GskRenderNode *node) +{ + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), TRUE); + + return node->opaque; +} + +/** + * gsk_render_node_contains: + * @node: a #GskRenderNode + * @descendant: a #GskRenderNode + * + * Checks whether @node contains @descendant. + * + * Returns: %TRUE if the #GskRenderNode contains the given + * descendant + * + * Since: 3.22 + */ +gboolean +gsk_render_node_contains (GskRenderNode *node, + GskRenderNode *descendant) +{ + GskRenderNode *tmp; + + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), FALSE); + g_return_val_if_fail (GSK_IS_RENDER_NODE (descendant), FALSE); + + for (tmp = descendant; tmp != NULL; tmp = tmp->parent) + if (tmp == node) + return TRUE; + + return FALSE; +} + +/** + * gsk_render_node_set_surface: + * @node: a #GskRenderNode + * @surface: (nullable): a Cairo surface + * + * Sets the contents of the #GskRenderNode. + * + * The @node will acquire a reference on the given @surface. + * + * Since: 3.22 + */ +void +gsk_render_node_set_surface (GskRenderNode *node, + cairo_surface_t *surface) +{ + g_return_if_fail (GSK_IS_RENDER_NODE (node)); + + g_clear_pointer (&node->surface, cairo_surface_destroy); + + if (surface != NULL) + node->surface = cairo_surface_reference (surface); + + gsk_render_node_queue_invalidate (node, GSK_RENDER_NODE_CHANGES_UPDATE_SURFACE); +} + +/*< private > + * gsk_render_node_get_toplevel: + * @node: a #GskRenderNode + * + * Retrieves the top level #GskRenderNode without a parent. + * + * Returns: (transfer none): the top level #GskRenderNode + */ +GskRenderNode * +gsk_render_node_get_toplevel (GskRenderNode *node) +{ + GskRenderNode *parent; + + parent = node->parent; + if (parent == NULL) + return node; + + while (parent != NULL) + { + if (parent->parent == NULL) + return parent; + + parent = parent->parent; + } + + return NULL; +} + +/*< private > + * gsk_render_node_update_world_matrix: + * @node: a #GskRenderNode + * @force: %TRUE if the update should be forced + * + * Updates the cached world matrix of @node and its children, if needed. + */ +void +gsk_render_node_update_world_matrix (GskRenderNode *node, + gboolean force) +{ + GskRenderNodeIter iter; + GskRenderNode *child; + + if (force || node->needs_world_matrix_update) + { + GSK_NOTE (RENDER_NODE, g_print ("Updating cached world matrix on node %p [parent=%p, t_set=%s, ct_set=%s]\n", + node, + node->parent != NULL ? node->parent : 0, + node->transform_set ? "y" : "n", + node->parent != NULL && node->parent->child_transform_set ? "y" : "n")); + + if (node->parent == NULL) + { + if (node->transform_set) + graphene_matrix_init_from_matrix (&node->world_matrix, &node->transform); + else + graphene_matrix_init_identity (&node->world_matrix); + } + else + { + GskRenderNode *parent = node->parent; + graphene_matrix_t tmp; + + if (parent->child_transform_set) + graphene_matrix_init_from_matrix (&tmp, &parent->child_transform); + else + graphene_matrix_init_identity (&tmp); + + if (node->transform_set) + graphene_matrix_multiply (&tmp, &node->transform, &tmp); + + graphene_matrix_multiply (&tmp, &parent->world_matrix, &node->world_matrix); + } + + node->needs_world_matrix_update = FALSE; + } + + gsk_render_node_iter_init (&iter, node); + while (gsk_render_node_iter_next (&iter, &child)) + gsk_render_node_update_world_matrix (child, TRUE); +} + +/*< private > + * gsk_render_node_get_surface: + * @node: a #GskRenderNode + * + * Retrieves the surface set using gsk_render_node_set_surface(). + * + * Returns: (transfer none) (nullable): a Cairo surface + */ +cairo_surface_t * +gsk_render_node_get_surface (GskRenderNode *node) +{ + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL); + + return node->surface; +} + +/*< private > + * gsk_render_node_get_world_matrix: + * @node: a #GskRenderNode + * @mv: (out caller-allocates): return location for the modelview matrix + * in world-relative coordinates + * + * Retrieves the modelview matrix in world-relative coordinates. + */ +void +gsk_render_node_get_world_matrix (GskRenderNode *node, + graphene_matrix_t *mv) +{ + g_return_if_fail (GSK_IS_RENDER_NODE (node)); + g_return_if_fail (mv != NULL); + + if (node->needs_world_matrix_update) + { + GskRenderNode *tmp = gsk_render_node_get_toplevel (node); + + gsk_render_node_update_world_matrix (tmp, TRUE); + + g_assert (!node->needs_world_matrix_update); + } + + *mv = node->world_matrix; +} + +void +gsk_render_node_set_invalidate_func (GskRenderNode *node, + GskRenderNodeInvalidateFunc invalidate_func, + gpointer func_data, + GDestroyNotify destroy_func_data) +{ + if (node->parent != NULL) + { + g_critical ("Render node of type '%s' is not a root node. Only root " + "nodes can have an invalidation function.", + G_OBJECT_TYPE_NAME (node)); + return; + } + + if (node->invalidate_func != NULL) + { + if (node->destroy_func_data != NULL) + node->destroy_func_data (node->func_data); + } + + node->invalidate_func = invalidate_func; + node->func_data = func_data; + node->destroy_func_data = destroy_func_data; +} + +GskRenderNodeChanges +gsk_render_node_get_current_state (GskRenderNode *node) +{ + GskRenderNodeChanges res = 0; + + if (node->needs_resize) + res |= GSK_RENDER_NODE_CHANGES_UPDATE_BOUNDS; + if (node->needs_world_matrix_update) + res |= GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM; + if (node->needs_content_update) + res |= GSK_RENDER_NODE_CHANGES_UPDATE_SURFACE; + if (node->needs_opacity_update) + res |= GSK_RENDER_NODE_CHANGES_UPDATE_OPACITY; + if (node->needs_visibility_update) + res |= GSK_RENDER_NODE_CHANGES_UPDATE_VISIBILITY; + + return res; +} + +GskRenderNodeChanges +gsk_render_node_get_last_state (GskRenderNode *node) +{ + return node->last_state_change; +} + +void +gsk_render_node_queue_invalidate (GskRenderNode *node, + GskRenderNodeChanges changes) +{ + GskRenderNodeChanges cur_invalidated_bits = 0; + GskRenderNode *root; + int i; + + cur_invalidated_bits = gsk_render_node_get_current_state (node); + if ((cur_invalidated_bits & changes) != 0) + return; + + node->needs_resize = (changes & GSK_RENDER_NODE_CHANGES_UPDATE_BOUNDS) != 0; + node->needs_world_matrix_update = (changes & GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM) != 0; + node->needs_content_update = (changes & GSK_RENDER_NODE_CHANGES_UPDATE_SURFACE) != 0; + node->needs_opacity_update = (changes & GSK_RENDER_NODE_CHANGES_UPDATE_OPACITY) != 0; + node->needs_visibility_update = (changes & GSK_RENDER_NODE_CHANGES_UPDATE_VISIBILITY) != 0; + + if (node->parent == NULL) + { + GSK_NOTE (RENDER_NODE, g_print ("Invalid node [%p] is top-level\n", node)); + return; + } + + root = gsk_render_node_get_toplevel (node); + + if (root->invalidated_descendants == NULL) + root->invalidated_descendants = g_ptr_array_new (); + + for (i = 0; i < root->invalidated_descendants->len; i++) + { + if (node == g_ptr_array_index (root->invalidated_descendants, i)) + { + GSK_NOTE (RENDER_NODE, g_print ("Node [%p] already invalidated; skipping...\n", node)); + return; + } + } + + GSK_NOTE (RENDER_NODE, g_print ("Adding node [%p] to list of invalid descendants of [%p]\n", node, root)); + g_ptr_array_add (root->invalidated_descendants, node); +} + +void +gsk_render_node_validate (GskRenderNode *node) +{ + GPtrArray *invalidated_descendants; + gboolean call_invalidate_func; + int i; + + node->last_state_change = gsk_render_node_get_current_state (node); + + /* We call the invalidation function if our state changed, or if + * the descendants state has changed + */ + call_invalidate_func = node->last_state_change != 0 || + node->invalidated_descendants != NULL; + + gsk_render_node_maybe_resize (node); + gsk_render_node_update_world_matrix (node, FALSE); + node->needs_content_update = FALSE; + node->needs_visibility_update = FALSE; + node->needs_opacity_update = FALSE; + + /* Steal the array of invalidated descendants, so that changes caused by + * the validation will not cause recursions + */ + invalidated_descendants = node->invalidated_descendants; + node->invalidated_descendants = NULL; + + if (invalidated_descendants != NULL) + { + for (i = 0; i < invalidated_descendants->len; i++) + { + GskRenderNode *child = g_ptr_array_index (invalidated_descendants, i); + + child->last_state_change = 0; + + GSK_NOTE (RENDER_NODE, g_print ("Validating descendant node [%p] (resize:%s, transform:%s)\n", + child, + child->needs_resize ? "yes" : "no", + child->needs_world_matrix_update ? "yes" : "no")); + + child->last_state_change = gsk_render_node_get_current_state (child); + + gsk_render_node_maybe_resize (child); + gsk_render_node_update_world_matrix (child, FALSE); + + child->needs_content_update = FALSE; + child->needs_visibility_update = FALSE; + child->needs_opacity_update = FALSE; + } + } + + g_clear_pointer (&invalidated_descendants, g_ptr_array_unref); + + if (call_invalidate_func && node->invalidate_func != NULL) + node->invalidate_func (node, node->func_data); +} + +void +gsk_render_node_maybe_resize (GskRenderNode *node) +{ + g_return_if_fail (GSK_IS_RENDER_NODE (node)); + + if (!node->needs_resize) + return; + + GSK_RENDER_NODE_GET_CLASS (node)->resize (node); + + node->needs_resize = FALSE; +} + +/** + * gsk_render_node_set_name: + * @node: a #GskRenderNode + * @name: (nullable): a name for the node + * + * Sets the name of the node. + * + * A name is generally useful for debugging purposes. + * + * Since: 3.22 + */ +void +gsk_render_node_set_name (GskRenderNode *node, + const char *name) +{ + g_return_if_fail (GSK_IS_RENDER_NODE (node)); + + g_free (node->name); + node->name = g_strdup (name); +} + +static cairo_user_data_key_t render_node_context_key; + +static void +surface_invalidate (void *data) +{ + GskRenderNode *node = data; + + gsk_render_node_queue_invalidate (node, GSK_RENDER_NODE_CHANGES_UPDATE_SURFACE); +} + +/** + * gsk_render_node_get_draw_context: + * @node: a #GskRenderNode + * + * Creates a Cairo context for drawing using the surface associated + * to the render node. If no surface has been attached to the render + * node, a new surface will be created as a side effect. + * + * Returns: (transfer full): a Cairo context used for drawing; use + * cairo_destroy() when done drawing + * + * Since: 3.22 + */ +cairo_t * +gsk_render_node_get_draw_context (GskRenderNode *node) +{ + cairo_t *res; + + g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL); + + if (node->surface == NULL) + node->surface = cairo_image_surface_create (node->opaque ? CAIRO_FORMAT_RGB24 + : CAIRO_FORMAT_ARGB32, + node->bounds.size.width, + node->bounds.size.height); + + res = cairo_create (node->surface); + + cairo_rectangle (res, + node->bounds.origin.x, node->bounds.origin.y, + node->bounds.size.width, node->bounds.size.height); + cairo_clip (res); + + cairo_set_user_data (res, &render_node_context_key, node, surface_invalidate); + + return res; +} diff --git a/gsk/gskrendernode.h b/gsk/gskrendernode.h new file mode 100644 index 0000000000..f729c274f0 --- /dev/null +++ b/gsk/gskrendernode.h @@ -0,0 +1,117 @@ +/* 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/>. + */ + +#ifndef __GSK_RENDER_NODE_H__ +#define __GSK_RENDER_NODE_H__ + +#if !defined (__GSK_H_INSIDE__) && !defined (GSK_COMPILATION) +#error "Only <gsk/gsk.h> can be included directly." +#endif + +#include <gsk/gsktypes.h> + +G_BEGIN_DECLS + +#define GSK_TYPE_RENDER_NODE (gsk_render_node_get_type ()) + +#define GSK_RENDER_NODE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSK_TYPE_RENDER_NODE, GskRenderNode)) +#define GSK_IS_RENDER_NODE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSK_TYPE_RENDER_NODE)) + +typedef struct _GskRenderNode GskRenderNode; +typedef struct _GskRenderNodeClass GskRenderNodeClass; + +GDK_AVAILABLE_IN_3_22 +GType gsk_render_node_get_type (void) G_GNUC_CONST; + +GDK_AVAILABLE_IN_3_22 +GskRenderNode * gsk_render_node_new (void); + +GDK_AVAILABLE_IN_3_22 +GskRenderNode * gsk_render_node_get_parent (GskRenderNode *node); +GDK_AVAILABLE_IN_3_22 +GskRenderNode * gsk_render_node_get_first_child (GskRenderNode *node); +GDK_AVAILABLE_IN_3_22 +GskRenderNode * gsk_render_node_get_last_child (GskRenderNode *node); +GDK_AVAILABLE_IN_3_22 +GskRenderNode * gsk_render_node_get_next_sibling (GskRenderNode *node); +GDK_AVAILABLE_IN_3_22 +GskRenderNode * gsk_render_node_get_previous_sibling (GskRenderNode *node); + +GDK_AVAILABLE_IN_3_22 +GskRenderNode * gsk_render_node_insert_child_at_pos (GskRenderNode *node, + GskRenderNode *child, + int index_); +GDK_AVAILABLE_IN_3_22 +GskRenderNode * gsk_render_node_insert_child_before (GskRenderNode *node, + GskRenderNode *child, + GskRenderNode *sibling); +GDK_AVAILABLE_IN_3_22 +GskRenderNode * gsk_render_node_insert_child_after (GskRenderNode *node, + GskRenderNode *child, + GskRenderNode *sibling); +GDK_AVAILABLE_IN_3_22 +GskRenderNode * gsk_render_node_remove_child (GskRenderNode *node, + GskRenderNode *child); +GDK_AVAILABLE_IN_3_22 +GskRenderNode * gsk_render_node_replace_child (GskRenderNode *node, + GskRenderNode *new_child, + GskRenderNode *old_child); +GDK_AVAILABLE_IN_3_22 +GskRenderNode * gsk_render_node_remove_all_children (GskRenderNode *node); +GDK_AVAILABLE_IN_3_22 +guint gsk_render_node_get_n_children (GskRenderNode *node); + +GDK_AVAILABLE_IN_3_22 +gboolean gsk_render_node_contains (GskRenderNode *node, + GskRenderNode *descendant); + +GDK_AVAILABLE_IN_3_22 +void gsk_render_node_set_bounds (GskRenderNode *node, + const graphene_rect_t *bounds); +GDK_AVAILABLE_IN_3_22 +void gsk_render_node_set_transform (GskRenderNode *node, + const graphene_matrix_t *transform); +GDK_AVAILABLE_IN_3_22 +void gsk_render_node_set_child_transform (GskRenderNode *node, + const graphene_matrix_t *transform); +GDK_AVAILABLE_IN_3_22 +void gsk_render_node_set_opacity (GskRenderNode *node, + double opacity); +GDK_AVAILABLE_IN_3_22 +void gsk_render_node_set_hidden (GskRenderNode *node, + gboolean hidden); +GDK_AVAILABLE_IN_3_22 +gboolean gsk_render_node_is_hidden (GskRenderNode *node); +GDK_AVAILABLE_IN_3_22 +void gsk_render_node_set_opaque (GskRenderNode *node, + gboolean opaque); +GDK_AVAILABLE_IN_3_22 +gboolean gsk_render_node_is_opaque (GskRenderNode *node); +GDK_AVAILABLE_IN_3_22 +void gsk_render_node_set_surface (GskRenderNode *node, + cairo_surface_t *surface); +GDK_AVAILABLE_IN_3_22 +cairo_t * gsk_render_node_get_draw_context (GskRenderNode *node); + +GDK_AVAILABLE_IN_3_22 +void gsk_render_node_set_name (GskRenderNode *node, + const char *name); + +G_END_DECLS + +#endif /* __GSK_RENDER_NODE_H__ */ diff --git a/gsk/gskrendernodeiter.c b/gsk/gskrendernodeiter.c new file mode 100644 index 0000000000..d354fb21ac --- /dev/null +++ b/gsk/gskrendernodeiter.c @@ -0,0 +1,254 @@ +/* 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:GskRenderNodeIter + * @title: GskRenderNodeIter + * @Short_desc: Iterator helper for render nodes + * + * TODO + */ + +#include "config.h" + +#include "gskrendernodeiter.h" +#include "gskrendernodeprivate.h" + +typedef struct { + GskRenderNode *root; + GskRenderNode *current; + gint64 age; + gpointer reserved1; + gpointer reserved2; +} RealIter; + +#define REAL_ITER(iter) ((RealIter *) (iter)) + +/** + * gsk_render_node_iter_new: (constructor) + * + * Allocates a new #GskRenderNodeIter. + * + * Returns: (transfer full): the newly allocated #GskRenderNodeIter + * + * Since: 3.22 + */ +GskRenderNodeIter * +gsk_render_node_iter_new (void) +{ + return g_slice_new (GskRenderNodeIter); +} + +/*< private > + * gsk_render_node_iter_copy: + * @src: a #GskRenderNodeIter + * + * Copies a #GskRenderNodeIter. + * + * Returns: (transfer full): a #GskRenderNodeIter + */ +static GskRenderNodeIter * +gsk_render_node_iter_copy (GskRenderNodeIter *src) +{ + return g_slice_dup (GskRenderNodeIter, src); +} + +/** + * gsk_render_node_iter_free: + * @iter: a #GskRenderNodeIter + * + * Frees the resources allocated by gsk_render_node_iter_new(). + * + * Since: 3.22 + */ +void +gsk_render_node_iter_free (GskRenderNodeIter *iter) +{ + g_slice_free (GskRenderNodeIter, iter); +} + +G_DEFINE_BOXED_TYPE (GskRenderNodeIter, gsk_render_node_iter, + gsk_render_node_iter_copy, + gsk_render_node_iter_free) + +/** + * gsk_render_node_iter_init: + * @iter: a #GskRenderNodeIter + * @node: a #GskRenderNode + * + * Initializes a #GskRenderNodeIter for iterating over the + * children of @node. + * + * It's safe to call this function multiple times on the same + * #GskRenderNodeIter instance. + * + * Since: 3.22 + */ +void +gsk_render_node_iter_init (GskRenderNodeIter *iter, + GskRenderNode *node) +{ + RealIter *riter = REAL_ITER (iter); + + g_return_if_fail (iter != NULL); + g_return_if_fail (GSK_IS_RENDER_NODE (node)); + + riter->root = node; + riter->age = node->age; + riter->current = NULL; +} + +/** + * gsk_render_node_iter_is_valid: + * @iter: a #GskRenderNodeIter + * + * Checks whether a #GskRenderNodeIter is associated to a #GskRenderNode, + * or whether the associated node was modified while iterating. + * + * Returns: %TRUE if the iterator is still valid. + * + * Since: 3.22 + */ +gboolean +gsk_render_node_iter_is_valid (GskRenderNodeIter *iter) +{ + RealIter *riter = REAL_ITER (iter); + + g_return_val_if_fail (iter != NULL, FALSE); + + if (riter->root == NULL) + return FALSE; + + return riter->root->age == riter->age; +} + +/** + * gsk_render_node_iter_next: + * @iter: a #GskRenderNodeIter + * @child: (out) (transfer none): return location for a #GskRenderNode + * + * Advances the @iter and retrieves the next child of the root #GskRenderNode + * used to initialize the #GskRenderNodeIter. + * + * If the iterator could advance, this function returns %TRUE and sets the + * @child argument with the child #GskRenderNode. + * + * If the iterator could not advance, this function returns %FALSE and the + * contents of the @child argument are undefined. + * + * Returns: %TRUE if the iterator could advance, and %FALSE otherwise + * + * Since: 3.22 + */ +gboolean +gsk_render_node_iter_next (GskRenderNodeIter *iter, + GskRenderNode **child) +{ + RealIter *riter = REAL_ITER (iter); + + g_return_val_if_fail (riter != NULL, FALSE); + g_return_val_if_fail (riter->root != NULL, FALSE); + g_return_val_if_fail (riter->root->age == riter->age, FALSE); + + if (riter->current == NULL) + riter->current = riter->root->first_child; + else + riter->current = riter->current->next_sibling; + + if (child != NULL) + *child = riter->current; + + return riter->current != NULL; +} + +/** + * gsk_render_node_iter_prev: + * @iter: a #GskRenderNodeIter + * @child: (out) (transfer none): return location for a #GskRenderNode + * + * Advances the @iter and retrieves the previous child of the root + * #GskRenderNode used to initialize the #GskRenderNodeIter. + * + * If the iterator could advance, this function returns %TRUE and sets the + * @child argument with the child #GskRenderNode. + * + * If the iterator could not advance, this function returns %FALSE and the + * contents of the @child argument are undefined. + * + * Returns: %TRUE if the iterator could advance, and %FALSE otherwise + * + * Since: 3.22 + */ +gboolean +gsk_render_node_iter_prev (GskRenderNodeIter *iter, + GskRenderNode **child) +{ + RealIter *riter = REAL_ITER (iter); + + g_return_val_if_fail (riter != NULL, FALSE); + g_return_val_if_fail (riter->root != NULL, FALSE); + g_return_val_if_fail (riter->root->age == riter->age, FALSE); + + if (riter->current == NULL) + riter->current = riter->root->last_child; + else + riter->current = riter->current->prev_sibling; + + if (child != NULL) + *child = riter->current; + + return riter->current != NULL; +} + +/** + * gsk_render_node_iter_remove: + * @iter: a #GskRenderNodeIter + * + * Removes the child #GskRenderNode currently being visited by + * the iterator. + * + * Calling this function on an invalid #GskRenderNodeIter results + * in undefined behavior. + * + * Since: 3.22 + */ +void +gsk_render_node_iter_remove (GskRenderNodeIter *iter) +{ + RealIter *riter = REAL_ITER (iter); + GskRenderNode *tmp; + + g_return_if_fail (riter != NULL); + g_return_if_fail (riter->root != NULL); + g_return_if_fail (riter->root->age == riter->age); + g_return_if_fail (riter->current != NULL); + + tmp = riter->current; + + if (tmp != NULL) + { + riter->current = tmp->prev_sibling; + + gsk_render_node_remove_child (riter->root, tmp); + + riter->age += 1; + + /* Safety net */ + g_assert (riter->age == riter->root->age); + } +} diff --git a/gsk/gskrendernodeiter.h b/gsk/gskrendernodeiter.h new file mode 100644 index 0000000000..4114e85256 --- /dev/null +++ b/gsk/gskrendernodeiter.h @@ -0,0 +1,45 @@ +#ifndef __GSK_RENDER_NODE_ITER_H__ +#define __GSK_RENDER_NODE_ITER_H__ + +#include <gsk/gskrendernode.h> + +G_BEGIN_DECLS + +#define GSK_TYPE_RENDER_NODE_ITER (gsk_render_node_iter_get_type()) + +typedef struct _GskRenderNodeIter GskRenderNodeIter; + +struct _GskRenderNodeIter +{ + /*< private >*/ + gpointer dummy1; + gpointer dummy2; + gint64 dummy3; + gpointer dummy4; + gpointer dummy5; +}; + +GDK_AVAILABLE_IN_3_22 +GType gsk_render_node_iter_get_type (void) G_GNUC_CONST; +GDK_AVAILABLE_IN_3_22 +GskRenderNodeIter * gsk_render_node_iter_new (void); +GDK_AVAILABLE_IN_3_22 +void gsk_render_node_iter_free (GskRenderNodeIter *iter); +GDK_AVAILABLE_IN_3_22 +void gsk_render_node_iter_init (GskRenderNodeIter *iter, + GskRenderNode *node); + +GDK_AVAILABLE_IN_3_22 +gboolean gsk_render_node_iter_is_valid (GskRenderNodeIter *iter); +GDK_AVAILABLE_IN_3_22 +gboolean gsk_render_node_iter_prev (GskRenderNodeIter *iter, + GskRenderNode **child); +GDK_AVAILABLE_IN_3_22 +gboolean gsk_render_node_iter_next (GskRenderNodeIter *iter, + GskRenderNode **child); +GDK_AVAILABLE_IN_3_22 +void gsk_render_node_iter_remove (GskRenderNodeIter *iter); + +G_END_DECLS + +#endif /* GSK_RENDER_NODE_ITER_H */ diff --git a/gsk/gskrendernodeprivate.h b/gsk/gskrendernodeprivate.h new file mode 100644 index 0000000000..72373d8e68 --- /dev/null +++ b/gsk/gskrendernodeprivate.h @@ -0,0 +1,124 @@ +#ifndef __GSK_RENDER_NODE_PRIVATE_H__ +#define __GSK_RENDER_NODE_PRIVATE_H__ + +#include "gskrendernode.h" +#include <cairo.h> + +G_BEGIN_DECLS + +#define GSK_RENDER_NODE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSK_TYPE_RENDER_NODE, GskRenderNodeClass)) +#define GSK_IS_RENDER_NODE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSK_TYPE_RENDER_NODE)) +#define GSK_RENDER_NODE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSK_TYPE_RENDER_NODE, GskRenderNodeClass)) + +typedef enum { + GSK_RENDER_NODE_CHANGES_UPDATE_BOUNDS = 1 << 0, + GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM = 1 << 1, + GSK_RENDER_NODE_CHANGES_UPDATE_SURFACE = 1 << 2, + GSK_RENDER_NODE_CHANGES_UPDATE_OPACITY = 1 << 3, + GSK_RENDER_NODE_CHANGES_UPDATE_VISIBILITY = 1 << 4, + GSK_RENDER_NODE_CHANEGS_UPDATE_HIERARCHY = 1 << 5 +} GskRenderNodeChanges; + +typedef void (* GskRenderNodeInvalidateFunc) (GskRenderNode *node, + gpointer data); + +struct _GskRenderNode +{ + GObject parent_instance; + + /* The graph */ + GskRenderNode *parent; + GskRenderNode *first_child; + GskRenderNode *last_child; + GskRenderNode *prev_sibling; + GskRenderNode *next_sibling; + + int n_children; + + /* Use for debugging */ + char *name; + + /* Tag updated when adding/removing children */ + gint64 age; + + /* The contents of the node */ + cairo_surface_t *surface; + + /* Paint opacity */ + double opacity; + + /* Clip rectangle */ + graphene_rect_t bounds; + + /* Transformations relative to the root of the scene */ + graphene_matrix_t world_matrix; + + /* Transformations applied to the node */ + graphene_matrix_t transform; + + /* Transformations applied to the children of the node */ + graphene_matrix_t child_transform; + + /* Invalidation function for root node */ + GskRenderNodeInvalidateFunc invalidate_func; + gpointer func_data; + GDestroyNotify destroy_func_data; + + /* Descendants that need to be validated; only for root node */ + GPtrArray *invalidated_descendants; + + GskRenderNodeChanges last_state_change; + + /* Bit fields; leave at the end */ + gboolean hidden : 1; + gboolean opaque : 1; + gboolean transform_set : 1; + gboolean child_transform_set : 1; + gboolean needs_resize : 1; + gboolean needs_world_matrix_update : 1; + gboolean needs_content_update : 1; + gboolean needs_opacity_update : 1; + gboolean needs_visibility_update : 1; +}; + +struct _GskRenderNodeClass +{ + GObjectClass parent_class; + + void (* resize) (GskRenderNode *node); +}; + +void gsk_render_node_get_bounds (GskRenderNode *node, + graphene_rect_t *frame); +void gsk_render_node_get_transform (GskRenderNode *node, + graphene_matrix_t *mv); +double gsk_render_node_get_opacity (GskRenderNode *node); + +cairo_surface_t *gsk_render_node_get_surface (GskRenderNode *node); + +GskRenderNode *gsk_render_node_get_toplevel (GskRenderNode *node); + +void gsk_render_node_update_world_matrix (GskRenderNode *node, + gboolean force); + +void gsk_render_node_get_world_matrix (GskRenderNode *node, + graphene_matrix_t *mv); + +void gsk_render_node_queue_invalidate (GskRenderNode *node, + GskRenderNodeChanges changes); + +void gsk_render_node_set_invalidate_func (GskRenderNode *root, + GskRenderNodeInvalidateFunc validate_func, + gpointer data, + GDestroyNotify notify); + +void gsk_render_node_validate (GskRenderNode *node); + +void gsk_render_node_maybe_resize (GskRenderNode *node); + +GskRenderNodeChanges gsk_render_node_get_current_state (GskRenderNode *node); +GskRenderNodeChanges gsk_render_node_get_last_state (GskRenderNode *node); + +G_END_DECLS + +#endif /* __GSK_RENDER_NODE_PRIVATE_H__ */ diff --git a/gsk/gsktypes.h b/gsk/gsktypes.h new file mode 100644 index 0000000000..8513328ba5 --- /dev/null +++ b/gsk/gsktypes.h @@ -0,0 +1,29 @@ +/* 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/>. + */ + +#ifndef __GSK_TYPES_H__ +#define __GSK_TYPES_H__ + +#if !defined (__GSK_H_INSIDE__) && !defined (GSK_COMPILATION) +#error "Only <gsk/gsk.h> can be included directly." +#endif + +#include <graphene.h> +#include <gdk/gdk.h> +#include <gsk/gskenums.h> + +#endif /* __GSK_TYPES_H__ */ diff --git a/gsk/resources/glsl/base-renderer-fragment.glsl b/gsk/resources/glsl/base-renderer-fragment.glsl new file mode 100644 index 0000000000..07458db527 --- /dev/null +++ b/gsk/resources/glsl/base-renderer-fragment.glsl @@ -0,0 +1,13 @@ +#version 150 + +smooth in vec2 vUv; + +out vec4 outputColor; + +uniform mat4 mvp; +uniform sampler2D map; +uniform float alpha; + +void main() { + outputColor = texture2D(map, vUv) * vec4(alpha); +} diff --git a/gsk/resources/glsl/base-renderer-vertex.glsl b/gsk/resources/glsl/base-renderer-vertex.glsl new file mode 100644 index 0000000000..534f201bfa --- /dev/null +++ b/gsk/resources/glsl/base-renderer-vertex.glsl @@ -0,0 +1,16 @@ +#version 150 + +uniform mat4 mvp; +uniform sampler2D map; +uniform float alpha; + +in vec2 position; +in vec2 uv; + +smooth out vec2 vUv; + +void main() { + gl_Position = mvp * vec4(position, 0.0, 1.0); + + vUv = vec2(uv.x, 1 - uv.y); +} |