diff options
author | Alexander Larsson <alexl@redhat.com> | 2020-09-18 17:46:57 +0200 |
---|---|---|
committer | Alexander Larsson <alexl@redhat.com> | 2020-09-29 09:51:16 +0200 |
commit | 7ea755e206c36960db05f0e0660c6f5f20b692c6 (patch) | |
tree | 56a83de7a5cd4f40d8f4ab539f4b3211015a4f12 | |
parent | 6e9b58b6f081f3cf1cba697adada8cd3bc2438d5 (diff) | |
download | gtk+-7ea755e206c36960db05f0e0660c6f5f20b692c6.tar.gz |
Add GskGLShaderNode and GskGLShader
A GskGLShader is an abstraction of a GLSL fragment shader that
can produce pixel values given inputs:
* N (currently max 4) textures
* Current arguments for the shader uniform
Uniform types are: float,(u)int,bool,vec234)
There is also a builder for the uniform arguments which are
passed around as immutable GBytes in the built form.
A GskGLShaderNode is a render node that renders a GskGLShader inside a
specified rectangular bounds. It renders its child nodes as textures
and passes those as texture arguments to the shader. You also pass it
a uniform arguments object.
-rw-r--r-- | docs/reference/gsk/gsk4-docs.xml | 1 | ||||
-rw-r--r-- | docs/reference/gsk/gsk4-sections.txt | 51 | ||||
-rw-r--r-- | docs/reference/gsk/gsk4.types | 1 | ||||
-rw-r--r-- | gsk/gsk.h | 1 | ||||
-rw-r--r-- | gsk/gskenums.h | 32 | ||||
-rw-r--r-- | gsk/gskglshader.c | 1394 | ||||
-rw-r--r-- | gsk/gskglshader.h | 162 | ||||
-rw-r--r-- | gsk/gskglshaderprivate.h | 19 | ||||
-rw-r--r-- | gsk/gskrendernode.h | 21 | ||||
-rw-r--r-- | gsk/gskrendernodeimpl.c | 220 | ||||
-rw-r--r-- | gsk/gskrendernodeparser.c | 336 | ||||
-rw-r--r-- | gsk/gskrendernodeprivate.h | 2 | ||||
-rw-r--r-- | gsk/meson.build | 2 | ||||
-rw-r--r-- | gtk/inspector/recorder.c | 136 |
14 files changed, 2375 insertions, 3 deletions
diff --git a/docs/reference/gsk/gsk4-docs.xml b/docs/reference/gsk/gsk4-docs.xml index ea2952f62c..2bfecbf290 100644 --- a/docs/reference/gsk/gsk4-docs.xml +++ b/docs/reference/gsk/gsk4-docs.xml @@ -20,6 +20,7 @@ <xi:include href="xml/GskRenderNode.xml" /> <xi:include href="xml/GskRoundedRect.xml" /> <xi:include href="xml/GskTransform.xml" /> + <xi:include href="xml/GskGLShader.xml" /> </reference> <index id="api-index-full"> diff --git a/docs/reference/gsk/gsk4-sections.txt b/docs/reference/gsk/gsk4-sections.txt index 3d0e9f0ccf..b0bf7b506b 100644 --- a/docs/reference/gsk/gsk4-sections.txt +++ b/docs/reference/gsk/gsk4-sections.txt @@ -48,6 +48,7 @@ GskShadowNode GskTextNode GskTextureNode GskTransformNode +GskGLShaderNode gsk_render_node_ref gsk_render_node_unref GskRenderNodeType @@ -152,6 +153,11 @@ gsk_blur_node_get_radius gsk_debug_node_new gsk_debug_node_get_child gsk_debug_node_get_message +gsk_gl_shader_node_new +gsk_gl_shader_node_get_n_children +gsk_gl_shader_node_get_child +gsk_gl_shader_node_get_args +gsk_gl_shader_node_get_shader <SUBSECTION Standard> GSK_IS_RENDER_NODE GSK_RENDER_NODE @@ -177,6 +183,7 @@ GSK_TYPE_SHADOW_NODE GSK_TYPE_TEXT_NODE GSK_TYPE_TEXTURE_NODE GSK_TYPE_TRANSFORM_NODE +GSK_TYPE_GLSHADER_NODE GskRenderNodeClass gsk_blend_node_get_type gsk_blur_node_get_type @@ -202,6 +209,7 @@ gsk_shadow_node_get_type gsk_text_node_get_type gsk_texture_node_get_type gsk_transform_node_get_type +gsk_gl_shader_node_get_type GSK_TYPE_BLEND_MODE <SUBSECTION Standard> gsk_serialization_error_quark @@ -266,3 +274,46 @@ gsk_transform_get_type gsk_transform_new </SECTION> +<SECTION> +<FILE>GskGLShader</FILE> +GskGLShader +gsk_gl_shader_new_from_bytes +gsk_gl_shader_new_from_resource +gsk_gl_shader_compile +gsk_gl_shader_get_source +gsk_gl_shader_get_n_textures +gsk_gl_shader_get_n_uniforms +gsk_gl_shader_get_uniform_name +gsk_gl_shader_find_uniform_by_name +gsk_gl_shader_get_uniform_type +gsk_gl_shader_get_uniform_offset +gsk_gl_shader_get_args_size + +<SUBSECTION Uniform Data> +gsk_gl_shader_get_arg_float +gsk_gl_shader_get_arg_int +gsk_gl_shader_get_arg_uint +gsk_gl_shader_get_arg_bool +gsk_gl_shader_get_arg_vec2 +gsk_gl_shader_get_arg_vec3 +gsk_gl_shader_get_arg_vec4 +gsk_gl_shader_format_args_va +gsk_gl_shader_format_args + +<SUBSECTION Shader Args Builder> +GskShaderArgsBuilder + +gsk_shader_args_builder_new +gsk_shader_args_builder_to_args +gsk_shader_args_builder_free_to_args +gsk_shader_args_builder_unref +gsk_shader_args_builder_ref + +gsk_shader_args_builder_set_float +gsk_shader_args_builder_set_int +gsk_shader_args_builder_set_uint +gsk_shader_args_builder_set_bool +gsk_shader_args_builder_set_vec2 +gsk_shader_args_builder_set_vec3 +gsk_shader_args_builder_set_vec4 +</SECTION> diff --git a/docs/reference/gsk/gsk4.types b/docs/reference/gsk/gsk4.types index 71a774ca97..94eeabf270 100644 --- a/docs/reference/gsk/gsk4.types +++ b/docs/reference/gsk/gsk4.types @@ -1,2 +1,3 @@ gsk_render_node_get_type gsk_renderer_get_type +gsk_gl_shader_get_type @@ -25,6 +25,7 @@ #include <gsk/gskrendernode.h> #include <gsk/gskroundedrect.h> #include <gsk/gsktransform.h> +#include <gsk/gskglshader.h> #include <gsk/gskcairorenderer.h> diff --git a/gsk/gskenums.h b/gsk/gskenums.h index 24aafec502..0cd0c284fc 100644 --- a/gsk/gskenums.h +++ b/gsk/gskenums.h @@ -48,6 +48,7 @@ * @GSK_TEXT_NODE: A node containing a glyph string * @GSK_BLUR_NODE: A node that applies a blur * @GSK_DEBUG_NODE: Debug information that does not affect the rendering + * @GSK_GL_SHADER_NODE: A node that uses OpenGL fragment shaders to render * The type of a node determines what the node is rendering. */ @@ -75,7 +76,8 @@ typedef enum { GSK_CROSS_FADE_NODE, GSK_TEXT_NODE, GSK_BLUR_NODE, - GSK_DEBUG_NODE + GSK_DEBUG_NODE, + GSK_GL_SHADER_NODE } GskRenderNodeType; /** @@ -218,4 +220,32 @@ typedef enum GSK_TRANSFORM_CATEGORY_IDENTITY } GskTransformCategory; +/** + * GskGLUniformType: + * @GSK_GL_UNIFORM_TYPE_NONE: No type, used for uninitialized or unspecified values. + * @GSK_GL_UNIFORM_TYPE_FLOAT: A float uniform + * @GSK_GL_UNIFORM_TYPE_INT: A GLSL int / gint32 uniform + * @GSK_GL_UNIFORM_TYPE_UINT: A GLSL uint / guint32 uniform + * @GSK_GL_UNIFORM_TYPE_BOOL: A GLSL bool / gboolean uniform + * @GSK_GL_UNIFORM_TYPE_VEC2: A GLSL vec2 / graphene_vec2_t uniform + * @GSK_GL_UNIFORM_TYPE_VEC3: A GLSL vec3 / graphene_vec3_t uniform + * @GSK_GL_UNIFORM_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_GL_UNIFORM_TYPE_NONE, + GSK_GL_UNIFORM_TYPE_FLOAT, + GSK_GL_UNIFORM_TYPE_INT, + GSK_GL_UNIFORM_TYPE_UINT, + GSK_GL_UNIFORM_TYPE_BOOL, + GSK_GL_UNIFORM_TYPE_VEC2, + GSK_GL_UNIFORM_TYPE_VEC3, + GSK_GL_UNIFORM_TYPE_VEC4, +} GskGLUniformType; + + #endif /* __GSK_TYPES_H__ */ diff --git a/gsk/gskglshader.c b/gsk/gskglshader.c new file mode 100644 index 0000000000..7ad7773496 --- /dev/null +++ b/gsk/gskglshader.c @@ -0,0 +1,1394 @@ +/* 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 <http://www.gnu.org/licenses/>. + */ + +/** + * SECTION:GskGLShader + * @Title: GskGLShader + * @Short_description: Fragment shaders for GSK + * + * A #GskGLShader is a snippet of GLSL that is meant to run in the + * fragment shader of the rendering pipeline. A fragment shader + * gets the coordinates being rendered as input and produces 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 receive up to 4 + * textures that it can use as input when producing the pixel data. + * + #GskGLShader is usually used with gtk_snapshot_push_gl_shader() +* to produce a #GskGLShaderNode in the rendering hierarchy, and then +* its input textures are constructed by rendering the child nodes to +* textures before rendering the shader node itself. (You can pass +* texture nodes as children if you want to directly use a texture +* as input). + * + * 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 must implement is: + * + * |[<!-- language="plain" --> + * 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 (with + * premultiplied 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 define + * up to 4 uniforms for textures which must be called u_textureN + * (i.e. u_texture1 to u_texture4) as well as any custom uniforms + * you want of types int, uint, bool, float, vec2, vec3 or vec4. + * + * All textures sources contain premultiplied alpha colors, but if some + * there are outer sources of colors there is a gsk_premultiply() helper + * to compute premultiplication when needed. + * + * Note that GTK parses the uniform declarations, so each uniform has to + * be on a line by itself with no other code, like so: + * + * |[<!-- language="plain" --> + * uniform float u_time; + * uniform vec3 u_color; + * uniform sampler2D u_texture1; + * uniform sampler2D u_texture2; + * ]| + * + * 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: + * + * |[<!-- language="plain" --> + * vec4 GskTexture(sampler2D sampler, vec2 texCoords); + * ]| + * + * This samples a texture (e.g. u_texture1) at the specified + * coordinates, and containes some helper ifdefs to ensure that + * it works on all OpenGL versions. + * + * # An example shader + * + * |[<!-- language="plain" --> + * uniform float position; + * uniform sampler2D u_texture1; + * uniform sampler2D u_texture2; + * + * void mainImage(out vec4 fragColor, + * in vec2 fragCoord, + * in vec2 resolution, + * in vec2 uv) { + * vec4 source1 = GskTexture(u_texture1, uv); + * vec4 source2 = GskTexture(u_texture2, uv); + * + * fragColor = position * source1 + (1.0 - position) * source2; + * } + * ]| + */ + +#include "config.h" +#include "gskglshader.h" +#include "gskglshaderprivate.h" +#include "gskdebugprivate.h" +#include "gl/gskglrendererprivate.h" + +static GskGLUniformType +uniform_type_from_glsl (const char *str) +{ + if (strcmp (str, "int") == 0) + return GSK_GL_UNIFORM_TYPE_INT; + if (strcmp (str, "uint") == 0) + return GSK_GL_UNIFORM_TYPE_UINT; + if (strcmp (str, "bool") == 0) + return GSK_GL_UNIFORM_TYPE_BOOL; + if (strcmp (str, "float") == 0) + return GSK_GL_UNIFORM_TYPE_FLOAT; + if (strcmp (str, "vec2") == 0) + return GSK_GL_UNIFORM_TYPE_VEC2; + if (strcmp (str, "vec3") == 0) + return GSK_GL_UNIFORM_TYPE_VEC3; + if (strcmp (str, "vec4") == 0) + return GSK_GL_UNIFORM_TYPE_VEC4; + + return GSK_GL_UNIFORM_TYPE_NONE; +} + +static const char * +uniform_type_name (GskGLUniformType type) +{ + switch (type) + { + case GSK_GL_UNIFORM_TYPE_FLOAT: + return "float"; + + case GSK_GL_UNIFORM_TYPE_INT: + return "int"; + + case GSK_GL_UNIFORM_TYPE_UINT: + return "uint"; + + case GSK_GL_UNIFORM_TYPE_BOOL: + return "bool"; + + case GSK_GL_UNIFORM_TYPE_VEC2: + return "vec2"; + + case GSK_GL_UNIFORM_TYPE_VEC3: + return "vec3"; + + case GSK_GL_UNIFORM_TYPE_VEC4: + return "vec4"; + + case GSK_GL_UNIFORM_TYPE_NONE: + default: + g_assert_not_reached (); + return NULL; + } +} + +static int +uniform_type_size (GskGLUniformType type) +{ + switch (type) + { + case GSK_GL_UNIFORM_TYPE_FLOAT: + return sizeof (float); + + case GSK_GL_UNIFORM_TYPE_INT: + return sizeof (gint32); + + case GSK_GL_UNIFORM_TYPE_UINT: + case GSK_GL_UNIFORM_TYPE_BOOL: + return sizeof (guint32); + + case GSK_GL_UNIFORM_TYPE_VEC2: + return sizeof (float) * 2; + + case GSK_GL_UNIFORM_TYPE_VEC3: + return sizeof (float) * 3; + + case GSK_GL_UNIFORM_TYPE_VEC4: + return sizeof (float) * 4; + + case GSK_GL_UNIFORM_TYPE_NONE: + default: + g_assert_not_reached (); + return 0; + } +} + +struct _GskGLShader +{ + GObject parent_instance; + GBytes *source; + char *resource; + int n_textures; + int uniforms_size; + GArray *uniforms; +}; + +G_DEFINE_TYPE (GskGLShader, gsk_gl_shader, G_TYPE_OBJECT) + +enum { + GLSHADER_PROP_0, + GLSHADER_PROP_SOURCE, + GLSHADER_PROP_RESOURCE, + GLSHADER_N_PROPS +}; +static GParamSpec *gsk_gl_shader_properties[GLSHADER_N_PROPS]; + +static void +gsk_gl_shader_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GskGLShader *shader = GSK_GL_SHADER (object); + + switch (prop_id) + { + case GLSHADER_PROP_SOURCE: + g_value_set_boxed (value, shader->source); + break; + + case GLSHADER_PROP_RESOURCE: + g_value_set_string (value, shader->resource); + break; + + default: + g_assert_not_reached (); + } +} + +static void +gsk_gl_shader_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GskGLShader *shader = GSK_GL_SHADER (object); + + switch (prop_id) + { + case GLSHADER_PROP_SOURCE: + g_clear_pointer (&shader->source, g_bytes_unref); + shader->source = g_value_dup_boxed (value); + break; + + case GLSHADER_PROP_RESOURCE: + { + GError *error = NULL; + GBytes *source; + const char *resource; + + resource = g_value_get_string (value); + if (resource == NULL) + break; + + source = g_resources_lookup_data (resource, 0, &error); + if (source) + { + g_clear_pointer (&shader->source, g_bytes_unref); + shader->source = source; + shader->resource = g_strdup (resource); + } + else + { + g_critical ("Unable to load resource %s for glshader: %s", resource, error->message); + g_error_free (error); + if (shader->source == NULL) + shader->source = g_bytes_new_static ("", 1); + } + } + break; + + default: + g_assert_not_reached (); + } +} + +static void +gsk_gl_shader_finalize (GObject *object) +{ + GskGLShader *shader = GSK_GL_SHADER (object); + + g_bytes_unref (shader->source); + g_free (shader->resource); + 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_gl_shader_parent_class)->finalize (object); +} + +static GRegex *uniform_regexp = NULL; /* Initialized in class_init */ + + +static void +gsk_gl_shader_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); +} + +static void +gsk_gl_shader_constructed (GObject *object) +{ + GskGLShader *shader = GSK_GL_SHADER (object); + gsize string_len; + const char *string = g_bytes_get_data (shader->source, &string_len); + GMatchInfo *match_info; + int max_texture_seen = 0; + + g_regex_match_full (uniform_regexp, + string, string_len, 0, 0, + &match_info, NULL); + while (g_match_info_matches (match_info)) + { + char *type = g_match_info_fetch (match_info, 1); + char *name = g_match_info_fetch (match_info, 2); + + if (strcmp (type, "sampler2D") == 0) + { + /* Textures are special cased */ + + if (g_str_has_prefix (name, "u_texture") && + strlen (name) == strlen ("u_texture")+1) + { + char digit = name[strlen("u_texture")]; + if (digit >= '1' && digit <= '9') + max_texture_seen = MAX(max_texture_seen, digit - '0'); + } + else + g_debug ("Unhandled shader texture uniform '%s', use uniforms of name 'u_texture[1..9]'", name); + } + else + { + GskGLUniformType utype = uniform_type_from_glsl (type); + g_assert (utype != GSK_GL_UNIFORM_TYPE_NONE); // Shouldn't have matched the regexp + gsk_gl_shader_add_uniform (shader, name, utype); + } + + g_free (type); + g_free (name); + + g_match_info_next (match_info, NULL); + } + + g_match_info_free (match_info); + + shader->n_textures = max_texture_seen; + + if (GSK_DEBUG_CHECK(SHADERS)) + { + GString *s; + + s = g_string_new (""); + for (int i = 0; i < shader->uniforms->len; i++) + { + GskGLUniform *u = &g_array_index (shader->uniforms, GskGLUniform, i); + if (i > 0) + g_string_append (s, ", "); + g_string_append_printf (s, "%s %s", uniform_type_name (u->type), u->name); + } + g_message ("Shader constructed: %d textures, %d uniforms (%s)", + shader->n_textures, shader->uniforms->len, + s->str); + g_string_free (s, TRUE); + } +} + +#define SPACE_RE "[ \\t]+" // Don't use \s, we don't want to match newlines +#define OPT_SPACE_RE "[ \\t]*" +#define UNIFORM_TYPE_RE "(int|uint|bool|float|vec2|vec3|vec4|sampler2D)" +#define UNIFORM_NAME_RE "([\\w]+)" +#define OPT_INIT_VALUE_RE "[-\\w(),. ]+" // This is a bit simple, but will match most initializers +#define OPT_COMMENT_RE "(//.*)?" +#define OPT_INITIALIZER_RE "(" OPT_SPACE_RE "=" OPT_SPACE_RE OPT_INIT_VALUE_RE ")?" +#define UNIFORM_MATCHER_RE "^uniform" SPACE_RE UNIFORM_TYPE_RE SPACE_RE UNIFORM_NAME_RE OPT_INITIALIZER_RE OPT_SPACE_RE ";" OPT_SPACE_RE OPT_COMMENT_RE "$" + +static void +gsk_gl_shader_class_init (GskGLShaderClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + uniform_regexp = g_regex_new (UNIFORM_MATCHER_RE, + G_REGEX_MULTILINE | G_REGEX_RAW | G_REGEX_OPTIMIZE, + 0, NULL); + + object_class->get_property = gsk_gl_shader_get_property; + object_class->set_property = gsk_gl_shader_set_property; + object_class->finalize = gsk_gl_shader_finalize; + object_class->constructed = gsk_gl_shader_constructed; + + /** + * GskGLShader:sourcecode: + * + * The source code for the shader, as a #GBytes. + */ + gsk_gl_shader_properties[GLSHADER_PROP_SOURCE] = + g_param_spec_boxed ("source", + "Source", + "The sourcecode for the shader", + G_TYPE_BYTES, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + /** + * GskGLShader:resource: + * + * resource containing the source code for the shader. + */ + gsk_gl_shader_properties[GLSHADER_PROP_RESOURCE] = + g_param_spec_string ("resource", + "Resources", + "Resource path to the source code", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, GLSHADER_N_PROPS, gsk_gl_shader_properties); +} + +static void +gsk_gl_shader_init (GskGLShader *shader) +{ + shader->uniforms = g_array_new (FALSE, FALSE, sizeof (GskGLUniform)); +} + +/** + * gsk_gl_shader_new_from_bytes: + * @sourcecode: The sourcecode for the shader, as a #GBytes + * + * Creates a #GskGLShader that will render pixels using the specified code. + * + * Returns: (transfer full): A new #GskGLShader + */ +GskGLShader * +gsk_gl_shader_new_from_bytes (GBytes *sourcecode) +{ + g_return_val_if_fail (sourcecode != NULL, NULL); + + return g_object_new (GSK_TYPE_GL_SHADER, + "source", sourcecode, + NULL); +} + +/** + * gsk_gl_shader_new_from_resource: + * @resource_path: valid path to a resource that contains the sourcecode for the shader + * + * Creates a #GskGLShader that will render pixels using the specified code. + * + * Returns: (transfer full): A new #GskGLShader + */ +GskGLShader * +gsk_gl_shader_new_from_resource (const char *resource_path) +{ + g_return_val_if_fail (resource_path != NULL, NULL); + + return g_object_new (GSK_TYPE_GL_SHADER, + "resource", resource_path, + NULL); +} + +/** + * gsk_gl_shader_compile: + * @shader: A #GskGLShader + * @renderer: A #GskRenderer + * @error: Location to store error int + * + * Tries to compile the @shader for the given @renderer, and reports + * %FALSE with an error if there is a problem. You should use this + * before relying on the shader for rendering and use a fallback with + * a simpler shader or without shaders if it fails. + * + * Note that this will modify the rendering state (for example + * change the current GL context) and requires the renderer to be + * set up. This means that the widget has to be realized. Commonly you + * want to call this from the realize signal of a widget, or during + * widget snapshot. + * + * Returns: %TRUE on success, %FALSE if an error occurred + */ +gboolean +gsk_gl_shader_compile (GskGLShader *shader, + GskRenderer *renderer, + GError **error) +{ + g_return_val_if_fail (GSK_IS_GL_SHADER (shader), FALSE); + + if (GSK_IS_GL_RENDERER (renderer)) + return gsk_gl_render_try_compile_gl_shader (GSK_GL_RENDERER (renderer), + shader, error); + + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "The renderer does not support gl shaders"); + return FALSE; +} + + +/** + * gsk_gl_shader_get_source: + * @shader: A #GskGLShader + * + * Get the source code being used to render this shader. + * + * Returns: (transfer none): The source code for the shader + */ +GBytes * +gsk_gl_shader_get_source (GskGLShader *shader) +{ + g_return_val_if_fail (GSK_IS_GL_SHADER (shader), NULL); + + return shader->source; +} + +/** + * gsk_gl_shader_get_resource: + * @shader: A #GskGLShader + * + * Get the resource path for the sourcecode being used to render this shader. + * + * Returns: (transfer none): The resource path for the shader, or %NULL if none. + */ +const char * +gsk_gl_shader_get_resource (GskGLShader *shader) +{ + g_return_val_if_fail (GSK_IS_GL_SHADER (shader), NULL); + + return shader->resource; +} + +/** + * gsk_gl_shader_get_n_textures: + * @shader: A #GskGLShader + * + * Returns the number of textures that the shader requires. This can be used + * to check that the a passed shader works in your usecase. This + * is determined by looking at the highest u_textureN value that the + * shader defines. + * + * Returns: The nr of texture input this shader requires. + */ +int +gsk_gl_shader_get_n_textures (GskGLShader *shader) +{ + g_return_val_if_fail (GSK_IS_GL_SHADER (shader), 0); + + return shader->n_textures; +} + +/** + * gsk_gl_shader_get_n_uniforms: + * @shader: A #GskGLShader + * + * Get the number of declared uniforms for this shader. + * + * Returns: The nr of declared uniforms + */ +int +gsk_gl_shader_get_n_uniforms (GskGLShader *shader) +{ + g_return_val_if_fail (GSK_IS_GL_SHADER (shader), 0); + + return shader->uniforms->len; +} + +/** + * gsk_gl_shader_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_gl_shader_get_uniform_name (GskGLShader *shader, + int idx) +{ + g_return_val_if_fail (GSK_IS_GL_SHADER (shader), NULL); + + return g_array_index (shader->uniforms, GskGLUniform, idx).name; +} + +/** + * gsk_gl_shader_find_uniform_by_name: + * @shader: A #GskGLShader + * @name: A uniform name + * + * Looks for a uniform by the name @name, and returns the index + * of the unifor, or -1 if it was not found. + * + * Returns: The index of the uniform, or -1 + */ +int +gsk_gl_shader_find_uniform_by_name (GskGLShader *shader, + const char *name) +{ + g_return_val_if_fail (GSK_IS_GL_SHADER (shader), -1); + + for (int i = 0; i < shader->uniforms->len; i++) + { + const GskGLUniform *u = &g_array_index (shader->uniforms, GskGLUniform, i); + if (strcmp (u->name, name) == 0) + return i; + } + + return -1; +} + +/** + * gsk_gl_shader_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_gl_shader_get_uniform_type (GskGLShader *shader, + int idx) +{ + g_return_val_if_fail (GSK_IS_GL_SHADER (shader), 0); + + return g_array_index (shader->uniforms, GskGLUniform, idx).type; +} + +/** + * gsk_gl_shader_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_gl_shader_get_uniform_offset (GskGLShader *shader, + int idx) +{ + g_return_val_if_fail (GSK_IS_GL_SHADER (shader), 0); + + return g_array_index (shader->uniforms, GskGLUniform, idx).offset; +} + +const GskGLUniform * +gsk_gl_shader_get_uniforms (GskGLShader *shader, + int *n_uniforms) +{ + *n_uniforms = shader->uniforms->len; + return &g_array_index (shader->uniforms, GskGLUniform, 0); +} + +/** + * gsk_gl_shader_get_args_size: + * @shader: A #GskGLShader + * + * Get the size of the data block used to specify arguments for this shader. + * + * Returns: The size of the data block + */ +gsize +gsk_gl_shader_get_args_size (GskGLShader *shader) +{ + g_return_val_if_fail (GSK_IS_GL_SHADER (shader), 0); + + return shader->uniforms_size; +} + +static const GskGLUniform * +gsk_gl_shader_find_uniform (GskGLShader *shader, + const char *name) +{ + for (int 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_gl_shader_get_arg_float: + * @shader: A #GskGLShader + * @args: The uniform arguments + * @idx: The index of the uniform + * + * Gets the value of the uniform @idx in the @args block. + * The uniform must be of float type. + * + * Returns: The value + */ +float +gsk_gl_shader_get_arg_float (GskGLShader *shader, + GBytes *args, + int idx) +{ + const GskGLUniform *u; + const guchar *args_src; + gsize size; + const guchar *data = g_bytes_get_data (args, &size); + + g_return_val_if_fail (GSK_IS_GL_SHADER (shader), 0); + + g_assert (size == shader->uniforms_size); + g_assert (idx < shader->uniforms->len); + u = &g_array_index (shader->uniforms, GskGLUniform, idx); + g_assert (u->type == GSK_GL_UNIFORM_TYPE_FLOAT); + + args_src = data + u->offset; + return *(float *)args_src; +} + +/** + * gsk_gl_shader_get_arg_int: + * @shader: A #GskGLShader + * @args: The uniform arguments + * @idx: The index of the uniform + * + * Gets the value of the uniform @idx in the @args block. + * The uniform must be of int type. + * + * Returns: The value + */ +gint32 +gsk_gl_shader_get_arg_int (GskGLShader *shader, + GBytes *args, + int idx) +{ + const GskGLUniform *u; + const guchar *args_src; + gsize size; + const guchar *data = g_bytes_get_data (args, &size); + + g_return_val_if_fail (GSK_IS_GL_SHADER (shader), 0); + + g_assert (size == shader->uniforms_size); + g_assert (idx < shader->uniforms->len); + u = &g_array_index (shader->uniforms, GskGLUniform, idx); + g_assert (u->type == GSK_GL_UNIFORM_TYPE_INT); + + args_src = data + u->offset; + return *(gint32 *)args_src; +} + +/** + * gsk_gl_shader_get_arg_uint: + * @shader: A #GskGLShader + * @args: The uniform arguments + * @idx: The index of the uniform + * + * Gets the value of the uniform @idx in the @args block. + * The uniform must be of uint type. + * + * Returns: The value + */ +guint32 +gsk_gl_shader_get_arg_uint (GskGLShader *shader, + GBytes *args, + int idx) +{ + const GskGLUniform *u; + const guchar *args_src; + gsize size; + const guchar *data = g_bytes_get_data (args, &size); + + g_return_val_if_fail (GSK_IS_GL_SHADER (shader), 0); + + g_assert (size == shader->uniforms_size); + g_assert (idx < shader->uniforms->len); + u = &g_array_index (shader->uniforms, GskGLUniform, idx); + g_assert (u->type == GSK_GL_UNIFORM_TYPE_UINT); + + args_src = data + u->offset; + return *(guint32 *)args_src; +} + +/** + * gsk_gl_shader_get_arg_bool: + * @shader: A #GskGLShader + * @args: The uniform arguments + * @idx: The index of the uniform + * + * Gets the value of the uniform @idx in the @args block. + * The uniform must be of bool type. + * + * Returns: The value + */ +gboolean +gsk_gl_shader_get_arg_bool (GskGLShader *shader, + GBytes *args, + int idx) +{ + const GskGLUniform *u; + const guchar *args_src; + gsize size; + const guchar *data = g_bytes_get_data (args, &size); + + g_return_val_if_fail (GSK_IS_GL_SHADER (shader), 0); + + g_assert (size == shader->uniforms_size); + g_assert (idx < shader->uniforms->len); + u = &g_array_index (shader->uniforms, GskGLUniform, idx); + g_assert (u->type == GSK_GL_UNIFORM_TYPE_BOOL); + + args_src = data + u->offset; + return *(guint32 *)args_src; +} + +/** + * gsk_gl_shader_get_arg_vec2: + * @shader: A #GskGLShader + * @args: The uniform arguments + * @idx: The index of the uniform + * @out_value: Location to store set the uniform value too + * + * Gets the value of the uniform @idx in the @args block. + * The uniform must be of vec2 type. + */ +void +gsk_gl_shader_get_arg_vec2 (GskGLShader *shader, + GBytes *args, + int idx, + graphene_vec2_t *out_value) +{ + const GskGLUniform *u; + const guchar *args_src; + gsize size; + const guchar *data = g_bytes_get_data (args, &size); + + g_return_if_fail (GSK_IS_GL_SHADER (shader)); + + g_assert (size == shader->uniforms_size); + g_assert (idx < shader->uniforms->len); + u = &g_array_index (shader->uniforms, GskGLUniform, idx); + g_assert (u->type == GSK_GL_UNIFORM_TYPE_VEC2); + + args_src = data + u->offset; + graphene_vec2_init_from_float (out_value, (float *)args_src); +} + +/** + * gsk_gl_shader_get_arg_vec3: + * @shader: A #GskGLShader + * @args: The uniform arguments + * @idx: The index of the uniform + * @out_value: Location to store set the uniform value too + * + * Gets the value of the uniform @idx in the @args block. + * The uniform must be of vec3 type. + */ +void +gsk_gl_shader_get_arg_vec3 (GskGLShader *shader, + GBytes *args, + int idx, + graphene_vec3_t *out_value) +{ + const GskGLUniform *u; + const guchar *args_src; + gsize size; + const guchar *data = g_bytes_get_data (args, &size); + + g_return_if_fail (GSK_IS_GL_SHADER (shader)); + + g_assert (size == shader->uniforms_size); + g_assert (idx < shader->uniforms->len); + u = &g_array_index (shader->uniforms, GskGLUniform, idx); + g_assert (u->type == GSK_GL_UNIFORM_TYPE_VEC3); + + args_src = data + u->offset; + graphene_vec3_init_from_float (out_value, (float *)args_src); +} + +/** + * gsk_gl_shader_get_arg_vec4: + * @shader: A #GskGLShader + * @args: The uniform arguments + * @idx: The index of the uniform + * @out_value: Location to store set the uniform value too + * + * Gets the value of the uniform @idx in the @args block. + * The uniform must be of vec4 type. + */ +void +gsk_gl_shader_get_arg_vec4 (GskGLShader *shader, + GBytes *args, + int idx, + graphene_vec4_t *out_value) +{ + const GskGLUniform *u; + const guchar *args_src; + gsize size; + const guchar *data = g_bytes_get_data (args, &size); + + g_return_if_fail (GSK_IS_GL_SHADER (shader)); + + g_assert (size == shader->uniforms_size); + g_assert (idx < shader->uniforms->len); + u = &g_array_index (shader->uniforms, GskGLUniform, idx); + g_assert (u->type == GSK_GL_UNIFORM_TYPE_VEC4); + + args_src = data + u->offset; + graphene_vec4_init_from_float (out_value, (float *)args_src); +} + +/** + * gsk_gl_shader_format_args_va: + * @shader: A #GskGLShader + * @uniforms: Name-Value pairs for the uniforms of @shader, ending with a + * %NULL name, all values are passed by reference. + * + * 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 values for the types that match the declared uniforms + * (i.e. double/int/guint/gboolean for primitive values and + * `graphene_vecN_t *` for vecN uniforms). + * + * It is an error to pass a uniform name that is not declared by the shader. + * + * Any uniforms of the shader that are not included in the argument list + * are zero-initialized. + * + * Returns: (transfer full): A newly allocated block of data which can be + * passed to gsk_gl_shader_node_new(). + */ +GBytes * +gsk_gl_shader_format_args_va (GskGLShader *shader, + va_list uniforms) +{ + guchar *args = g_malloc0 (shader->uniforms_size); + const char *name; + + g_return_val_if_fail (GSK_IS_GL_SHADER (shader), NULL); + + while ((name = va_arg (uniforms, const char *)) != NULL) + { + const GskGLUniform *u; + guchar *args_dest; + + u = gsk_gl_shader_find_uniform (shader, name); + if (u == NULL) + { + g_warning ("No uniform named `%s` in shader", name); + break; + } + + 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_GL_UNIFORM_TYPE_FLOAT: + *(float *)args_dest = (float)va_arg (uniforms, double); /* floats are promoted to double in varargs */ + break; + + case GSK_GL_UNIFORM_TYPE_INT: + *(gint32 *)args_dest = (gint32)va_arg (uniforms, int); + break; + + case GSK_GL_UNIFORM_TYPE_UINT: + *(guint32 *)args_dest = (guint32)va_arg (uniforms, guint); + break; + + case GSK_GL_UNIFORM_TYPE_BOOL: + *(guint32 *)args_dest = (gboolean)va_arg (uniforms, gboolean); + break; + + case GSK_GL_UNIFORM_TYPE_VEC2: + graphene_vec2_to_float (va_arg (uniforms, const graphene_vec2_t *), + (float *)args_dest); + break; + + case GSK_GL_UNIFORM_TYPE_VEC3: + graphene_vec3_to_float (va_arg (uniforms, const graphene_vec3_t *), + (float *)args_dest); + break; + + case GSK_GL_UNIFORM_TYPE_VEC4: + graphene_vec4_to_float (va_arg (uniforms, const graphene_vec4_t *), + (float *)args_dest); + break; + + case GSK_GL_UNIFORM_TYPE_NONE: + default: + g_assert_not_reached (); + } + } + + return g_bytes_new_take (args, shader->uniforms_size); +} + +/** + * gsk_gl_shader_format_args: + * @shader: A #GskGLShader + * @...: Name-Value pairs for the uniforms of @shader, ending with a %NULL + * name, all values are passed by reference. + * + * 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 values for the types that match the declared uniforms + * (i.e. double/int/guint/gboolean for primitive values and + * `graphene_vecN_t *` for vecN uniforms). + * + * Any uniforms of the shader that are not included in the argument list + * are zero-initialized. + * + * Returns: (transfer full): A newly allocated block of data which can be + * passed to gsk_gl_shader_node_new(). + */ +GBytes * +gsk_gl_shader_format_args (GskGLShader *shader, + ...) +{ + GBytes *bytes; + va_list args; + + va_start (args, shader); + bytes = gsk_gl_shader_format_args_va (shader, args); + va_end (args); + + return bytes; +} + +struct _GskShaderArgsBuilder { + guint ref_count; + GskGLShader *shader; + guchar *data; +}; + +G_DEFINE_BOXED_TYPE (GskShaderArgsBuilder, gsk_shader_args_builder, + gsk_shader_args_builder_ref, + gsk_shader_args_builder_unref); + + +/** + * gsk_gl_shader_build_args: + * @shader: A #GskGLShader + * @initial_values: (nullable): optional #Bytes with initial values + * + * Allocates a builder that can be used to construct a new uniform data + * chunk. + * + * Returns: (transfer full): The newly allocated builder, free with + * gsk_shader_args_builder_free() + */ +GskShaderArgsBuilder * +gsk_shader_args_builder_new (GskGLShader *shader, + GBytes *initial_values) +{ + GskShaderArgsBuilder *builder = g_new0 (GskShaderArgsBuilder, 1); + builder->ref_count = 1; + builder->shader = g_object_ref (shader); + builder->data = g_malloc0 (shader->uniforms_size); + + if (initial_values) + { + gsize size; + const guchar *data = g_bytes_get_data (initial_values, &size); + + g_assert (size == shader->uniforms_size); + memcpy (builder->data, data, size); + } + + return builder; +} + +/** + * gsk_shader_args_builder_to_args: + * @builder: A #GskShaderArgsBuilder + * + * Creates a new #GBytes args from the current state of the + * given @builder. Any uniforms of the shader that have not + * been explicitly set on the @builder are zero-initialized. + * + * The given #GskShaderArgsBuilder is reset once this function returns; + * you cannot call this function multiple times on the same @builder instance. + * + * This function is intended primarily for bindings. C code should use + * gsk_shader_args_builder_free_to_args(). + * + * + * Returns: (transfer full): The newly allocated builder, free with + * gsk_shader_args_builder_free() + */ +GBytes * +gsk_shader_args_builder_to_args (GskShaderArgsBuilder *builder) +{ + return g_bytes_new_take (g_steal_pointer (&builder->data), + builder->shader->uniforms_size); +} + +/** + * gdk_content_formats_builder_free_to_args: (skip) + * @builder: a #GdkContentFormatsBuilder + * + * Creates a new #GBytes args from the current state of the + * given @builder, and frees the @builder instance. Any uniforms + * of the shader that have not been explicitly set on the @builder + * are zero-initialized. + * + * Returns: (transfer full): the newly created #GBytes + * with all the args added to @builder + */ +GBytes * +gsk_shader_args_builder_free_to_args (GskShaderArgsBuilder *builder) +{ + GBytes *res; + + g_return_val_if_fail (builder != NULL, NULL); + + res = gsk_shader_args_builder_to_args (builder); + + gsk_shader_args_builder_unref (builder); + + return res; +} + + +/** + * gsk_shader_args_builder_unref: + * @builder: A #GskShaderArgsBuilder + * + * Decreases the reference count of a #GskShaderArgBuilder by one. + * If the resulting reference count is zero, frees the builder. + */ +void +gsk_shader_args_builder_unref (GskShaderArgsBuilder *builder) + +{ + g_return_if_fail (builder != NULL); + g_return_if_fail (builder->ref_count > 0); + + builder->ref_count--; + if (builder->ref_count > 0) + return; + + g_object_unref (builder->shader); + g_free (builder->data); + g_free (builder); +} + +/** + * gsk_shader_args_builder_ref: + * @builder: A #GskShaderArgsBuilder + * + * Increases the reference count of a #GskShaderArgsBuilder by one. + * + * Returns: the passed in #GskShaderArgsBuilder. + */ +GskShaderArgsBuilder * +gsk_shader_args_builder_ref (GskShaderArgsBuilder *builder) +{ + g_return_val_if_fail (builder != NULL, NULL); + + builder->ref_count++; + return builder; +} + +/** + * gsk_shader_args_builder_set_float: + * @builder: A #GskShaderArgsBuilder + * @idx: The index of the uniform + * @value: The value to set the uniform too + * + * Sets the value of the uniform @idx. + * The uniform must be of float type. + */ +void +gsk_shader_args_builder_set_float (GskShaderArgsBuilder *builder, + int idx, + float value) +{ + GskGLShader *shader = builder->shader; + const GskGLUniform *u; + guchar *args_dest; + + g_assert (builder->data != NULL); + g_assert (idx < shader->uniforms->len); + u = &g_array_index (shader->uniforms, GskGLUniform, idx); + g_assert (u->type == GSK_GL_UNIFORM_TYPE_FLOAT); + + args_dest = builder->data + u->offset; + *(float *)args_dest = value; +} + +/** + * gsk_shader_args_builder_set_int: + * @builder: A #GskShaderArgsBuilder + * @idx: The index of the uniform + * @value: The value to set the uniform too + * + * Sets the value of the uniform @idx. + * The uniform must be of int type. + */ +void +gsk_shader_args_builder_set_int (GskShaderArgsBuilder *builder, + int idx, + gint32 value) +{ + GskGLShader *shader = builder->shader; + const GskGLUniform *u; + guchar *args_dest; + + g_assert (builder->data != NULL); + g_assert (idx < shader->uniforms->len); + u = &g_array_index (shader->uniforms, GskGLUniform, idx); + g_assert (u->type == GSK_GL_UNIFORM_TYPE_INT); + + args_dest = builder->data + u->offset; + *(gint32 *)args_dest = value; +} + +/** + * gsk_shader_args_builder_set_uint: + * @builder: A #GskShaderArgsBuilder + * @idx: The index of the uniform + * @value: The value to set the uniform too + * + * Sets the value of the uniform @idx. + * The uniform must be of uint type. + */ +void +gsk_shader_args_builder_set_uint (GskShaderArgsBuilder *builder, + int idx, + guint32 value) +{ + GskGLShader *shader = builder->shader; + const GskGLUniform *u; + guchar *args_dest; + + g_assert (builder->data != NULL); + g_assert (idx < shader->uniforms->len); + u = &g_array_index (shader->uniforms, GskGLUniform, idx); + g_assert (u->type == GSK_GL_UNIFORM_TYPE_UINT); + + args_dest = builder->data + u->offset; + *(guint32 *)args_dest = value; +} + +/** + * gsk_shader_args_builder_set_bool: + * @builder: A #GskShaderArgsBuilder + * @idx: The index of the uniform + * @value: The value to set the uniform too + * + * Sets the value of the uniform @idx. + * The uniform must be of bool type. + */ +void +gsk_shader_args_builder_set_bool (GskShaderArgsBuilder *builder, + int idx, + gboolean value) +{ + GskGLShader *shader = builder->shader; + const GskGLUniform *u; + guchar *args_dest; + + g_assert (builder->data != NULL); + g_assert (idx < shader->uniforms->len); + u = &g_array_index (shader->uniforms, GskGLUniform, idx); + g_assert (u->type == GSK_GL_UNIFORM_TYPE_BOOL); + + args_dest = builder->data + u->offset; + *(guint32 *)args_dest = !!value; +} + +/** + * gsk_shader_args_builder_set_vec2: + * @builder: A #GskShaderArgsBuilder + * @idx: The index of the uniform + * @value: The value to set the uniform too + * + * Sets the value of the uniform @idx. + * The uniform must be of vec2 type. + */ +void +gsk_shader_args_builder_set_vec2 (GskShaderArgsBuilder *builder, + int idx, + const graphene_vec2_t *value) +{ + GskGLShader *shader = builder->shader; + const GskGLUniform *u; + guchar *args_dest; + + g_assert (builder->data != NULL); + g_assert (idx < shader->uniforms->len); + u = &g_array_index (shader->uniforms, GskGLUniform, idx); + g_assert (u->type == GSK_GL_UNIFORM_TYPE_VEC2); + + args_dest = builder->data + u->offset; + graphene_vec2_to_float (value, (float *)args_dest); +} + +/** + * gsk_shader_args_builder_set_vec3: + * @builder: A #GskShaderArgsBuilder + * @idx: The index of the uniform + * @value: The value to set the uniform too + * + * Sets the value of the uniform @idx. + * The uniform must be of vec3 type. + */ +void +gsk_shader_args_builder_set_vec3 (GskShaderArgsBuilder *builder, + int idx, + const graphene_vec3_t *value) +{ + GskGLShader *shader = builder->shader; + const GskGLUniform *u; + guchar *args_dest; + + g_assert (builder->data != NULL); + g_assert (idx < shader->uniforms->len); + u = &g_array_index (shader->uniforms, GskGLUniform, idx); + g_assert (u->type == GSK_GL_UNIFORM_TYPE_VEC3); + + args_dest = builder->data + u->offset; + graphene_vec3_to_float (value, (float *)args_dest); +} + +/** + * gsk_shader_args_builder_set_vec4: + * @builder: A #GskShaderArgsBuilder + * @idx: The index of the uniform + * @value: The value to set the uniform too + * + * Sets the value of the uniform @idx. + * The uniform must be of vec4 type. + */ +void +gsk_shader_args_builder_set_vec4 (GskShaderArgsBuilder *builder, + int idx, + const graphene_vec4_t *value) +{ + GskGLShader *shader = builder->shader; + const GskGLUniform *u; + guchar *args_dest; + + g_assert (builder->data != NULL); + g_assert (idx < shader->uniforms->len); + u = &g_array_index (shader->uniforms, GskGLUniform, idx); + g_assert (u->type == GSK_GL_UNIFORM_TYPE_VEC4); + + args_dest = builder->data + u->offset; + graphene_vec4_to_float (value, (float *)args_dest); +} diff --git a/gsk/gskglshader.h b/gsk/gskglshader.h new file mode 100644 index 0000000000..5a35394095 --- /dev/null +++ b/gsk/gskglshader.h @@ -0,0 +1,162 @@ +/* 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef __GSK_GL_SHADER_H__ +#define __GSK_GL_SHADER_H__ + +#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only <gsk/gsk.h> can be included directly." +#endif + +#include <stdarg.h> + +#include <gsk/gsktypes.h> +#include <gsk/gskenums.h> + +G_BEGIN_DECLS + +#define GSK_TYPE_SHADER_ARGS_BUILDER (gsk_shader_args_builder_get_type ()) + +typedef struct _GskShaderArgsBuilder GskShaderArgsBuilder; + +#define GSK_TYPE_GL_SHADER (gsk_gl_shader_get_type ()) + +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GskGLShader, gsk_gl_shader, GSK, GL_SHADER, GObject) + +GDK_AVAILABLE_IN_ALL +GskGLShader * gsk_gl_shader_new_from_bytes (GBytes *sourcecode); +GDK_AVAILABLE_IN_ALL +GskGLShader * gsk_gl_shader_new_from_resource (const char *resource_path); +GDK_AVAILABLE_IN_ALL +gboolean gsk_gl_shader_compile (GskGLShader *shader, + GskRenderer *renderer, + GError **error); +GDK_AVAILABLE_IN_ALL +GBytes * gsk_gl_shader_get_source (GskGLShader *shader); +GDK_AVAILABLE_IN_ALL +const char * gsk_gl_shader_get_resource (GskGLShader *shader); +GDK_AVAILABLE_IN_ALL +int gsk_gl_shader_get_n_textures (GskGLShader *shader); +GDK_AVAILABLE_IN_ALL +int gsk_gl_shader_get_n_uniforms (GskGLShader *shader); +GDK_AVAILABLE_IN_ALL +const char * gsk_gl_shader_get_uniform_name (GskGLShader *shader, + int idx); +GDK_AVAILABLE_IN_ALL +int gsk_gl_shader_find_uniform_by_name (GskGLShader *shader, + const char *name); +GDK_AVAILABLE_IN_ALL +GskGLUniformType gsk_gl_shader_get_uniform_type (GskGLShader *shader, + int idx); +GDK_AVAILABLE_IN_ALL +int gsk_gl_shader_get_uniform_offset (GskGLShader *shader, + int idx); +GDK_AVAILABLE_IN_ALL +gsize gsk_gl_shader_get_args_size (GskGLShader *shader); + + +/* Helpers for managing shader args */ + +GDK_AVAILABLE_IN_ALL +GBytes * gsk_gl_shader_format_args_va (GskGLShader *shader, + va_list uniforms); +GDK_AVAILABLE_IN_ALL +GBytes * gsk_gl_shader_format_args (GskGLShader *shader, + ...) G_GNUC_NULL_TERMINATED; + +GDK_AVAILABLE_IN_ALL +float gsk_gl_shader_get_arg_float (GskGLShader *shader, + GBytes *args, + int idx); +GDK_AVAILABLE_IN_ALL +gint32 gsk_gl_shader_get_arg_int (GskGLShader *shader, + GBytes *args, + int idx); +GDK_AVAILABLE_IN_ALL +guint32 gsk_gl_shader_get_arg_uint (GskGLShader *shader, + GBytes *args, + int idx); +GDK_AVAILABLE_IN_ALL +gboolean gsk_gl_shader_get_arg_bool (GskGLShader *shader, + GBytes *args, + int idx); +GDK_AVAILABLE_IN_ALL +void gsk_gl_shader_get_arg_vec2 (GskGLShader *shader, + GBytes *args, + int idx, + graphene_vec2_t *out_value); +GDK_AVAILABLE_IN_ALL +void gsk_gl_shader_get_arg_vec3 (GskGLShader *shader, + GBytes *args, + int idx, + graphene_vec3_t *out_value); +GDK_AVAILABLE_IN_ALL +void gsk_gl_shader_get_arg_vec4 (GskGLShader *shader, + GBytes *args, + int idx, + graphene_vec4_t *out_value); + +GDK_AVAILABLE_IN_ALL +GType gsk_shader_args_builder_get_type (void) G_GNUC_CONST; + +GDK_AVAILABLE_IN_ALL +GskShaderArgsBuilder *gsk_shader_args_builder_new (GskGLShader *shader, + GBytes *initial_values); +GDK_AVAILABLE_IN_ALL +GBytes * gsk_shader_args_builder_to_args (GskShaderArgsBuilder *builder); +GDK_AVAILABLE_IN_ALL +GBytes * gsk_shader_args_builder_free_to_args (GskShaderArgsBuilder *builder); +GDK_AVAILABLE_IN_ALL +GskShaderArgsBuilder *gsk_shader_args_builder_ref (GskShaderArgsBuilder *builder); +GDK_AVAILABLE_IN_ALL +void gsk_shader_args_builder_unref (GskShaderArgsBuilder *builder); + +GDK_AVAILABLE_IN_ALL +void gsk_shader_args_builder_set_float (GskShaderArgsBuilder *builder, + int idx, + float value); +GDK_AVAILABLE_IN_ALL +void gsk_shader_args_builder_set_int (GskShaderArgsBuilder *builder, + int idx, + gint32 value); +GDK_AVAILABLE_IN_ALL +void gsk_shader_args_builder_set_uint (GskShaderArgsBuilder *builder, + int idx, + guint32 value); +GDK_AVAILABLE_IN_ALL +void gsk_shader_args_builder_set_bool (GskShaderArgsBuilder *builder, + int idx, + gboolean value); +GDK_AVAILABLE_IN_ALL +void gsk_shader_args_builder_set_vec2 (GskShaderArgsBuilder *builder, + int idx, + const graphene_vec2_t *value); +GDK_AVAILABLE_IN_ALL +void gsk_shader_args_builder_set_vec3 (GskShaderArgsBuilder *builder, + int idx, + const graphene_vec3_t *value); +GDK_AVAILABLE_IN_ALL +void gsk_shader_args_builder_set_vec4 (GskShaderArgsBuilder *builder, + int idx, + const graphene_vec4_t *value); + + +G_END_DECLS + +#endif /* __GSK_GL_SHADER_H__ */ diff --git a/gsk/gskglshaderprivate.h b/gsk/gskglshaderprivate.h new file mode 100644 index 0000000000..3dc82c35dc --- /dev/null +++ b/gsk/gskglshaderprivate.h @@ -0,0 +1,19 @@ +#ifndef __GSK_GLSHADER_PRIVATE_H__ +#define __GSK_GLSHADER_PRIVATE_H__ + +#include <gsk/gskglshader.h> + +G_BEGIN_DECLS + +typedef struct { + char *name; + GskGLUniformType type; + gsize offset; +} GskGLUniform; + +const GskGLUniform *gsk_gl_shader_get_uniforms (GskGLShader *shader, + int *n_uniforms); + +G_END_DECLS + +#endif /* __GSK_GLSHADER_PRIVATE_H__ */ diff --git a/gsk/gskrendernode.h b/gsk/gskrendernode.h index 424039753a..dd655d3b35 100644 --- a/gsk/gskrendernode.h +++ b/gsk/gskrendernode.h @@ -25,6 +25,7 @@ #include <gsk/gskroundedrect.h> #include <gsk/gsktypes.h> +#include <gsk/gskglshader.h> #include <gtk/css/gtkcss.h> 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_GL_SHADER_NODE (gsk_gl_shader_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,24 @@ 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_gl_shader_node_get_type (void) G_GNUC_CONST; +GDK_AVAILABLE_IN_ALL +GskRenderNode * gsk_gl_shader_node_new (GskGLShader *shader, + const graphene_rect_t *bounds, + GBytes *args, + GskRenderNode **children, + guint n_children); +GDK_AVAILABLE_IN_ALL +guint gsk_gl_shader_node_get_n_children (GskRenderNode *node); +GDK_AVAILABLE_IN_ALL +GskRenderNode * gsk_gl_shader_node_get_child (GskRenderNode *node, + guint idx); +GDK_AVAILABLE_IN_ALL +GBytes * gsk_gl_shader_node_get_args (GskRenderNode *node); +GDK_AVAILABLE_IN_ALL +GskGLShader * gsk_gl_shader_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..1a54bd972c 100644 --- a/gsk/gskrendernodeimpl.c +++ b/gsk/gskrendernodeimpl.c @@ -4470,6 +4470,209 @@ gsk_debug_node_get_message (GskRenderNode *node) return self->message; } +/*** GSK_GL_SHADER_NODE ***/ + +struct _GskGLShaderNode +{ + GskRenderNode render_node; + + GskGLShader *shader; + GBytes *args; + GskRenderNode **children; + guint n_children; +}; + +static void +gsk_gl_shader_node_finalize (GskRenderNode *node) +{ + GskGLShaderNode *self = (GskGLShaderNode *) node; + GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_GL_SHADER_NODE)); + + for (guint i = 0; i < self->n_children; i++) + gsk_render_node_unref (self->children[i]); + g_free (self->children); + + g_bytes_unref (self->args); + + g_object_unref (self->shader); + + parent_class->finalize (node); +} + +static void +gsk_gl_shader_node_draw (GskRenderNode *node, + cairo_t *cr) +{ + cairo_set_source_rgb (cr, 255 / 255., 105 / 255., 180 / 255.); + gsk_cairo_rectangle (cr, &node->bounds); + cairo_fill (cr); +} + +static void +gsk_gl_shader_node_diff (GskRenderNode *node1, + GskRenderNode *node2, + cairo_region_t *region) +{ + GskGLShaderNode *self1 = (GskGLShaderNode *) node1; + GskGLShaderNode *self2 = (GskGLShaderNode *) node2; + + if (graphene_rect_equal (&node1->bounds, &node2->bounds) && + self1->shader == self2->shader && + g_bytes_compare (self1->args, self2->args) == 0 && + self1->n_children == self2->n_children) + { + cairo_region_t *child_region = cairo_region_create(); + for (guint i = 0; i < self1->n_children; i++) + gsk_render_node_diff (self1->children[i], self2->children[i], child_region); + if (!cairo_region_is_empty (child_region)) + gsk_render_node_diff_impossible (node1, node2, region); + cairo_region_destroy (child_region); + } + else + { + gsk_render_node_diff_impossible (node1, node2, region); + } +} + +/** + * gsk_gl_shader_node_new: + * @shader: the #GskGLShader + * @bounds: the rectangle to render the shader into + * @args: Arguments for the uniforms + * @children: (array length=n_children): array of child nodes, these will + * be rendered to textures and used as input. + * @n_children: Length of @children (currenly the GL backend supports + * up to 4 children) + * + * Creates a #GskRenderNode that will render the given @shader into the + * area given by @bounds. The @args 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_gl_shader_format_args() or #GskGLShaderArgBuilder. + * + * See #GskGLShader for details about how the shader should be written. + * + * All the children will be rendered into textures (if they aren't already + * #GskTextureNodes, which will be used directly). These textures will be + * sent as input to the shader. + * + * If the renderer doesn't support GL shaders, or if there is any problem + * when compiling the shader, then the node will draw pink. You should use + * gsk_gl_shader_compile() to ensure the @shader will work for the + * renderer before using it. + * + * Returns: (transfer full) (type GskGLShaderNode): A new #GskRenderNode + */ +GskRenderNode * +gsk_gl_shader_node_new (GskGLShader *shader, + const graphene_rect_t *bounds, + GBytes *args, + GskRenderNode **children, + guint n_children) +{ + GskGLShaderNode *self; + GskRenderNode *node; + + g_return_val_if_fail (GSK_IS_GL_SHADER (shader), NULL); + g_return_val_if_fail (bounds != NULL, NULL); + g_return_val_if_fail ((args == NULL && gsk_gl_shader_get_n_uniforms (shader) == 0) || + (args != NULL && g_bytes_get_size (args) == gsk_gl_shader_get_args_size (shader)), NULL); + g_return_val_if_fail ((children == NULL && n_children == 0) || + (children != NULL && n_children == gsk_gl_shader_get_n_textures (shader)), NULL); + + self = gsk_render_node_alloc (GSK_GL_SHADER_NODE); + node = (GskRenderNode *) self; + + graphene_rect_init_from_rect (&node->bounds, bounds); + self->shader = g_object_ref (shader); + + self->args = g_bytes_ref (args); + + 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_gl_shader_node_get_n_children: + * @node: (type GskGLShaderNode): a #GskRenderNode for a gl shader + * + * Returns the number of children + * + * Returns: The number of children + */ +guint +gsk_gl_shader_node_get_n_children (GskRenderNode *node) +{ + GskGLShaderNode *self = (GskGLShaderNode *) node; + + g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_GL_SHADER_NODE), 0); + + return self->n_children; +} + +/** + * gsk_gl_shader_node_get_child: + * @node: (type GskGLShaderNode): a #GskRenderNode for a gl shader + * @idx: the position of the child to get + * + * Gets one of the children. + * + * Returns: (transfer none): the @idx'th child of @node + */ +GskRenderNode * +gsk_gl_shader_node_get_child (GskRenderNode *node, + guint idx) +{ + GskGLShaderNode *self = (GskGLShaderNode *) node; + + g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_GL_SHADER_NODE), NULL); + g_return_val_if_fail (idx < self->n_children, NULL); + + return self->children[idx]; +} + +/** + * gsk_gl_shader_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_gl_shader_node_get_shader (GskRenderNode *node) +{ + GskGLShaderNode *self = (GskGLShaderNode *) node; + + g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_GL_SHADER_NODE), 0); + + return self->shader; +} + +/** + * gsk_gl_shader_node_get_args: + * @node: (type GskGLShaderNode): a #GskRenderNode for a gl shader + * + * Gets args for the node. + * + * Returns: (transfer none): A #GBytes with the uniform arguments + */ +GBytes * +gsk_gl_shader_node_get_args (GskRenderNode *node) +{ + GskGLShaderNode *self = (GskGLShaderNode *) node; + + g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_GL_SHADER_NODE), NULL); + + return self->args; +} + GType gsk_render_node_types[GSK_RENDER_NODE_TYPE_N_TYPES]; #ifndef I_ @@ -4506,6 +4709,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_gl_shader_node, GSK_GL_SHADER_NODE) GSK_DEFINE_RENDER_NODE_TYPE (gsk_debug_node, GSK_DEBUG_NODE) static void @@ -4866,6 +5070,22 @@ gsk_render_node_init_types_once (void) { const GskRenderNodeTypeInfo node_info = { + GSK_GL_SHADER_NODE, + sizeof (GskGLShaderNode), + NULL, + gsk_gl_shader_node_finalize, + gsk_gl_shader_node_draw, + NULL, + gsk_gl_shader_node_diff, + }; + + GType node_type = gsk_render_node_type_register_static (I_("GskGLShaderNode"), &node_info); + gsk_render_node_types[GSK_GL_SHADER_NODE] = node_type; + } + + { + const GskRenderNodeTypeInfo node_info = + { GSK_DEBUG_NODE, sizeof (GskDebugNode), NULL, diff --git a/gsk/gskrendernodeparser.c b/gsk/gskrendernodeparser.c index 870ce8818b..667a68f8a9 100644 --- a/gsk/gskrendernodeparser.c +++ b/gsk/gskrendernodeparser.c @@ -824,9 +824,9 @@ clear_node (gpointer inout_node) static GskRenderNode * parse_container_node (GtkCssParser *parser) { - GskRenderNode *node; GPtrArray *nodes; const GtkCssToken *token; + GskRenderNode *node; nodes = g_ptr_array_new_with_free_func ((GDestroyNotify) gsk_render_node_unref); @@ -1089,6 +1089,209 @@ parse_inset_shadow_node (GtkCssParser *parser) return gsk_inset_shadow_node_new (&outline, &color, dx, dy, spread, blur); } +typedef union { + gint32 i; + double v[4]; +} UniformValue; + +typedef struct { + GskGLShader *shader; + GskShaderArgsBuilder *args; +} ShaderInfo; + +static void +clear_shader_info (gpointer data) +{ + ShaderInfo *info = data; + g_clear_object (&info->shader); + g_clear_pointer (&info->args, gsk_shader_args_builder_unref); +} + +static gboolean +parse_shader (GtkCssParser *parser, + gpointer out_shader_info) +{ + ShaderInfo *shader_info = out_shader_info; + char *sourcecode = NULL; + GBytes *bytes; + GskGLShader *shader; + + if (!parse_string (parser, &sourcecode)) + return FALSE; + + bytes = g_bytes_new_take (sourcecode, strlen (sourcecode)); + shader = gsk_gl_shader_new_from_bytes (bytes); + g_bytes_unref (bytes); + + shader_info->shader = shader; + + return TRUE; +} + +static gboolean +parse_uniform_value (GtkCssParser *parser, + int idx, + ShaderInfo *shader_info) +{ + switch (gsk_gl_shader_get_uniform_type (shader_info->shader, idx)) + { + case GSK_GL_UNIFORM_TYPE_FLOAT: + { + double f; + if (!gtk_css_parser_consume_number (parser, &f)) + return FALSE; + gsk_shader_args_builder_set_float (shader_info->args, idx, f); + } + break; + + case GSK_GL_UNIFORM_TYPE_INT: + { + int i; + if (!gtk_css_parser_consume_integer (parser, &i)) + return FALSE; + gsk_shader_args_builder_set_int (shader_info->args, idx, i); + } + break; + + case GSK_GL_UNIFORM_TYPE_UINT: + { + int i; + if (!gtk_css_parser_consume_integer (parser, &i) || i < 0) + return FALSE; + gsk_shader_args_builder_set_uint (shader_info->args, idx, i); + } + break; + + case GSK_GL_UNIFORM_TYPE_BOOL: + { + int i; + if (!gtk_css_parser_consume_integer (parser, &i) || (i != 0 && i != 1)) + return FALSE; + gsk_shader_args_builder_set_bool (shader_info->args, idx, i); + } + break; + + case GSK_GL_UNIFORM_TYPE_VEC2: + { + double f0, f1; + graphene_vec2_t v; + if (!gtk_css_parser_consume_number (parser, &f0) || + !gtk_css_parser_consume_number (parser, &f1)) + return FALSE; + graphene_vec2_init (&v, f0, f1); + gsk_shader_args_builder_set_vec2 (shader_info->args, idx, &v); + } + break; + + case GSK_GL_UNIFORM_TYPE_VEC3: + { + double f0, f1, f2; + graphene_vec3_t v; + if (!gtk_css_parser_consume_number (parser, &f0) || + !gtk_css_parser_consume_number (parser, &f1) || + !gtk_css_parser_consume_number (parser, &f2)) + return FALSE; + graphene_vec3_init (&v, f0, f1, f2); + gsk_shader_args_builder_set_vec3 (shader_info->args, idx, &v); + } + break; + + case GSK_GL_UNIFORM_TYPE_VEC4: + { + double f0, f1, f2, f3; + graphene_vec4_t v; + if (!gtk_css_parser_consume_number (parser, &f0) || + !gtk_css_parser_consume_number (parser, &f1) || + !gtk_css_parser_consume_number (parser, &f2) || + !gtk_css_parser_consume_number (parser, &f3)) + return FALSE; + graphene_vec4_init (&v, f0, f1, f2, f3); + gsk_shader_args_builder_set_vec4 (shader_info->args, idx, &v); + } + break; + + case GSK_GL_UNIFORM_TYPE_NONE: + default: + g_assert_not_reached (); + break; + } + + if (idx < gsk_gl_shader_get_n_uniforms (shader_info->shader)) + { + if (!gtk_css_parser_try_token (parser, GTK_CSS_TOKEN_COMMA)) + return FALSE; + } + + return TRUE; +} + +static gboolean +parse_shader_args (GtkCssParser *parser, gpointer data) +{ + ShaderInfo *shader_info = data; + int n_uniforms; + int i; + + shader_info->args = gsk_shader_args_builder_new (shader_info->shader, NULL); + n_uniforms = gsk_gl_shader_get_n_uniforms (shader_info->shader); + + for (i = 0; i < n_uniforms; i++) + { + if (!parse_uniform_value (parser, i, shader_info)) + return FALSE; + } + + return TRUE; +} + +static GskRenderNode * +parse_glshader_node (GtkCssParser *parser) +{ + graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 50, 50); + GskRenderNode *child[4] = { NULL, }; + ShaderInfo shader_info = { + NULL, + NULL, + }; + const Declaration declarations[] = { + { "bounds", parse_rect, NULL, &bounds }, + { "sourcecode", parse_shader, NULL, &shader_info }, + { "args", parse_shader_args, clear_shader_info, &shader_info }, + { "child1", parse_node, clear_node, &child[0] }, + { "child2", parse_node, clear_node, &child[1] }, + { "child3", parse_node, clear_node, &child[2] }, + { "child4", parse_node, clear_node, &child[3] }, + }; + GskGLShader *shader; + GskRenderNode *node; + GBytes *args = NULL; + int len, i; + + parse_declarations (parser, declarations, G_N_ELEMENTS(declarations)); + + for (len = 0; len < 4; len++) + { + if (child[len] == NULL) + break; + } + + shader = shader_info.shader; + args = gsk_shader_args_builder_free_to_args (shader_info.args); + + node = gsk_gl_shader_node_new (shader, &bounds, args, child, len); + + g_bytes_unref (args); + g_object_unref (shader); + + for (i = 0; i < 4; i++) + { + if (child[i]) + gsk_render_node_unref (child[i]); + } + + return node; +} + static GskRenderNode * parse_border_node (GtkCssParser *parser) { @@ -1603,6 +1806,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; @@ -1915,6 +2119,18 @@ append_point_param (Printer *p, } 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); + _gtk_css_print_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, const graphene_vec4_t *value) @@ -2441,6 +2657,124 @@ render_node_print (Printer *p, } break; + case GSK_GL_SHADER_NODE: + { + GskGLShader *shader = gsk_gl_shader_node_get_shader (node); + GBytes *args = gsk_gl_shader_node_get_args (node); + + start_node (p, "glshader"); + + append_rect_param (p, "bounds", &node->bounds); + + GBytes *bytes = gsk_gl_shader_get_source (shader); + /* Ensure we are zero-terminated */ + char *sourcecode = g_strndup (g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes)); + append_string_param (p, "sourcecode", sourcecode); + g_free (sourcecode); + + if (gsk_gl_shader_get_n_uniforms (shader) > 0) + { + GString *data = g_string_new (""); + + for (guint i = 0; i < gsk_gl_shader_get_n_uniforms (shader); i++) + { + if (i > 0) + g_string_append (data, ", "); + + switch (gsk_gl_shader_get_uniform_type (shader, i)) + { + case GSK_GL_UNIFORM_TYPE_NONE: + default: + g_assert_not_reached (); + break; + + case GSK_GL_UNIFORM_TYPE_FLOAT: + { + float value = gsk_gl_shader_get_arg_float (shader, args, i); + string_append_double (data, value); + } + break; + + case GSK_GL_UNIFORM_TYPE_INT: + { + gint32 value = gsk_gl_shader_get_arg_int (shader, args, i); + g_string_append_printf (data, "%d", value); + } + break; + + case GSK_GL_UNIFORM_TYPE_UINT: + { + guint32 value = gsk_gl_shader_get_arg_uint (shader, args, i); + g_string_append_printf (data, "%u", value); + } + break; + + case GSK_GL_UNIFORM_TYPE_BOOL: + { + gboolean value = gsk_gl_shader_get_arg_bool (shader, args, i); + g_string_append_printf (data, "%d", value); + } + break; + + case GSK_GL_UNIFORM_TYPE_VEC2: + { + graphene_vec2_t value; + gsk_gl_shader_get_arg_vec2 (shader, args, i, + &value); + string_append_double (data, graphene_vec2_get_x (&value)); + g_string_append (data, " "); + string_append_double (data, graphene_vec2_get_y (&value)); + } + break; + + case GSK_GL_UNIFORM_TYPE_VEC3: + { + graphene_vec3_t value; + gsk_gl_shader_get_arg_vec3 (shader, args, i, + &value); + string_append_double (data, graphene_vec3_get_x (&value)); + g_string_append (data, " "); + string_append_double (data, graphene_vec3_get_y (&value)); + g_string_append (data, " "); + string_append_double (data, graphene_vec3_get_z (&value)); + } + break; + + case GSK_GL_UNIFORM_TYPE_VEC4: + { + graphene_vec4_t value; + gsk_gl_shader_get_arg_vec4 (shader, args, i, + &value); + string_append_double (data, graphene_vec4_get_x (&value)); + g_string_append (data, " "); + string_append_double (data, graphene_vec4_get_y (&value)); + g_string_append (data, " "); + string_append_double (data, graphene_vec4_get_z (&value)); + g_string_append (data, " "); + string_append_double (data, graphene_vec4_get_w (&value)); + } + break; + } + } + _indent (p); + g_string_append_printf (p->str, "args: %s;\n", data->str); + g_string_free (data, TRUE); + } + + for (guint i = 0; i < gsk_gl_shader_node_get_n_children (node); i ++) + { + GskRenderNode *child = gsk_gl_shader_node_get_child (node, i); + char *name; + + name = g_strdup_printf ("child%d", i + 1); + append_node_param (p, name, child); + g_free (name); + } + + 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..7e11a14b8f 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_GL_SHADER_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..135b0b2def 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_GL_SHADER_NODE: + { + GListStore *store = g_list_store_new (GDK_TYPE_PAINTABLE); + + for (guint i = 0; i < gsk_gl_shader_node_get_n_children (node); i++) + { + GskRenderNode *child = gsk_gl_shader_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_GL_SHADER_NODE: + return "GL Shader"; } } @@ -301,6 +322,7 @@ node_name (GskRenderNode *node) case GSK_CROSS_FADE_NODE: case GSK_TEXT_NODE: case GSK_BLUR_NODE: + case GSK_GL_SHADER_NODE: return g_strdup (node_type_name (gsk_render_node_get_node_type (node))); case GSK_DEBUG_NODE: @@ -512,6 +534,34 @@ add_color_row (GtkListStore *store, } static void +add_int_row (GtkListStore *store, + const char *name, + int value) +{ + char *text = g_strdup_printf ("%d", value); + add_text_row (store, name, text); + g_free (text); +} + +static void +add_uint_row (GtkListStore *store, + const char *name, + guint value) +{ + char *text = g_strdup_printf ("%u", value); + add_text_row (store, name, text); + g_free (text); +} + +static void +add_boolean_row (GtkListStore *store, + const char *name, + gboolean value) +{ + add_text_row (store, name, value ? "TRUE" : "FALSE"); +} + +static void add_float_row (GtkListStore *store, const char *name, float value) @@ -759,6 +809,92 @@ populate_render_node_properties (GtkListStore *store, add_float_row (store, "Radius", gsk_blur_node_get_radius (node)); break; + case GSK_GL_SHADER_NODE: + { + GskGLShader *shader = gsk_gl_shader_node_get_shader (node); + GBytes *args = gsk_gl_shader_node_get_args (node); + int i; + + add_int_row (store, "Required textures", gsk_gl_shader_get_n_textures (shader)); + for (i = 0; i < gsk_gl_shader_get_n_uniforms (shader); i++) + { + const char *name; + char *title; + + name = gsk_gl_shader_get_uniform_name (shader, i); + title = g_strdup_printf ("Uniform %s", name); + + switch (gsk_gl_shader_get_uniform_type (shader, i)) + { + case GSK_GL_UNIFORM_TYPE_NONE: + default: + g_assert_not_reached (); + break; + + case GSK_GL_UNIFORM_TYPE_FLOAT: + add_float_row (store, title, + gsk_gl_shader_get_arg_float (shader, args, i)); + break; + + case GSK_GL_UNIFORM_TYPE_INT: + add_int_row (store, title, + gsk_gl_shader_get_arg_int (shader, args, i)); + break; + + case GSK_GL_UNIFORM_TYPE_UINT: + add_uint_row (store, title, + gsk_gl_shader_get_arg_uint (shader, args, i)); + break; + + case GSK_GL_UNIFORM_TYPE_BOOL: + add_boolean_row (store, title, + gsk_gl_shader_get_arg_bool (shader, args, i)); + break; + + case GSK_GL_UNIFORM_TYPE_VEC2: + { + graphene_vec2_t v; + gsk_gl_shader_get_arg_vec2 (shader, args, i, &v); + float x = graphene_vec2_get_x (&v); + float y = graphene_vec2_get_x (&v); + char *s = g_strdup_printf ("%.2f %.2f", x, y); + add_text_row (store, title, s); + g_free (s); + } + break; + + case GSK_GL_UNIFORM_TYPE_VEC3: + { + graphene_vec3_t v; + gsk_gl_shader_get_arg_vec3 (shader, args, i, &v); + float x = graphene_vec3_get_x (&v); + float y = graphene_vec3_get_y (&v); + float z = graphene_vec3_get_z (&v); + char *s = g_strdup_printf ("%.2f %.2f %.2f", x, y, z); + add_text_row (store, title, s); + g_free (s); + } + break; + + case GSK_GL_UNIFORM_TYPE_VEC4: + { + graphene_vec4_t v; + gsk_gl_shader_get_arg_vec4 (shader, args, i, &v); + float x = graphene_vec4_get_x (&v); + float y = graphene_vec4_get_y (&v); + float z = graphene_vec4_get_z (&v); + float w = graphene_vec4_get_w (&v); + char *s = g_strdup_printf ("%.2f %.2f %.2f %.2f", x, y, z, w); + add_text_row (store, title, s); + g_free (s); + } + break; + } + g_free (title); + } + } + break; + case GSK_INSET_SHADOW_NODE: { const GdkRGBA *color = gsk_inset_shadow_node_peek_color (node); |