[gtk+/wip/ebassi/gsk-1] Snapshot/WIP
- From: Emmanuele Bassi <ebassi src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk+/wip/ebassi/gsk-1] Snapshot/WIP
- Date: Wed, 13 Apr 2016 15:02:23 +0000 (UTC)
commit 47f515d3fce85dc45dfcb6767a07baca65e6ba80
Author: Emmanuele Bassi <ebassi gnome org>
Date: Tue Apr 12 09:00:09 2016 +0100
Snapshot/WIP
gsk/Makefile.am | 39 ++-
gsk/gskglrenderer.c | 735 ++++++++++++++++++++++--
gsk/gskprivate.c | 16 +
gsk/gskprivate.h | 12 +
gsk/gskrenderer.c | 106 +++-
gsk/gskrenderer.h | 5 +
gsk/gskrendernode.c | 86 +++-
gsk/gskrendernode.h | 2 +
gsk/gskrendernodeprivate.h | 4 +-
gsk/resources/glsl/base-renderer-fragment.glsl | 13 +
gsk/resources/glsl/base-renderer-vertex.glsl | 16 +
tests/testgskrenderer.c | 108 +++-
12 files changed, 1048 insertions(+), 94 deletions(-)
---
diff --git a/gsk/Makefile.am b/gsk/Makefile.am
index f89155d..4a8ec18 100644
--- a/gsk/Makefile.am
+++ b/gsk/Makefile.am
@@ -38,12 +38,16 @@ gsk_private_source_h = \
gskdebugprivate.h \
gskglrendererprivate.h \
gskrendererprivate.h \
- gskrendernodeprivate.h
-gsk_private_source_c =
+ gskrendernodeprivate.h \
+ gskprivate.h
+gsk_private_source_c = \
+ gskprivate.c
gsk_built_source_h = \
- gskenumtypes.h
+ gskenumtypes.h \
+ gskresources.h
gsk_built_source_c = \
- gskenumtypes.c
+ gskenumtypes.c \
+ gskresources.c
gsk_source_c = \
gskcairorenderer.c \
gskdebug.c \
@@ -59,7 +63,7 @@ all_sources = \
$(gsk_private_source_c) \
$(gsk_source_c)
-BUILT_SOURCES += $(gsk_built_source_h) $(gsk_built_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,$^) > \
@@ -74,6 +78,31 @@ gskenumtypes.c: $(gsk_public_source_h) gskenumtypes.c.template
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_3_la_SOURCES = $(all_sources)
nodist_libgsk_3_la_SOURCES = $(gsk_built_source_h) $(gsk_built_source_c)
libgsk_3_la_CFLAGS = $(AM_CFLAGS) $(GDK_HIDDEN_VISIBILITY_CFLAGS)
diff --git a/gsk/gskglrenderer.c b/gsk/gskglrenderer.c
index 0870a22..b65e9eb 100644
--- a/gsk/gskglrenderer.c
+++ b/gsk/gskglrenderer.c
@@ -2,18 +2,37 @@
#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 {
- GskRenderNode *node;
- guint texture_id;
- graphene_rect_t frame;
- graphene_matrix_t modelview;
+ graphene_point3d_t min;
+ graphene_point3d_t max;
+
+ graphene_size_t size;
+
+ graphene_matrix_t mvp;
+
+ gboolean opaque : 1;
float opacity;
+ float z;
+
+ 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
@@ -30,6 +49,13 @@ struct _GskGLRenderer
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;
@@ -46,6 +72,8 @@ struct _GskGLRendererClass
GskRendererClass parent_class;
};
+static void render_item_clear (gpointer data_);
+
G_DEFINE_TYPE (GskGLRenderer, gsk_gl_renderer, GSK_TYPE_RENDERER)
static void
@@ -54,8 +82,6 @@ gsk_gl_renderer_dispose (GObject *gobject)
GskGLRenderer *self = GSK_GL_RENDERER (gobject);
g_clear_object (&self->context);
- g_clear_pointer (&self->opaque_render_items, g_array_unref);
- g_clear_pointer (&self->transparent_render_items, g_array_unref);
G_OBJECT_CLASS (gsk_gl_renderer_parent_class)->dispose (gobject);
}
@@ -63,9 +89,14 @@ gsk_gl_renderer_dispose (GObject *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 (self->has_alpha)
+ if (gsk_renderer_get_use_alpha (GSK_RENDERER (self)))
{
if (self->texture_id == 0)
glGenTextures (1, &self->texture_id);
@@ -102,15 +133,99 @@ gsk_gl_renderer_create_buffers (GskGLRenderer *self)
}
}
+ /* 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);
@@ -139,14 +254,151 @@ gsk_gl_renderer_destroy_buffers (GskGLRenderer *self)
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)
- return FALSE;
+ {
+ 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)
@@ -156,8 +408,18 @@ gsk_gl_renderer_realize (GskRenderer *renderer)
return FALSE;
}
- gdk_gl_context_make_current (self->context);
+ 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;
}
@@ -171,7 +433,12 @@ gsk_gl_renderer_unrealize (GskRenderer *renderer)
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 ();
@@ -181,14 +448,6 @@ static void
gsk_gl_renderer_resize_viewport (GskRenderer *renderer,
const graphene_rect_t *viewport)
{
- GskGLRenderer *self = GSK_GL_RENDERER (renderer);
-
- if (self->context == NULL)
- return;
-
- gdk_gl_context_make_current (self->context);
-
- glViewport (0, 0, viewport->size.width, viewport->size.height);
}
static void
@@ -198,91 +457,492 @@ gsk_gl_renderer_update (GskRenderer *renderer,
{
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_;
+
+ glDeleteBuffers (1, &item->buffer_id);
+ item->buffer_id = 0;
+
+ 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 */
+ GSK_NOTE (OPENGL, g_print ("Drawing item with opacity: %g\n",
+ item->opaque ? 1 : item->opacity));
+ 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 */
+ 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))
- return;
+ {
+ GSK_NOTE (OPENGL, g_print ("Skipping hidden node <%s>[%p]\n",
+ node->name != NULL ? node->name : "unnamed",
+ node));
+ return;
+ }
- item.node = g_object_ref (node);
- gsk_render_node_get_bounds (node, &item.frame);
- gsk_render_node_get_world_matrix (node, &item.modelview);
+ gsk_renderer_get_viewport (GSK_RENDERER (self), &viewport);
+
+ gsk_render_node_get_bounds (node, &bounds);
+
+ /* 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_get_opacity (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
- /* TODO: This should really be an asset atlas */
- surface = gsk_render_node_get_surface (node);
- if (surface != NULL)
- gdk_cairo_surface_to_texture (surface, &item.texture_id);
+ 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
- if (gsk_render_node_is_opaque (node))
+ /* 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_prepend_val (self->transparent_render_items, item);
+ 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);
+ if (self->context == NULL)
+ return;
+
+ gdk_gl_context_make_current (self->context);
+
+ GSK_NOTE (OPENGL, g_print ("Resetting the render items\n"));
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);
+
+ 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)
{
- GskGLRenderer *self = GSK_GL_RENDERER (renderer);
-
- gdk_gl_context_make_current (self->context);
-
- glClearColor (0, 0, 0, 0);
- glClear (GL_COLOR_BUFFER_BIT);
}
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
@@ -305,11 +965,12 @@ gsk_gl_renderer_class_init (GskGLRendererClass *klass)
static void
gsk_gl_renderer_init (GskGLRenderer *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);
+ gsk_ensure_resources ();
- 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);
+ graphene_matrix_init_identity (&self->mvp);
+
+ self->has_depth_buffer = TRUE;
+ self->has_stencil_buffer = TRUE;
}
void
diff --git a/gsk/gskprivate.c b/gsk/gskprivate.c
new file mode 100644
index 0000000..9e82502
--- /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 0000000..84539c1
--- /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
index 8307707..cf90e38 100644
--- a/gsk/gskrenderer.c
+++ b/gsk/gskrenderer.c
@@ -39,6 +39,13 @@
#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;
@@ -56,6 +63,7 @@ typedef struct
GskRenderNode *root_node;
cairo_surface_t *surface;
+ cairo_t *draw_context;
gboolean is_realized : 1;
gboolean needs_viewport_resize : 1;
@@ -135,6 +143,10 @@ gsk_renderer_dispose (GObject *gobject)
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);
@@ -547,6 +559,8 @@ gsk_renderer_set_viewport (GskRenderer *renderer,
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]);
}
@@ -817,7 +831,6 @@ gsk_renderer_set_surface (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);
if (priv->surface == surface)
return;
@@ -858,27 +871,54 @@ gsk_renderer_get_surface (GskRenderer *renderer)
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_format_t format;
+ cairo_content_t content;
if (priv->use_alpha)
- format = CAIRO_FORMAT_ARGB32;
+ content = CAIRO_CONTENT_COLOR_ALPHA;
else
- format = CAIRO_FORMAT_RGB24;
+ 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_image_surface (priv->window,
- format,
- width, height,
- scale);
+ 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
@@ -1116,13 +1156,14 @@ gsk_renderer_maybe_validate_tree (GskRenderer *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 scene graph is up to date */
- gsk_render_node_validate (priv->root_node);
-
+ /* Ensure that the Renderer can update itself */
renderer_class->validate_tree (renderer, priv->root_node);
priv->needs_tree_validation = FALSE;
}
@@ -1165,12 +1206,17 @@ gsk_renderer_render (GskRenderer *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);
@@ -1291,5 +1337,41 @@ gsk_renderer_get_use_alpha (GskRenderer *renderer)
GskRenderer *
gsk_renderer_get_for_display (GdkDisplay *display)
{
- return g_object_new (GSK_TYPE_CAIRO_RENDERER, "display", display, NULL);
+ 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
index be8ae5b..a18ab9b 100644
--- a/gsk/gskrenderer.h
+++ b/gsk/gskrenderer.h
@@ -89,6 +89,11 @@ void gsk_renderer_set_window (GskRenderer
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,
diff --git a/gsk/gskrendernode.c b/gsk/gskrendernode.c
index 34cf61c..9767571 100644
--- a/gsk/gskrendernode.c
+++ b/gsk/gskrendernode.c
@@ -732,6 +732,8 @@ gsk_render_node_set_opacity (GskRenderNode *node,
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_SURFACE);
}
/**
@@ -880,6 +882,8 @@ gsk_render_node_set_surface (GskRenderNode *node,
if (surface != NULL)
node->surface = cairo_surface_reference (surface);
+
+ gsk_render_node_queue_invalidate (node, GSK_RENDER_NODE_CHANGES_UPDATE_SURFACE);
}
/*< private >
@@ -1040,11 +1044,15 @@ gsk_render_node_queue_invalidate (GskRenderNode *node,
if (node->needs_world_matrix_update)
cur_invalidated_bits |= GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM;
+ if (node->needs_content_update)
+ cur_invalidated_bits |= GSK_RENDER_NODE_CHANGES_UPDATE_SURFACE;
+
if (cur_invalidated_bits == changes)
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;
if (node->parent == NULL)
{
@@ -1074,22 +1082,32 @@ void
gsk_render_node_validate (GskRenderNode *node)
{
GPtrArray *invalidated_descendants;
+ gboolean call_invalidate_func = FALSE;
int i;
- if (node->invalidated_descendants == NULL &&
- !node->needs_resize &&
- !node->needs_world_matrix_update)
+ if (!node->needs_resize &&
+ !node->needs_world_matrix_update &&
+ !node->needs_content_update &&
+ node->invalidated_descendants == NULL)
return;
if (node->needs_resize)
- gsk_render_node_resize (node);
+ {
+ gsk_render_node_resize (node);
+ call_invalidate_func = TRUE;
+ }
if (node->needs_world_matrix_update)
- gsk_render_node_update_world_matrix (node, FALSE);
+ {
+ gsk_render_node_update_world_matrix (node, FALSE);
+ call_invalidate_func = TRUE;
+ }
+ if (node->needs_content_update)
+ call_invalidate_func = TRUE;
if (node->invalidated_descendants == NULL)
- return;
+ goto out;
/* Steal the array of invalidated descendants, so that changes caused by
* the validation will not cause recursions
@@ -1112,7 +1130,8 @@ gsk_render_node_validate (GskRenderNode *node)
g_clear_pointer (&invalidated_descendants, g_ptr_array_unref);
- if (node->invalidate_func != NULL)
+out:
+ if (call_invalidate_func && node->invalidate_func != NULL)
node->invalidate_func (node, node->func_data);
}
@@ -1129,6 +1148,17 @@ gsk_render_node_resize (GskRenderNode *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)
@@ -1138,3 +1168,45 @@ gsk_render_node_set_name (GskRenderNode *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_set_user_data (res, &render_node_context_key, node, surface_invalidate);
+
+ return res;
+}
diff --git a/gsk/gskrendernode.h b/gsk/gskrendernode.h
index ffa1236..f729c27 100644
--- a/gsk/gskrendernode.h
+++ b/gsk/gskrendernode.h
@@ -105,6 +105,8 @@ gboolean gsk_render_node_is_opaque (GskRenderNode *
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,
diff --git a/gsk/gskrendernodeprivate.h b/gsk/gskrendernodeprivate.h
index 64ded07..9a85768 100644
--- a/gsk/gskrendernodeprivate.h
+++ b/gsk/gskrendernodeprivate.h
@@ -12,7 +12,8 @@ G_BEGIN_DECLS
typedef enum {
GSK_RENDER_NODE_CHANGES_UPDATE_BOUNDS = 1 << 0,
- GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM = 1 << 1
+ GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM = 1 << 1,
+ GSK_RENDER_NODE_CHANGES_UPDATE_SURFACE = 1 << 2
} GskRenderNodeChanges;
typedef void (* GskRenderNodeInvalidateFunc) (GskRenderNode *node,
@@ -70,6 +71,7 @@ struct _GskRenderNode
gboolean child_transform_set : 1;
gboolean needs_resize : 1;
gboolean needs_world_matrix_update : 1;
+ gboolean needs_content_update : 1;
};
struct _GskRenderNodeClass
diff --git a/gsk/resources/glsl/base-renderer-fragment.glsl b/gsk/resources/glsl/base-renderer-fragment.glsl
new file mode 100644
index 0000000..37d59c1
--- /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 = vec4(texture2D(map, vUv).xyz, alpha);
+}
diff --git a/gsk/resources/glsl/base-renderer-vertex.glsl b/gsk/resources/glsl/base-renderer-vertex.glsl
new file mode 100644
index 0000000..6c6bbe0
--- /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 = uv;
+}
diff --git a/tests/testgskrenderer.c b/tests/testgskrenderer.c
index 08fabca..a7a9eeb 100644
--- a/tests/testgskrenderer.c
+++ b/tests/testgskrenderer.c
@@ -9,22 +9,12 @@
#define PADDING 10.f
#define ROOT_SIZE BOX_SIZE * 2 + PADDING * 2
-static cairo_surface_t *
-create_color_surface (GdkRGBA *color, int w, int h)
+static void
+create_color_surface (cairo_t *cr, GdkRGBA *color, int w, int h)
{
- cairo_surface_t *res;
- cairo_t *cr;
-
- res = cairo_image_surface_create (CAIRO_FORMAT_RGB24, w, h);
- cr = cairo_create (res);
-
cairo_set_source_rgba (cr, color->red, color->green, color->blue, color->alpha);
cairo_rectangle (cr, 0, 0, w, h);
cairo_fill (cr);
-
- cairo_destroy (cr);
-
- return res;
}
static GskRenderer *
@@ -36,6 +26,7 @@ get_renderer (GtkWidget *widget)
if (res == NULL)
{
res = gsk_renderer_get_for_display (gtk_widget_get_display (widget));
+
g_object_set_data_full (G_OBJECT (widget), "-gsk-renderer",
res,
(GDestroyNotify) g_object_unref);
@@ -48,50 +39,53 @@ static void
create_scene (GskRenderer *renderer)
{
GskRenderNode *root, *node;
- cairo_surface_t *surface;
graphene_matrix_t ctm;
+ cairo_t *cr;
root = gsk_render_node_new ();
+ gsk_render_node_set_name (root, "Root node");
gsk_render_node_set_bounds (root, &(graphene_rect_t) {
.origin.x = 0.f,
.origin.y = 0.f,
.size.width = ROOT_SIZE,
.size.height = ROOT_SIZE
});
- surface = create_color_surface (&(GdkRGBA) { .red = 1, .green = 0, .blue = 0, .alpha = 1 }, ROOT_SIZE,
ROOT_SIZE);
- gsk_render_node_set_surface (root, surface);
- cairo_surface_destroy (surface);
+ cr = gsk_render_node_get_draw_context (root);
+ create_color_surface (cr, &(GdkRGBA) { .red = 1, .green = 0, .blue = 0, .alpha = 1 }, ROOT_SIZE,
ROOT_SIZE);
+ cairo_destroy (cr);
gsk_renderer_set_root_node (renderer, root);
g_object_set_data (G_OBJECT (renderer), "-gsk-renderer-root-node", root);
g_object_unref (root);
node = gsk_render_node_new ();
+ gsk_render_node_set_name (node, "Green node");
gsk_render_node_set_bounds (node, &(graphene_rect_t) {
.origin.x = 0.f,
.origin.y = 0.f,
.size.width = BOX_SIZE,
.size.height = BOX_SIZE
});
- surface = create_color_surface (&(GdkRGBA) { .red = 0, .green = 1, .blue = 0, .alpha = 1 }, BOX_SIZE,
BOX_SIZE);
- gsk_render_node_set_surface (node, surface);
- cairo_surface_destroy (surface);
- graphene_matrix_init_translate (&ctm, &(graphene_point3d_t) { .x = PADDING, .y = PADDING, .z = 0.f });
+ cr = gsk_render_node_get_draw_context (node);
+ create_color_surface (cr, &(GdkRGBA) { .red = 0, .green = 1, .blue = 0, .alpha = 1 }, BOX_SIZE, BOX_SIZE);
+ cairo_destroy (cr);
+ graphene_matrix_init_translate (&ctm, &(graphene_point3d_t) { .x = -0.5, .y = -0.5, .z = 0.f });
gsk_render_node_set_transform (node, &ctm);
gsk_render_node_insert_child_at_pos (root, node, 0);
g_object_unref (node);
node = gsk_render_node_new ();
+ gsk_render_node_set_name (node, "Blue node");
gsk_render_node_set_bounds (node, &(graphene_rect_t) {
.origin.x = 0.f,
.origin.y = 0.f,
.size.width = BOX_SIZE,
.size.height = BOX_SIZE
});
- surface = create_color_surface (&(GdkRGBA) { .red = 0, .green = 0, .blue = 1, .alpha = 1 }, BOX_SIZE,
BOX_SIZE);
- gsk_render_node_set_surface (node, surface);
- cairo_surface_destroy (surface);
- graphene_matrix_init_translate (&ctm, &(graphene_point3d_t) { .x = BOX_SIZE + PADDING, .y = BOX_SIZE +
PADDING, .z = 0.f });
+ cr = gsk_render_node_get_draw_context (node);
+ create_color_surface (cr, &(GdkRGBA) { .red = 0, .green = 0, .blue = 1, .alpha = 1 }, BOX_SIZE, BOX_SIZE);
+ cairo_destroy (cr);
+ graphene_matrix_init_translate (&ctm, &(graphene_point3d_t) { .x = 0.5, .y = 0.5, .z = 0.f });
gsk_render_node_set_transform (node, &ctm);
gsk_render_node_insert_child_at_pos (root, node, 1);
g_object_unref (node);
@@ -125,10 +119,17 @@ size_allocate (GtkWidget *widget, GtkAllocation *allocation)
gsk_renderer_set_viewport (renderer, &(graphene_rect_t) {
.origin.x = 0,
.origin.y = 0,
- .size.width = allocation->width - allocation->x,
- .size.height = allocation->height - allocation->y
+ .size.width = allocation->width,
+ .size.height = allocation->height
});
+ graphene_matrix_init_translate (&ctm, &(graphene_point3d_t) {
+ allocation->x,
+ allocation->y,
+ 0.f
+ });
+ gsk_renderer_set_modelview (renderer, &ctm);
+
root = g_object_get_data (G_OBJECT (renderer), "-gsk-renderer-root-node");
if (root == NULL)
{
@@ -137,9 +138,9 @@ size_allocate (GtkWidget *widget, GtkAllocation *allocation)
}
graphene_matrix_init_translate (&ctm, &(graphene_point3d_t) {
- .x = (allocation->width - allocation->x - ROOT_SIZE) / 2.f,
- .y = (allocation->height - allocation->y - ROOT_SIZE) / 2.f,
- .z = 0.f
+ .x = 0,
+ .y = 0,
+ .z = 0
});
gsk_render_node_set_transform (root, &ctm);
}
@@ -149,14 +150,53 @@ draw (GtkWidget *widget, cairo_t *cr)
{
GskRenderer *renderer = get_renderer (widget);
+ gsk_renderer_set_draw_context (renderer, cr);
gsk_renderer_render (renderer);
- cairo_set_source_surface (cr, gsk_renderer_get_surface (renderer), 0, 0);
- cairo_paint (cr);
-
return TRUE;
}
+static gboolean
+fade_out (GtkWidget *widget,
+ GdkFrameClock *frame_clock,
+ gpointer data)
+{
+ static gint64 first_frame_time;
+ static gboolean flip = FALSE;
+ gint64 now = gdk_frame_clock_get_frame_time (frame_clock);
+
+ if (first_frame_time == 0)
+ {
+ first_frame_time = now;
+
+ return G_SOURCE_CONTINUE;
+ }
+
+ double start = first_frame_time;
+ double end = first_frame_time + (double) 1000000;
+ double progress = (now - first_frame_time) / (end - start);
+
+ if (flip)
+ progress = 1 - progress;
+
+ if (progress < 0 || progress >= 1)
+ {
+ first_frame_time = now;
+ flip = !flip;
+ return G_SOURCE_CONTINUE;
+ }
+
+ GskRenderer *renderer = get_renderer (widget);
+ GskRenderNode *root = gsk_renderer_get_root_node (renderer);
+
+ gsk_render_node_set_opacity (root, 1.0 - progress);
+
+ gtk_widget_queue_draw (widget);
+
+ return G_SOURCE_CONTINUE;
+}
+
+
int
main (int argc, char *argv[])
{
@@ -172,6 +212,8 @@ main (int argc, char *argv[])
area = gtk_drawing_area_new ();
gtk_widget_set_hexpand (area, TRUE);
gtk_widget_set_vexpand (area, TRUE);
+ gtk_widget_set_has_window (GTK_WIDGET (area), FALSE);
+ gtk_widget_set_app_paintable (GTK_WIDGET (area), TRUE);
gtk_container_add (GTK_CONTAINER (window), area);
g_signal_connect (area, "realize", G_CALLBACK (realize), NULL);
@@ -179,6 +221,8 @@ main (int argc, char *argv[])
g_signal_connect (area, "size-allocate", G_CALLBACK (size_allocate), NULL);
g_signal_connect (area, "draw", G_CALLBACK (draw), NULL);
+ gtk_widget_add_tick_callback (area, fade_out, NULL, NULL);
+
gtk_widget_show_all (window);
gtk_main ();
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]