From adbd434d8c93f96994749f360b164732c3b3e8b4 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 18 Sep 2020 17:46:57 +0200 Subject: [PATCH] Add GskGLShaderNode This is a rendernode that is supposed to run a GLSL fragment shader with a set of inputs and produce outputs. The inputs are: * A GskGLShader object with the source and definitions of the uniforms * A the data for the unifors, formated according to the GskGLShader * a list of render nodes that are rendered to textures Additionally there is a fallback node which is used in case OpenGL is not supported or there is some kind of failure with the shader code. --- gsk/gskenums.h | 31 ++- gsk/gskglshader.c | 529 +++++++++++++++++++++++++++++++++++++ gsk/gskglshader.h | 70 +++++ gsk/gskrendernode.h | 25 ++ gsk/gskrendernodeimpl.c | 255 ++++++++++++++++++ gsk/gskrendernodeparser.c | 53 ++++ gsk/gskrendernodeprivate.h | 2 +- gsk/meson.build | 2 + gtk/inspector/recorder.c | 25 ++ 9 files changed, 990 insertions(+), 2 deletions(-) create mode 100644 gsk/gskglshader.c create mode 100644 gsk/gskglshader.h diff --git a/gsk/gskenums.h b/gsk/gskenums.h index 24aafec502..3594f9f810 100644 --- a/gsk/gskenums.h +++ b/gsk/gskenums.h @@ -75,7 +75,8 @@ typedef enum { GSK_CROSS_FADE_NODE, GSK_TEXT_NODE, GSK_BLUR_NODE, - GSK_DEBUG_NODE + GSK_DEBUG_NODE, + GSK_GLSHADER_NODE } GskRenderNodeType; /** @@ -218,4 +219,32 @@ typedef enum GSK_TRANSFORM_CATEGORY_IDENTITY } GskTransformCategory; +/** + * GskGLUniformType: + * @GSK_GLUNIFORM_TYPE_NONE: No type, used for uninitialized or unspecified values. + * @GSK_GLUNIFORM_TYPE_FLOAT: A float uniform + * @GSK_GLUNIFORM_TYPE_INT: A GLSL int / gint32 uniform + * @GSK_GLUNIFORM_TYPE_UINT: A GLSL uint / guint32 uniform + * @GSK_GLUNIFORM_TYPE_BOOL: A GLSL bool / gboolean uniform + * @GSK_GLUNIFORM_TYPE_VEC2: A GLSL vec2 / graphene_vec2_t uniform + * @GSK_GLUNIFORM_TYPE_VEC3: A GLSL vec3 / graphene_vec3_t uniform + * @GSK_GLUNIFORM_TYPE_VEC4: A GLSL vec4 / graphene_vec4_t uniform + * + * This defines the types of the uniforms that #GskGLShaders + * declare. It defines both what the type is called in the GLSL shader + * code, and what the corresponding C type is on the Gtk side. + */ +typedef enum +{ + GSK_GLUNIFORM_TYPE_NONE, + GSK_GLUNIFORM_TYPE_FLOAT, + GSK_GLUNIFORM_TYPE_INT, + GSK_GLUNIFORM_TYPE_UINT, + GSK_GLUNIFORM_TYPE_BOOL, + GSK_GLUNIFORM_TYPE_VEC2, + GSK_GLUNIFORM_TYPE_VEC3, + GSK_GLUNIFORM_TYPE_VEC4, +} GskGLUniformType; + + #endif /* __GSK_TYPES_H__ */ diff --git a/gsk/gskglshader.c b/gsk/gskglshader.c new file mode 100644 index 0000000000..a955731d2e --- /dev/null +++ b/gsk/gskglshader.c @@ -0,0 +1,529 @@ +/* GSK - The GTK Scene Kit + * + * Copyright 2020, Red Hat Inc + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +/** + * SECTION:GskGLShader + * @Title: GskGLShader + * @Short_description: A description of GskGLShader + * + * A #GskGLShader is a snippet of GLSL that is meant to run in the + * fragment shader of the rendering pipeline. A fragment shaader it + * gets the coordinates being rendered as input and produces a the + * pixel values for that particular pixel. Additionally the + * shader can declare a set of other input arguments, called + * uniforms (as they are uniform over all the calls to your shader in + * each instance of use). A shader can also receieve up to 4 + * textures that it can use as input when producing the pixel data. + * + * The typical way a #GskGLShader is used is as an argument to a + * #GskGLShaderNode in the rendering hierarchy, and then the textures + * it gets as input are constructed by rendering the child nodes to + * textures before rendering the shader node itself. (Although you can + * also pass texture nodes as children if you want to directly use a + * texture as input). Note that the #GskGLShaderNode API is a bit + * lowlevel, and highlevel code normally uses + * gtk_snapshot_push_glshader() to produce the nodes. + * + * The actual shader code is GLSL code that gets combined with + * some other code into the fragment shader. Since the exact + * capabilities of the GPU driver differs between different OpenGL + * drivers and hardware Gtk adds some defines that you can use + * to ensure your GLSL code runs on as many drivers as it can. + * + * If the OpenGL driver is GLES, then the shader language version + * is set to 100, and GSK_GLES will be defined in the shader. + * + * Otherwise, if the OpenGL driver does not support the 3.2 core profile, + * then the shader will run with language version 110 for GL2 and 130 for GL3, + * and GSK_LEGACY will be defined in the shader. + * + * If the OpenGL driver supports the 3.2 code profile, it will be used, + * the shader language version is set to 150, and GSK_GL3 will be defined + * in the shader. + * + * The main function the shader should implement is: + * + * |[ + * void mainImage(out vec4 fragColor, + * in vec2 fragCoord, + * in vec2 resolution, + * in vec2 uv) + * ]| + * + * Where the input @fragCoord is the coordinate of the pixel we're + * currently rendering, relative to the boundary rectangle that was + * specified in the #GskGLShaderNode, and @resolution is the width and + * height of that rectangle. This is in the typical Gtk+ coordinate + * system with the origin in the top left. @uv contains the u and v + * coordinates that can be used to index a texture at the + * corresponding point. These coordinates are in the [0..1]x[0..1] + * region, with 0, 0 being in the lower left cornder (which is typical + * for OpenGL). + * + * The output @fragColor should be a RGBA color (and alpha) that will + * be used as the output for the specified pixel location. Note that + * this output will be automatically clipped to the clip region of the + * glshader node. + * + * In addition to the function arguments the shader can use one of the + * pre-defined uniforms for tetxure sources (u_source, u_source2, + * u_source3 or u_source4), as well as any custom uniforms you + * declare. + * + * Gtk uses the the "gsk" namespace in the symbols it uses in the + * shader, so your code should not use any symbols with the prefix gsk + * or GSK. There are some helper functions declared that you can use: + * + * |[ + * vec4 GskTexture(sampler2D sampler, vec2 texCoords); + * ]| + * + * This samples a texture (e.g. u_source) at the specified + * coordinates, and containes some helper ifdefs to ensure that + * it works on all OpenGL versions. + */ + +#include "config.h" +#include "gskglshader.h" +#include "gskglshaderprivate.h" + +struct _GskGLShader +{ + GObject parent_instance; + char *sourcecode; + int n_required_sources; + int uniforms_size; + GArray *uniforms; +}; + +G_DEFINE_TYPE (GskGLShader, gsk_glshader, G_TYPE_OBJECT) + +enum { + GLSHADER_PROP_0, + GLSHADER_PROP_SOURCECODE, + GLSHADER_PROP_N_REQUIRED_SOURCES, + GLSHADER_N_PROPS +}; +static GParamSpec *gsk_glshader_properties[GLSHADER_N_PROPS]; + +static void +gsk_glshader_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GskGLShader *shader = GSK_GLSHADER (object); + + switch (prop_id) + { + case GLSHADER_PROP_SOURCECODE: + g_value_set_string (value, shader->sourcecode); + break; + + case GLSHADER_PROP_N_REQUIRED_SOURCES: + g_value_set_int (value, shader->n_required_sources); + break; + + default: + g_assert_not_reached (); + } +} + +static void +gsk_glshader_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GskGLShader *shader = GSK_GLSHADER (object); + + switch (prop_id) + { + case GLSHADER_PROP_SOURCECODE: + g_free (shader->sourcecode); + shader->sourcecode = g_value_dup_string (value); + break; + + case GLSHADER_PROP_N_REQUIRED_SOURCES: + gsk_glshader_set_n_required_sources (shader, g_value_get_int (value)); + break; + + default: + g_assert_not_reached (); + } +} + +static void +gsk_glshader_finalize (GObject *object) +{ + GskGLShader *shader = GSK_GLSHADER (object); + + g_free (shader->sourcecode); + for (int i = 0; i < shader->uniforms->len; i ++) + g_free (g_array_index (shader->uniforms, GskGLUniform, i).name); + g_array_free (shader->uniforms, TRUE); + + G_OBJECT_CLASS (gsk_glshader_parent_class)->finalize (object); +} + +static void +gsk_glshader_class_init (GskGLShaderClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = gsk_glshader_get_property; + object_class->set_property = gsk_glshader_set_property; + object_class->finalize = gsk_glshader_finalize; + + /** + * GskGLShader:sourcecode: + * + * The source code for the shader. + */ + gsk_glshader_properties[GLSHADER_PROP_SOURCECODE] = + g_param_spec_string ("sourcecode", + "Sourcecode", + "The sourcecode code for the shader", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + /** + * GskGLShader:n-required-sources: + * + * The number of texture sources (u_source*) that are required to be set for the + * shader to work. + */ + gsk_glshader_properties[GLSHADER_PROP_N_REQUIRED_SOURCES] = + g_param_spec_int ("n-required-sources", + "Number of required sources", + "Minimum number of source textures required for the shader to work", + 0, 4, 0, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, GLSHADER_N_PROPS, gsk_glshader_properties); +} + +static void +gsk_glshader_init (GskGLShader *shader) +{ + shader->uniforms = g_array_new (FALSE, FALSE, sizeof (GskGLUniform)); +} + +/** + * gsk_glshader_new: + * @sourcecode: The sourcecode for the shader + * + * Creates a #GskGLShader that will render pixels using the specified code. + * + * Returns: (transfer full): A new #GskGLShader + */ +GskGLShader * +gsk_glshader_new (const char *sourcecode) +{ + GskGLShader *shader = g_object_new (GSK_TYPE_GLSHADER, + "sourcecode", sourcecode, + NULL); + return shader; +} + +/** + * gsk_glshader_get_sourcecode: + * @shader: A #GskGLShader + * + * Get the source code being used to render this shader. + * + * Returns: (transfer none): The source code for the shader + */ +const char * +gsk_glshader_get_sourcecode (GskGLShader *shader) +{ + return shader->sourcecode; +} + +/** + * gsk_glshader_get_n_required_sources: + * @shader: A #GskGLShader + * + * Returns the number of texture sources that the shader requires. This can be set + * with gsk_glshader_set_n_required_sources() and can be used to check that the + * a passed shader works in your usecase. + * + * Returns: (transfer none): The set nr of texture sources this shader requires. + */ +int +gsk_glshader_get_n_required_sources (GskGLShader *shader) +{ + return shader->n_required_sources; +} + +/** + * gsk_glshader_set_n_required_sources: + * @shader: A #GskGLShader + * @n_required_sources: The number of sources. + * + * Sets the number of texture sources that the shader requires. This can be used + * to signal to other users that this shader needs this much input. + */ +void +gsk_glshader_set_n_required_sources (GskGLShader *shader, + int n_required_sources) +{ + shader->n_required_sources = n_required_sources; +} + +static int +uniform_type_size (GskGLUniformType type) +{ + switch (type) + { + case GSK_GLUNIFORM_TYPE_FLOAT: + return sizeof (float); + + case GSK_GLUNIFORM_TYPE_INT: + return sizeof (gint32); + + case GSK_GLUNIFORM_TYPE_UINT: + case GSK_GLUNIFORM_TYPE_BOOL: + return sizeof (guint32); + + case GSK_GLUNIFORM_TYPE_VEC2: + return sizeof (float) * 2; + + case GSK_GLUNIFORM_TYPE_VEC3: + return sizeof (float) * 3; + + case GSK_GLUNIFORM_TYPE_VEC4: + return sizeof (float) * 4; + + case GSK_GLUNIFORM_TYPE_NONE: + default: + g_assert_not_reached (); + return 0; + + } +} + +/** + * gsk_glshader_add_uniform: + * @shader: A #GskGLShader + * @name: Then name of the uniform. + * @type: The uniform type + * + * This declares that the shader uses a uniform of a particular @name + * and @type. You need to declare each uniform that your shader + * uses or you will not be able to pass data to it. + */ +void +gsk_glshader_add_uniform (GskGLShader *shader, + const char *name, + GskGLUniformType type) +{ + GskGLUniform uniform = { + g_strdup (name), + type, + shader->uniforms_size + }; + + shader->uniforms_size += uniform_type_size (type); + + g_array_append_val (shader->uniforms, uniform); +} + +/** + * gsk_glshader_get_n_uniforms: + * @shader: A #GskGLShader + * + * Get the number of declared uniforms for this shader. + * + * Returns: The nr of declared uniforms + */ +int +gsk_glshader_get_n_uniforms (GskGLShader *shader) +{ + return shader->uniforms->len; +} + +/** + * gsk_glshader_get_uniform_name: + * @shader: A #GskGLShader + * @idx: A zero-based index of the uniforms + * + * Get the name of a declared uniforms for this shader at index @indx. + * + * Returns: (transfer none): The name of the declared uniform + */ +const char * +gsk_glshader_get_uniform_name (GskGLShader *shader, + int idx) +{ + return g_array_index (shader->uniforms, GskGLUniform, idx).name; +} + +/** + * gsk_glshader_get_uniform_type: + * @shader: A #GskGLShader + * @idx: A zero-based index of the uniforms + * + * Get the type of a declared uniforms for this shader at index @indx. + * + * Returns: The type of the declared uniform + */ +GskGLUniformType +gsk_glshader_get_uniform_type (GskGLShader *shader, + int idx) +{ + return g_array_index (shader->uniforms, GskGLUniform, idx).type; +} + +/** + * gsk_glshader_get_uniform_offset: + * @shader: A #GskGLShader + * @idx: A zero-based index of the uniforms + * + * Get the offset into the data block where data for this uniforms is stored. + * + * Returns: The data offset + */ +int +gsk_glshader_get_uniform_offset (GskGLShader *shader, + int idx) +{ + return g_array_index (shader->uniforms, GskGLUniform, idx).offset; +} + +const GskGLUniform * +gsk_glshader_get_uniforms (GskGLShader *shader, + int *n_uniforms) +{ + *n_uniforms = shader->uniforms->len; + return &g_array_index (shader->uniforms, GskGLUniform, 0); +} + +/** + * gsk_glshader_get_uniforms_size: + * @shader: A #GskGLShader + * + * Get the size of the data block used to specify uniform data for this shader. + * + * Returns: The size of the data block + */ +int +gsk_glshader_get_uniforms_size (GskGLShader *shader) +{ + return shader->uniforms_size; +} + +static const GskGLUniform * +gsk_glshader_find_uniform (GskGLShader *shader, + const char *name) +{ + int i; + for (i = 0; i < shader->uniforms->len; i++) + { + const GskGLUniform *u = &g_array_index (shader->uniforms, GskGLUniform, i); + if (strcmp (u->name, name) == 0) + return u; + } + + return NULL; +} + +/** + * gsk_glshader_format_uniform_data_va: + * @shader: A #GskGLShader + * @uniforms: %NULL terminated va_list with (name, pointer-to-data) arguments pairs + * + * Formats the uniform data as needed for feeding the named uniforms values into the shader. + * The argument list is a list of pairs of names, and pointers to data of the types + * that match the declared uniforms (i.e. `float *` for float uniforms and `graphene_vec4_t *` f + * or vec3 uniforms). + * + * Returns: (transfer full): A newly allocated block of data which can be passed to gsk_glshader_node_new(). + */ +guchar * +gsk_glshader_format_uniform_data_va (GskGLShader *shader, + va_list uniforms) +{ + guchar *args = g_malloc0 (shader->uniforms_size); + const char *name; + + while ((name = va_arg (uniforms, const char *)) != NULL) + { + const GskGLUniform *u; + gpointer value = va_arg (uniforms, gpointer); + guchar *args_dest; + + u = gsk_glshader_find_uniform (shader, name); + if (u == NULL) + { + /* This isn't really an error, because we can easily imaging + a shader interface that have input which isn't needed for + a particular shader */ + g_debug ("No uniform named `%s` in shader", name); + continue; + } + + args_dest = args + u->offset; + + /* We use pointers-to-value so that all values are the same + size, otherwise we couldn't handle the missing uniform case above */ + + switch (u->type) + { + case GSK_GLUNIFORM_TYPE_FLOAT: + *(float *)args_dest = *(float *)value; + break; + + case GSK_GLUNIFORM_TYPE_INT: + *(gint32 *)args_dest = *(gint32 *)value; + break; + + case GSK_GLUNIFORM_TYPE_UINT: + *(guint32 *)args_dest = *(guint32 *)value; + break; + + case GSK_GLUNIFORM_TYPE_BOOL: + *(guint32 *)args_dest = *(gboolean *)value; + break; + + case GSK_GLUNIFORM_TYPE_VEC2: + graphene_vec2_to_float ((const graphene_vec2_t *)value, + (float *)args_dest); + break; + + case GSK_GLUNIFORM_TYPE_VEC3: + graphene_vec3_to_float ((const graphene_vec3_t *)value, + (float *)args_dest); + break; + + case GSK_GLUNIFORM_TYPE_VEC4: + graphene_vec4_to_float ((const graphene_vec4_t *)value, + (float *)args_dest); + break; + + case GSK_GLUNIFORM_TYPE_NONE: + default: + g_assert_not_reached (); + } + } + + return args; +} diff --git a/gsk/gskglshader.h b/gsk/gskglshader.h new file mode 100644 index 0000000000..e06a1e14d4 --- /dev/null +++ b/gsk/gskglshader.h @@ -0,0 +1,70 @@ +/* GSK - The GTK Scene Kit + * + * Copyright 2020 Red Hat Inc + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef __GSK_GLSHADER_H__ +#define __GSK_GLSHADER_H__ + +#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +#include +#include + +G_BEGIN_DECLS + +#define GSK_TYPE_GLSHADER (gsk_glshader_get_type ()) + +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GskGLShader, gsk_glshader, GSK, GLSHADER, GObject) + +GDK_AVAILABLE_IN_ALL +GskGLShader * gsk_glshader_new (const char *sourcecode); +GDK_AVAILABLE_IN_ALL +const char * gsk_glshader_get_sourcecode (GskGLShader *shader); +GDK_AVAILABLE_IN_ALL +int gsk_glshader_get_n_required_sources (GskGLShader *shader); +GDK_AVAILABLE_IN_ALL +void gsk_glshader_set_n_required_sources (GskGLShader *shader, + int n_required_sources); +GDK_AVAILABLE_IN_ALL +void gsk_glshader_add_uniform (GskGLShader *shader, + const char *name, + GskGLUniformType type); +GDK_AVAILABLE_IN_ALL +int gsk_glshader_get_n_uniforms (GskGLShader *shader); +GDK_AVAILABLE_IN_ALL +const char * gsk_glshader_get_uniform_name (GskGLShader *shader, + int idx); +GDK_AVAILABLE_IN_ALL +GskGLUniformType gsk_glshader_get_uniform_type (GskGLShader *shader, + int idx); +GDK_AVAILABLE_IN_ALL +int gsk_glshader_get_uniform_offset (GskGLShader *shader, + int idx); +GDK_AVAILABLE_IN_ALL +int gsk_glshader_get_uniforms_size (GskGLShader *shader); +GDK_AVAILABLE_IN_ALL +guchar * gsk_glshader_format_uniform_data_va (GskGLShader *shader, + va_list uniforms); + +G_END_DECLS + +#endif /* __GSK_GLSHADER_H__ */ diff --git a/gsk/gskrendernode.h b/gsk/gskrendernode.h index 424039753a..7b125ed7eb 100644 --- a/gsk/gskrendernode.h +++ b/gsk/gskrendernode.h @@ -25,6 +25,7 @@ #include #include +#include #include G_BEGIN_DECLS @@ -122,6 +123,7 @@ GskRenderNode * gsk_render_node_deserialize (GBytes #define GSK_TYPE_CROSS_FADE_NODE (gsk_cross_fade_node_get_type()) #define GSK_TYPE_TEXT_NODE (gsk_text_node_get_type()) #define GSK_TYPE_BLUR_NODE (gsk_blur_node_get_type()) +#define GSK_TYPE_GLSHADER_NODE (gsk_glshader_node_get_type()) typedef struct _GskDebugNode GskDebugNode; typedef struct _GskColorNode GskColorNode; @@ -146,6 +148,7 @@ typedef struct _GskBlendNode GskBlendNode; typedef struct _GskCrossFadeNode GskCrossFadeNode; typedef struct _GskTextNode GskTextNode; typedef struct _GskBlurNode GskBlurNode; +typedef struct _GskGLShaderNode GskGLShaderNode; GDK_AVAILABLE_IN_ALL GType gsk_debug_node_get_type (void) G_GNUC_CONST; @@ -451,6 +454,28 @@ GskRenderNode * gsk_blur_node_get_child (GskRenderNode GDK_AVAILABLE_IN_ALL float gsk_blur_node_get_radius (GskRenderNode *node); +GDK_AVAILABLE_IN_ALL +GType gsk_glshader_node_get_type (void) G_GNUC_CONST; +GDK_AVAILABLE_IN_ALL +GskRenderNode * gsk_glshader_node_new (GskGLShader *shader, + const graphene_rect_t *bounds, + guchar *uniform_data, + GskRenderNode *fallback, + GskRenderNode **children, + int n_children); +GDK_AVAILABLE_IN_ALL +GskRenderNode * gsk_glshader_node_get_fallback_child (GskRenderNode *node); +GDK_AVAILABLE_IN_ALL +guint gsk_glshader_node_get_n_children (GskRenderNode *node); +GDK_AVAILABLE_IN_ALL +GskRenderNode * gsk_glshader_node_get_child (GskRenderNode *node, + int idx); +GDK_AVAILABLE_IN_ALL +const guchar * gsk_glshader_node_get_uniform_data (GskRenderNode *node); +GDK_AVAILABLE_IN_ALL +GskGLShader * gsk_glshader_node_get_shader (GskRenderNode *node); + + G_END_DECLS #endif /* __GSK_RENDER_NODE_H__ */ diff --git a/gsk/gskrendernodeimpl.c b/gsk/gskrendernodeimpl.c index 76d2d774bb..15b1548c48 100644 --- a/gsk/gskrendernodeimpl.c +++ b/gsk/gskrendernodeimpl.c @@ -4470,6 +4470,244 @@ gsk_debug_node_get_message (GskRenderNode *node) return self->message; } +/*** GSK_GLSHADER_NODE ***/ + +struct _GskGLShaderNode +{ + GskRenderNode render_node; + + GskGLShader *shader; + union { + guchar inlined[8]; /* Most shaders have few args, so avoid extra alloc */ + guchar *external; + } args; + GskRenderNode *fallback; + GskRenderNode **children; + guint n_children; +}; + +static void +gsk_glshader_node_finalize (GskRenderNode *node) +{ + GskGLShaderNode *self = (GskGLShaderNode *) node; + GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_GLSHADER_NODE)); + int uniforms_size; + + for (guint i = 0; i < self->n_children; i++) + gsk_render_node_unref (self->children[i]); + g_free (self->children); + + gsk_render_node_unref (self->fallback); + + uniforms_size = gsk_glshader_get_uniforms_size (self->shader); + if (uniforms_size > sizeof (self->args.inlined)) + g_free (self->args.external); + + g_object_unref (self->shader); + + parent_class->finalize (node); +} + +static void +gsk_glshader_node_draw (GskRenderNode *node, + cairo_t *cr) +{ + GskGLShaderNode *self = (GskGLShaderNode *) node; + + gsk_render_node_draw (self->fallback, cr); +} + +static void +gsk_glshader_node_diff (GskRenderNode *node1, + GskRenderNode *node2, + cairo_region_t *region) +{ + GskGLShaderNode *self1 = (GskGLShaderNode *) node1; + GskGLShaderNode *self2 = (GskGLShaderNode *) node2; + int uniforms_size = gsk_glshader_get_uniforms_size (self1->shader); + + if (graphene_rect_equal (&node1->bounds, &node2->bounds) && + self1->shader == self2->shader && + ((uniforms_size <= sizeof (self1->args.inlined)) ? + memcmp (&self1->args.inlined[0], &self2->args.inlined[0], uniforms_size) == 0: + memcmp (self1->args.external, self2->args.external, uniforms_size) == 0) && + self1->n_children == self2->n_children) + { + gsk_render_node_diff (self1->fallback, self2->fallback, region); + + for (guint i = 0; i < self1->n_children; i++) + { + if (self1->children[i] != self2->children[i]) + { + gsk_render_node_diff_impossible (node1, node2, region); + break; + } + } + } + else + { + gsk_render_node_diff_impossible (node1, node2, region); + } +} + +/** + * gsk_glshader_node_new: + * @shader: the #GskGLShader + * @bounds: the rectangle to render the shader into + * @uniform_data: Data for the uniforms + * @fallback: Render node to use if OpenGL is not supported + * @children: List of child nodes, these will be rendered to textures and used as input. + * @n_children: Length of @children (currenly the GL backend only supports max 4 children) + * + * Creates a #GskRenderNode that will render the given @gl_program into the area given by @bounds. + * The @uniform_data is a block of data to use for uniform input, as per types and offsets + * defined by the @shader. Normally this is generated by gsk_glshader_format_uniform_data_va(). + * + * See #GskGLShader for details about how the shader should be written. + * + * All the children will be rendered into textures, if they aren't already #GskTextureNode:s + * then they will be used directly. These textures will be sent as input to the shader. + * + * If the backend doesn't support GL shaders, or if there is any problem when compiling + * the shader, then the fallback shader node will be used instead. + * + * Returns: (transfer full) (type GskGLShaderNode): A new #GskRenderNode + */ +GskRenderNode * +gsk_glshader_node_new (GskGLShader *shader, + const graphene_rect_t *bounds, + guchar *uniform_data, + GskRenderNode *fallback, + GskRenderNode **children, + int n_children) +{ + GskGLShaderNode *self; + GskRenderNode *node; + int uniforms_size; + + g_return_val_if_fail (bounds != NULL, NULL); + + self = gsk_render_node_alloc (GSK_GLSHADER_NODE); + node = (GskRenderNode *) self; + + graphene_rect_init_from_rect (&node->bounds, bounds); + self->shader = g_object_ref (shader); + + uniforms_size = gsk_glshader_get_uniforms_size (shader); + if (uniforms_size <= sizeof (self->args.inlined)) + memcpy (self->args.inlined, uniform_data, uniforms_size); + else + self->args.external = g_memdup (uniform_data, uniforms_size); + + self->fallback = gsk_render_node_ref (fallback); + + self->n_children = n_children; + if (n_children > 0) + { + self->children = g_malloc_n (n_children, sizeof (GskRenderNode *)); + for (guint i = 0; i < n_children; i++) + self->children[i] = gsk_render_node_ref (children[i]); + } + + return node; +} + +/** + * gsk_glshader_node_get_fallback_child: + * @node: (type GskGLShaderNode): a #GskRenderNode for a gl shader + * + * Gets the fallback child node + * + * Returns: (transfer none): The fallback node + */ +GskRenderNode * +gsk_glshader_node_get_fallback_child (GskRenderNode *node) +{ + GskGLShaderNode *self = (GskGLShaderNode *) node; + + g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_GLSHADER_NODE), NULL); + + return self->fallback; +} + +/** + * gsk_glshader_node_get_n_children: + * @node: (type GskGLShaderNode): a #GskRenderNode for a gl shader + * + * Returns the number of (non-fallback) children + * + * Returns: The number of children + */ +guint +gsk_glshader_node_get_n_children (GskRenderNode *node) +{ + GskGLShaderNode *self = (GskGLShaderNode *) node; + + g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_GLSHADER_NODE), 0); + + return self->n_children; +} + +/** + * gsk_glshader_node_get_child: + * @node: (type GskGLShaderNode): a #GskRenderNode for a gl shader + * @idx: the position of the child to get + * + * Gets one of the (non-fallback) children. + * + * Returns: (transfer none): the @idx'th child of @node + */ +GskRenderNode * +gsk_glshader_node_get_child (GskRenderNode *node, + int idx) +{ + GskGLShaderNode *self = (GskGLShaderNode *) node; + + g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_GLSHADER_NODE), NULL); + g_return_val_if_fail (idx < self->n_children, NULL); + + return self->children[idx]; +} + +/** + * gsk_glshader_node_get_shader: + * @node: (type GskGLShaderNode): a #GskRenderNode for a gl shader + * + * Gets shader code for the node. + * + * Returns: (transfer none): the #GskGLShader shader + */ +GskGLShader * +gsk_glshader_node_get_shader (GskRenderNode *node) +{ + GskGLShaderNode *self = (GskGLShaderNode *) node; + + g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_GLSHADER_NODE), 0); + + return self->shader; +} + +/** + * gsk_glshader_node_get_uniform_data: + * @node: (type GskGLShaderNode): a #GskRenderNode for a gl shader + * + * Gets args for the node. + */ +const guchar * +gsk_glshader_node_get_uniform_data (GskRenderNode *node) +{ + GskGLShaderNode *self = (GskGLShaderNode *) node; + int uniforms_size; + + g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_GLSHADER_NODE), NULL); + + uniforms_size = gsk_glshader_get_uniforms_size (self->shader); + if (uniforms_size > sizeof (self->args.inlined)) + return self->args.external; + else + return &self->args.inlined[0]; +} + GType gsk_render_node_types[GSK_RENDER_NODE_TYPE_N_TYPES]; #ifndef I_ @@ -4506,6 +4744,7 @@ GSK_DEFINE_RENDER_NODE_TYPE (gsk_blend_node, GSK_BLEND_NODE) GSK_DEFINE_RENDER_NODE_TYPE (gsk_cross_fade_node, GSK_CROSS_FADE_NODE) GSK_DEFINE_RENDER_NODE_TYPE (gsk_text_node, GSK_TEXT_NODE) GSK_DEFINE_RENDER_NODE_TYPE (gsk_blur_node, GSK_BLUR_NODE) +GSK_DEFINE_RENDER_NODE_TYPE (gsk_glshader_node, GSK_GLSHADER_NODE) GSK_DEFINE_RENDER_NODE_TYPE (gsk_debug_node, GSK_DEBUG_NODE) static void @@ -4863,6 +5102,22 @@ gsk_render_node_init_types_once (void) gsk_render_node_types[GSK_BLUR_NODE] = node_type; } + { + const GskRenderNodeTypeInfo node_info = + { + GSK_GLSHADER_NODE, + sizeof (GskGLShaderNode), + NULL, + gsk_glshader_node_finalize, + gsk_glshader_node_draw, + NULL, + gsk_glshader_node_diff, + }; + + GType node_type = gsk_render_node_type_register_static (I_("GskGLShaderNode"), &node_info); + gsk_render_node_types[GSK_GLSHADER_NODE] = node_type; + } + { const GskRenderNodeTypeInfo node_info = { diff --git a/gsk/gskrendernodeparser.c b/gsk/gskrendernodeparser.c index 870ce8818b..747d5dc59a 100644 --- a/gsk/gskrendernodeparser.c +++ b/gsk/gskrendernodeparser.c @@ -1089,6 +1089,15 @@ parse_inset_shadow_node (GtkCssParser *parser) return gsk_inset_shadow_node_new (&outline, &color, dx, dy, spread, blur); } + +static GskRenderNode * +parse_glshader_node (GtkCssParser *parser) +{ + /* TODO */ + gtk_css_parser_error_syntax (parser, "glshader node parsing not implemented yet"); + return NULL; +} + static GskRenderNode * parse_border_node (GtkCssParser *parser) { @@ -1603,6 +1612,7 @@ parse_node (GtkCssParser *parser, { "text", parse_text_node }, { "texture", parse_texture_node }, { "transform", parse_transform_node }, + { "glshader", parse_glshader_node }, }; GskRenderNode **node_p = out_node; guint i; @@ -1837,6 +1847,19 @@ append_point (GString *str, string_append_double (str, p->y); } +static void +append_string (GString *str, + const char *value) +{ + char *escaped = g_strescape (value, NULL); + + g_string_append_c (str, '"'); + g_string_append (str, escaped); + g_string_append_c (str, '"'); + + g_free (escaped); +} + static void append_vec4 (GString *str, const graphene_vec4_t *v) @@ -1914,6 +1937,18 @@ append_point_param (Printer *p, g_string_append_c (p->str, '\n'); } +static void +append_string_param (Printer *p, + const char *param_name, + const char *value) +{ + _indent (p); + g_string_append_printf (p->str, "%s: ", param_name); + append_string (p->str, value); + g_string_append_c (p->str, ';'); + g_string_append_c (p->str, '\n'); +} + static void append_vec4_param (Printer *p, const char *param_name, @@ -2441,6 +2476,24 @@ render_node_print (Printer *p, } break; + case GSK_GLSHADER_NODE: + { + start_node (p, "glshader"); + + GskGLShader *shader = gsk_glshader_node_get_shader (node); + append_string_param (p, "shader", gsk_glshader_get_sourcecode (shader)); + append_node_param (p, "fallback", gsk_glshader_node_get_fallback_child (node)); + for (guint i = 0; i < gsk_glshader_node_get_n_children (node); i ++) + { + GskRenderNode *child = gsk_glshader_node_get_child (node, i); + _indent (p); + render_node_print (p, child); + } + + end_node (p); + } + break; + case GSK_REPEAT_NODE: { GskRenderNode *child = gsk_repeat_node_get_child (node); diff --git a/gsk/gskrendernodeprivate.h b/gsk/gskrendernodeprivate.h index d75a2c68b1..13961acc82 100644 --- a/gsk/gskrendernodeprivate.h +++ b/gsk/gskrendernodeprivate.h @@ -13,7 +13,7 @@ typedef struct _GskRenderNodeClass GskRenderNodeClass; * We don't add an "n-types" value to avoid having to handle * it in every single switch. */ -#define GSK_RENDER_NODE_TYPE_N_TYPES (GSK_DEBUG_NODE + 1) +#define GSK_RENDER_NODE_TYPE_N_TYPES (GSK_GLSHADER_NODE + 1) extern GType gsk_render_node_types[]; diff --git a/gsk/meson.build b/gsk/meson.build index c3567d78ed..b52d379eb9 100644 --- a/gsk/meson.build +++ b/gsk/meson.build @@ -21,6 +21,7 @@ gsk_private_gl_shaders = [ gsk_public_sources = files([ 'gskdiff.c', 'gskcairorenderer.c', + 'gskglshader.c', 'gskrenderer.c', 'gskrendernode.c', 'gskrendernodeimpl.c', @@ -52,6 +53,7 @@ gsk_private_sources = files([ gsk_public_headers = files([ 'gskcairorenderer.h', 'gskenums.h', + 'gskglshader.h', 'gskrenderer.h', 'gskrendernode.h', 'gskroundedrect.h', diff --git a/gtk/inspector/recorder.c b/gtk/inspector/recorder.c index 8b42de031e..0f229d17ad 100644 --- a/gtk/inspector/recorder.c +++ b/gtk/inspector/recorder.c @@ -171,6 +171,25 @@ create_list_model_for_render_node (GskRenderNode *node) return create_render_node_list_model ((GskRenderNode *[2]) { gsk_cross_fade_node_get_start_child (node), gsk_cross_fade_node_get_end_child (node) }, 2); + case GSK_GLSHADER_NODE: + { + GListStore *store = G_LIST_STORE (create_render_node_list_model ((GskRenderNode *[1]) { gsk_glshader_node_get_fallback_child (node) }, 1)); + + for (guint i = 0; i < gsk_glshader_node_get_n_children (node); i++) + { + GskRenderNode *child = gsk_glshader_node_get_child (node, i); + graphene_rect_t bounds; + GdkPaintable *paintable; + + gsk_render_node_get_bounds (child, &bounds); + paintable = gtk_render_node_paintable_new (child, &bounds); + g_list_store_append (store, paintable); + g_object_unref (paintable); + } + + return G_LIST_MODEL (store); + } + case GSK_CONTAINER_NODE: { GListStore *store; @@ -270,6 +289,8 @@ node_type_name (GskRenderNodeType type) return "Text"; case GSK_BLUR_NODE: return "Blur"; + case GSK_GLSHADER_NODE: + return "GLShader"; } } @@ -301,6 +322,7 @@ node_name (GskRenderNode *node) case GSK_CROSS_FADE_NODE: case GSK_TEXT_NODE: case GSK_BLUR_NODE: + case GSK_GLSHADER_NODE: return g_strdup (node_type_name (gsk_render_node_get_node_type (node))); case GSK_DEBUG_NODE: @@ -759,6 +781,9 @@ populate_render_node_properties (GtkListStore *store, add_float_row (store, "Radius", gsk_blur_node_get_radius (node)); break; + case GSK_GLSHADER_NODE: + break; + case GSK_INSET_SHADOW_NODE: { const GdkRGBA *color = gsk_inset_shadow_node_peek_color (node);